"use client";

// Lead Verification data layer. Serves EITHER the local mock (when the Shell isn't configured —
// the demo keeps working) OR the live be-crm data door (crm.leadVerification.*), behind one
// interface so the verification desk is mode-agnostic. Mirrors team-api.ts / leads-api.ts.
//
// Live contract (be-crm):
//   query crm.leadVerification.search { query?, status?, source?, assigneeId?, page?, perPage? }
//                                                        -> { items: VerificationRowDTO[], meta }
//   query crm.leadVerification.get    { id }             -> VerificationDTO (+ activities)
//   query crm.leadVerification.stats  {}                 -> { verified, in_progress, assigned, pending, unverified }
//   cmd   crm.leadVerification.verify        { id, notes? }      -> { verification, lead }  (promotes → Lead)
//   cmd   crm.leadVerification.markUnverified { id, reason? }    -> VerificationDTO
//   cmd   crm.leadVerification.assign        { id, assigneeId }  -> VerificationDTO
//   cmd   crm.leadVerification.reassign      { id, assigneeId? } -> VerificationDTO (→ in_progress)
//   cmd   crm.leadVerification.moveToPending { id }              -> VerificationDTO
//   query crm.team.member.search { perPage }  (reused) -> assignee picker options

import { useCallback, useMemo, useState } from "react";
import { useAppShell, useQuery } from "@abe-kap/appshell-sdk/react";
import { isShellConfigured } from "./appshell";
import {
  V_LEADS as seedVLeads, V_ASSIGNEES,
  type VLead, type VStatus, type VActivity,
} from "@/components/dashboard/verify-data";

export interface Assignee { id: string; name: string; initials: string }

export interface VerifyData {
  live: boolean; loading: boolean; error: string | null;
  leads: VLead[];
  total: number;
  counts: Record<VStatus, number>;
  assignees: Assignee[];
  /** Hydrate a single record's detail (notes, activity, verifiedAt) — synthesized in mock mode. */
  getVerification: (lead: VLead) => Promise<VLead>;
  verify: (lead: VLead, notes?: string) => Promise<void>;
  markUnverified: (lead: VLead, reason?: string) => Promise<void>;
  assign: (lead: VLead, assigneeId: string) => Promise<void>;
  reassign: (lead: VLead, assigneeId?: string) => Promise<void>;
  moveToPending: (lead: VLead) => Promise<void>;
  refetch: () => void;
}

/* ---- helpers ------------------------------------------------------------ */

const initialsOf = (name: string) =>
  name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0]).join("").toUpperCase() || "?";

/** Resolve an assignee ref to a display name. be-crm echoes the raw principalId as the `name`,
 *  so prefer the local assignee list (principalId → real name); fall back to a backend name only
 *  when it isn't just the id, then to the id itself. Mirrors leads-api's resolveMemberName. */
function resolveAssigneeName(id: string, backendName: string, assignees: Assignee[]): string {
  const a = id ? assignees.find((x) => x.id === id) : undefined;
  if (a) return a.name;
  if (backendName && backendName !== id) return backendName;
  return backendName || id;
}
const prettyDate = (iso?: string | null): string => {
  if (!iso) return "—";
  const then = Date.parse(iso);
  if (Number.isNaN(then)) return iso;
  return new Date(then).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric" });
};
const prettyDateTime = (iso?: string | null): string | undefined => {
  if (!iso) return undefined;
  const then = Date.parse(iso);
  if (Number.isNaN(then)) return iso;
  return new Date(then).toLocaleString(undefined, { day: "numeric", month: "short", year: "numeric", hour: "numeric", minute: "2-digit" });
};

const EMPTY_COUNTS: Record<VStatus, number> = { verified: 0, in_progress: 0, assigned: 0, pending: 0, unverified: 0 };

const SUB_STATUS_DEFAULT: Record<VStatus, string> = {
  verified: "Verified", in_progress: "Verifying Identity", assigned: "Assigned",
  pending: "Pending Review", unverified: "Unverified",
};

/* ---- be-crm DTO types (defensive: backend is greenfield) ---------------- */

interface VerificationRowDTO {
  id: string; code: string; name: string; initials?: string;
  address?: string; phone?: string; source?: string;
  assignee?: { id?: string; initials?: string; name: string } | null;
  status?: VStatus; verification?: string; createdAt?: string;
}
interface ActivityDTO { text: string; occurredAt?: string; time?: string; actorLabel?: string; who?: string }
interface VerificationDTO extends VerificationRowDTO {
  email?: string; notes?: string; verifiedAt?: string; activities?: ActivityDTO[];
}

