/**
 * Tracks the entities each user has recently opened so the global search
 * dropdown can surface "Recently viewed" suggestions before the user types.
 *
 * Storage: Redis sorted set per (user, port). Key is
 * `recent-views:<userId>:<portId>`, score is the unix epoch of the view,
 * member is `<entityType>:<entityId>`. We trim the set to RECENT_VIEW_MAX
 * after every write so stale ids age out.
 *
 * The companion API route hydrates the typed/labelled rows on read by
 * looking up the underlying tables; this service only deals in (type, id)
 * pairs to stay schema-free and cheap.
 */

import { redis } from '@/lib/redis';

export type RecentlyViewedType =
  | 'client'
  | 'residential-client'
  | 'yacht'
  | 'company'
  | 'interest'
  | 'residential-interest'
  | 'berth'
  | 'invoice'
  | 'expense'
  | 'document';

export interface RecentlyViewedEntry {
  type: RecentlyViewedType;
  id: string;
  /** Unix milliseconds of the most recent view. */
  viewedAt: number;
}

const RECENT_VIEW_TTL = 60 * 60 * 24 * 30; // 30 days
const RECENT_VIEW_MAX = 20;

function key(userId: string, portId: string): string {
  return `recent-views:${userId}:${portId}`;
}

function encode(type: RecentlyViewedType, id: string): string {
  return `${type}:${id}`;
}

function decode(member: string): { type: RecentlyViewedType; id: string } | null {
  const colon = member.indexOf(':');
  if (colon < 0) return null;
  return {
    type: member.slice(0, colon) as RecentlyViewedType,
    id: member.slice(colon + 1),
  };
}

/**
 * Records an entity view. Fire-and-forget - caller should NOT await this
 * in the hot path; the redis op is logged-and-swallowed on failure since
 * a missed view never breaks the user experience.
 */
export function trackView(
  userId: string,
  portId: string,
  type: RecentlyViewedType,
  id: string,
): void {
  if (!userId || !portId || !id) return;

  const k = key(userId, portId);
  const member = encode(type, id);
  const now = Date.now();

  redis
    .zadd(k, now, member)
    .then(() => redis.zremrangebyrank(k, 0, -(RECENT_VIEW_MAX + 1)))
    .then(() => redis.expire(k, RECENT_VIEW_TTL))
    .catch(() => {
      // intentionally swallowed
    });
}

/**
 * Returns the user's recently-viewed entities, newest first. Limit is
 * defensively capped so a misbehaving caller can't pull thousands of rows.
 */
export async function getRecentlyViewed(
  userId: string,
  portId: string,
  limit = 10,
): Promise<RecentlyViewedEntry[]> {
  const k = key(userId, portId);
  const cap = Math.min(Math.max(limit, 1), RECENT_VIEW_MAX);

  // ZREVRANGE WITHSCORES → flat array [member, score, member, score, …]
  const raw = await redis.zrevrange(k, 0, cap - 1, 'WITHSCORES');

  const out: RecentlyViewedEntry[] = [];
  for (let i = 0; i < raw.length; i += 2) {
    const member = raw[i];
    const score = raw[i + 1];
    if (!member || !score) continue;
    const decoded = decode(member);
    if (!decoded) continue;
    out.push({ ...decoded, viewedAt: Number(score) });
  }
  return out;
}

/**
 * Removes a single entity from a user's recent-views (e.g. after the
 * entity is hard-deleted). Best-effort.
 */
export function forgetView(
  userId: string,
  portId: string,
  type: RecentlyViewedType,
  id: string,
): void {
  redis.zrem(key(userId, portId), encode(type, id)).catch(() => {});
}
