// ---------------------------------------------------------------------------
// Live-tail merge hook (sub-project 5, Phase 5 Task 20).
//
// Reads the matching slice of `useTailStore` and returns a single array:
// `baseItems` first (in the order the caller supplied them), then any new
// tail items in arrival order, with duplicates of base ids removed.
//
// Design notes:
// - The dedup runs against `baseItems` (not the other way around) because
//   the base list is the authoritative snapshot from the page's JSON
//   fetch — the tail slice is an opportunistic delta, so the base wins
//   when an id appears in both.
// - The optional `filterFn` is applied to the tail slice AFTER dedup so a
//   page-specific filter (e.g. "only show me submitted claims") doesn't
//   accidentally include items that were already filtered out of base.
// - We don't `useMemo` here: `baseItems` is a new array reference on
//   every render of the caller, so memo deps would invalidate anyway.
//   The merge is cheap (one Set build + two filters) and zustand's
//   selector ensures we only re-render when the slice actually changes.
// - SP25: the `id` type is widened to `string | number` because `Ack`
//   and `Ta1Ack` use numeric database ids while `Claim` / `Remittance`
//   use string ids. Dedup normalizes via `String(...)`.
// - SP28: `ClaimAck` adds another numeric-id slice (`claimAcks` /
//   `claimAckOrder`). Same dedup normalization applies — `String(id)`
//   keeps the symmetric comparison the hook has always used.
// ---------------------------------------------------------------------------

import { useTailStore } from "@/store/tail-store";
import type { TailResource } from "@/lib/tail-stream";

export function useMergedTail<T extends { id: string | number }>(
  resource: TailResource,
  baseItems: T[],
  filterFn?: (item: T) => boolean,
): T[] {
  // Select the slice that matches the resource. We return a fresh array
  // each time so the consumer gets a stable iteration order even when
  // zustand hands us a new container (Record or array) reference.
  const tailSlice = useTailStore((s) => {
    switch (resource) {
      case "claims": {
        // The store keys claims by id in a Record for O(1) updates, but
        // also keeps an `claimOrder` array so we can iterate in arrival
        // order. Filter out any holes (defensive — shouldn't happen but
        // type-narrows the result to Claim[]).
        const out: unknown[] = [];
        for (const id of s.claimOrder) {
          const v = s.claims[id];
          if (v !== undefined) out.push(v);
        }
        return out;
      }
      case "remittances": {
        const out: unknown[] = [];
        for (const id of s.remitOrder) {
          const v = s.remittances[id];
          if (v !== undefined) out.push(v);
        }
        return out;
      }
      case "activity":
        // Activity is already an array — the store appends in arrival
        // order, so iteration order is exactly what we want.
        return s.activity as unknown[];
      // SP25: same keyed-by-id shape as claims/remittances, just with
      // numeric ids. The store keys the dict by String(id); the order
      // array carries the numeric id for arrival-order iteration.
      case "acks": {
        const out: unknown[] = [];
        for (const id of s.ackOrder) {
          const v = s.acks[String(id)];
          if (v !== undefined) out.push(v);
        }
        return out;
      }
      case "ta1_acks": {
        const out: unknown[] = [];
        for (const id of s.ta1AckOrder) {
          const v = s.ta1Acks[String(id)];
          if (v !== undefined) out.push(v);
        }
        return out;
      }
      // SP28: the claim↔ack link slice. Same keyed-by-id shape as
      // acks/ta1_acks (numeric id from the `claim_acks` table), so the
      // dispatch is symmetric — the page-specific filter (e.g.
      // `link.claimId === claimId`) is applied AFTER dedup so the
      // base list's rows aren't accidentally dropped.
      case "claim-acks": {
        const out: unknown[] = [];
        for (const id of s.claimAckOrder) {
          const v = s.claimAcks[String(id)];
          if (v !== undefined) out.push(v);
        }
        return out;
      }
    }
  });

  // Normalize ids to strings so numeric (Ack/Ta1Ack) and string
  // (Claim/Remittance/Activity) ids dedup correctly against each other.
  // (The hook is generic over `T extends { id: string | number }`, but
  // the store's dedup set is a Set<string> internally — using the
  // string form here keeps the comparison symmetric.)
  const baseIds = new Set<string>();
  for (const b of baseItems) baseIds.add(String(b.id));

  const tailAfterDedup: unknown[] = [];
  for (const t of tailSlice) {
    const id = String((t as { id: string | number }).id);
    if (baseIds.has(id)) continue;
    tailAfterDedup.push(t);
  }

  const tailAfterFilter = filterFn
    ? tailAfterDedup.filter((t) => filterFn(t as T))
    : tailAfterDedup;

  return [...baseItems, ...(tailAfterFilter as T[])];
}