/* ---- DTO → UI VLead ----------------------------------------------------- */

function rowToVLead(d: VerificationRowDTO, assignees: Assignee[] = []): VLead {
  const status = (d.status ?? "pending") as VStatus;
  let assignee: VLead["assignee"] = null;
  if (d.assignee) {
    const id = d.assignee.id ?? "";
    const name = resolveAssigneeName(id, d.assignee.name, assignees);
    assignee = { id, initials: d.assignee.initials || initialsOf(name), name };
  }
  return {
    id: d.code || d.id,
    refId: d.id,
    initials: d.initials || (d.name ? d.name[0].toUpperCase() : "?"),
    name: d.name ?? "",
    address: d.address ?? "",
    phone: d.phone ?? "",
    source: d.source ?? "",
    assignee,
    status,
    verification: d.verification ?? SUB_STATUS_DEFAULT[status],
    created: prettyDate(d.createdAt),
  };
}

function detailToVLead(d: VerificationDTO, fallback: VLead, assignees: Assignee[] = []): VLead {
  const base = rowToVLead(d, assignees);
  return {
    ...base,
    email: d.email,
    notes: d.notes,
    verifiedAt: prettyDateTime(d.verifiedAt),
    createdAt: prettyDateTime(d.createdAt) ?? fallback.createdAt,
    activity: d.activities?.length
      ? d.activities.map<VActivity>((a) => ({
          text: a.text,
          time: prettyDateTime(a.occurredAt) ?? a.time ?? "",
          who: a.actorLabel ?? a.who ?? "System",
        }))
      : undefined,
  };
}

/* ======================================================================== */
/* Mock implementation (no Shell configured)                                */
/* ======================================================================== */

function useMockVerify(): VerifyData {
  const [leads, setLeads] = useState<VLead[]>(() => seedVLeads);

  const counts = useMemo(() => {
    const m = { ...EMPTY_COUNTS };
    for (const l of leads) m[l.status] += 1;
    return m;
  }, [leads]);

  const assignees = useMemo<Assignee[]>(
    () => V_ASSIGNEES.map((name) => ({ id: name, name, initials: initialsOf(name) })),
    [],
  );

  const patch = useCallback((lead: VLead, next: Partial<VLead>) => {
    setLeads((l) => l.map((x) => (x.id === lead.id ? { ...x, ...next } : x)));
  }, []);

  const getVerification = useCallback(async (lead: VLead) => lead, []);
  const verify = useCallback(async (lead: VLead) => {
    patch(lead, { status: "verified", verification: "Verified" });
  }, [patch]);
  const markUnverified = useCallback(async (lead: VLead) => {
    patch(lead, { status: "unverified", verification: "Unverified" });
  }, [patch]);
  const assign = useCallback(async (lead: VLead, assigneeId: string) => {
    const a = assignees.find((x) => x.id === assigneeId);
    patch(lead, { status: "assigned", verification: "Assigned", assignee: a ? { id: a.id, initials: a.initials, name: a.name } : lead.assignee });
  }, [assignees, patch]);
  const reassign = useCallback(async (lead: VLead, assigneeId?: string) => {
    const a = assigneeId ? assignees.find((x) => x.id === assigneeId) : undefined;
    patch(lead, { status: "in_progress", verification: "Verifying Identity", ...(a ? { assignee: { id: a.id, initials: a.initials, name: a.name } } : {}) });
  }, [assignees, patch]);
  const moveToPending = useCallback(async (lead: VLead) => {
    patch(lead, { status: "pending", verification: "Pending Review" });
  }, [patch]);

  return {
    live: false, loading: false, error: null,
    leads, total: leads.length, counts, assignees,
    getVerification, verify, markUnverified, assign, reassign, moveToPending, refetch: () => {},
  };
}

/* ======================================================================== */
/* Live implementation (be-crm data door)                                   */
/* ======================================================================== */

interface Page<T> { items: T[]; meta?: { total?: number } }
interface MemberDTO { id: string; principalId?: string; displayName?: string | null; firstName?: string | null; lastName?: string | null; jobTitle?: string | null }

function useLiveVerify(): VerifyData {
  const { sdk } = useAppShell();
  // A collection read may come back as { items, meta } OR as a bare array (§6) — tolerate both.
  const listQ = useQuery<Page<VerificationRowDTO> | VerificationRowDTO[]>("crm.leadVerification.search", { perPage: 100 });
  const statsQ = useQuery<Partial<Record<VStatus, number>>>("crm.leadVerification.stats", {});
  const membersQ = useQuery<{ items: MemberDTO[] }>("crm.team.member.search", { perPage: 100 });

  const refetch = useCallback(() => { listQ.refetch(); statsQ.refetch(); }, [listQ, statsQ]);

  // be-crm resolves the assignee ref by principalId, so send that (not the opaque member id).
  // This list also lets us resolve an assignee id → real name for display (be-crm echoes the id).
  const assignees = useMemo<Assignee[]>(() => (membersQ.data?.items ?? []).map((m) => {
    const name = m.displayName?.trim()
      || [m.firstName, m.lastName].filter(Boolean).join(" ").trim()
      || m.jobTitle?.trim()
      || `Member ${(m.principalId ?? m.id).slice(0, 6)}`;
    return { id: m.principalId ?? m.id, name, initials: initialsOf(name) };
  }), [membersQ.data]);

  const rawItems = useMemo(
    () => (Array.isArray(listQ.data) ? listQ.data : listQ.data?.items ?? []),
    [listQ.data],
  );
  const base = useMemo(() => rawItems.map((d) => rowToVLead(d, assignees)), [rawItems, assignees]);

  // Optimistic status overrides keyed by lead id. After a transition command we patch the row
  // locally so the Status column updates immediately, even if the server's search read lags
  // (read-after-write). An override always reflects the user's last action, so it stays correct
  // whether the refetch is stale or fresh — no reconciliation needed.
  const [overrides, setOverrides] = useState<Record<string, Partial<VLead>>>({});

  const leads = useMemo(
    () => base.map((l) => (overrides[l.id] ? { ...l, ...overrides[l.id] } : l)),
    [base, overrides],
  );

  // Run a transition command, then optimistically patch the row + refetch to reconcile.
  const mutate = useCallback(async (lead: VLead, patch: Partial<VLead>, action: string, variables: Record<string, unknown>) => {
    await sdk.command(action, variables);
    setOverrides((o) => ({ ...o, [lead.id]: { ...o[lead.id], ...patch } }));
    refetch();
  }, [sdk, refetch]);

  // Derive tile counts from the (optimistically patched) rows so they move in lockstep with the
  // table. With perPage 100 this covers the loaded queue; a transition reflects immediately.
  const counts = useMemo(() => {
    const m = { ...EMPTY_COUNTS };
    for (const l of leads) m[l.status] += 1;
    return m;
  }, [leads]);

  const getVerification = useCallback(async (lead: VLead): Promise<VLead> => {
    const dto = await sdk.query<VerificationDTO>("crm.leadVerification.get", { id: lead.refId ?? lead.id });
    return detailToVLead(dto, lead, assignees);
  }, [sdk, assignees]);

  return {
    live: true,
    loading: listQ.loading || statsQ.loading,
    error: (listQ.error ?? statsQ.error)?.message ?? null,
    leads,
    total: (Array.isArray(listQ.data) ? undefined : listQ.data?.meta?.total) ?? leads.length,
    counts,
    assignees,
    getVerification,
    verify: (lead, notes) => mutate(lead, { status: "verified", verification: "Verified" },
      "crm.leadVerification.verify", { id: lead.refId ?? lead.id, ...(notes ? { notes } : {}) }),
    markUnverified: (lead, reason) => mutate(lead, { status: "unverified", verification: "Unverified" },
      "crm.leadVerification.markUnverified", { id: lead.refId ?? lead.id, ...(reason ? { reason } : {}) }),
    assign: (lead, assigneeId) => mutate(lead, { status: "assigned", verification: "Assigned" },
      "crm.leadVerification.assign", { id: lead.refId ?? lead.id, assigneeId }),
    reassign: (lead, assigneeId) => mutate(lead, { status: "in_progress", verification: "Verifying Identity" },
      "crm.leadVerification.reassign", { id: lead.refId ?? lead.id, ...(assigneeId ? { assigneeId } : {}) }),
    moveToPending: (lead) => mutate(lead, { status: "pending", verification: "Pending Review" },
      "crm.leadVerification.moveToPending", { id: lead.refId ?? lead.id }),
    refetch,
  };
}

/* ---- public hook: pick the implementation at module-config time --------- */

const SHELL = isShellConfigured();

export function useVerifyData(): VerifyData {
  // SHELL is a build-time constant, so the same hook path runs every render (Rules-of-Hooks safe).
  return SHELL ? useLiveVerify() : useMockVerify();
}
