"use client";

import * as React from "react";

import { format } from "date-fns";
import { ChevronRight, Info, Layers3, Loader2, Repeat2, Search } from "lucide-react";

import { useRenderEffect } from "@/hooks/use-changed";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { formatNumberFixed } from "@/lib/format/numbers";
import { fetchCustomerPortalWidget } from "@/lib/customer-portal-api-client";

import { resolveCustomerTenant } from "@/app/customer/_lib/resolve-customer-tenant";
import { FRONTEND_ROUTES } from "@/config/frontend-routes";

import { fetchAssetAnalyticsOptionsClient } from "../../_lib/asset-analytics-options-api";
import { ShowExpiredToggle } from "./show-expired-toggle";
import type {
  DeliveredHoldingsEvent,
  DeliveredHoldingsPosition,
} from "../../../delivered-holdings-history/_lib/delivered-holdings-history-types";

function verdictToneClass(group: string | undefined): string {
  switch (group) {
    case "ok":
      return "border-emerald-600/30 bg-emerald-600/10 text-emerald-700 dark:text-emerald-400";
    case "watch":
      return "border-amber-600/30 bg-amber-600/10 text-amber-700 dark:text-amber-400";
    case "expired":
      return "border-border/60 bg-muted text-muted-foreground";
    default:
      return "border-border/60 bg-muted/60 text-foreground";
  }
}

function YesNo({ value }: { value: boolean | undefined }) {
  return (
    <span className={cn("font-medium", value ? "text-emerald-600 dark:text-emerald-400" : "text-muted-foreground")}>
      {value ? "Yes" : "No"}
    </span>
  );
}

function EventRow({ event }: { event: DeliveredHoldingsEvent }) {
  const isCash = event.event_kind === "interest_dividend";
  const cashOut = isCash && (event.amount ?? 0) < 0;
  // Ledger amounts are in the booking's own currency (not converted), shown bare like the
  // Delivered Holdings History report.
  const value = isCash
    ? event.amount != null
      ? formatNumberFixed(event.amount, 2)
      : "—"
    : event.delivered_qty != null
      ? `${formatNumberFixed(event.delivered_qty, 2)} sh`
      : "—";

  return (
    <tr className="border-t border-border/60">
      <td className="px-3 py-2 whitespace-nowrap text-muted-foreground">{event.event_date || "—"}</td>
      <td className="px-3 py-2">
        <span
          className={cn(
            "inline-flex rounded-full px-2 py-0.5 font-medium text-xs",
            isCash && !cashOut
              ? "bg-emerald-600/10 text-emerald-700 dark:text-emerald-400"
              : "bg-muted text-muted-foreground",
          )}
        >
          {isCash ? (cashOut ? "Cash" : "Cash received") : event.received_type || "Option"}
        </span>
      </td>
      <td className="px-3 py-2 text-muted-foreground">{event.underlying_isin || event.ticker || "—"}</td>
      <td className="px-3 py-2 text-right tabular-nums">{value}</td>
      <td className="px-3 py-2 text-muted-foreground">{event.delivered_label || "—"}</td>
    </tr>
  );
}

function OptionPositionCard({
  position,
  events,
}: {
  position: DeliveredHoldingsPosition;
  events: DeliveredHoldingsEvent[];
}) {
  return (
    <div className="overflow-hidden rounded-2xl border border-border/60 bg-card shadow-sm">
      <div className="space-y-2 border-b border-border/60 bg-gradient-to-b from-muted/40 to-transparent px-4 py-3.5">
        <div className="flex flex-wrap items-center justify-between gap-2">
          <div className="flex items-center gap-2">
            <span className="font-mono font-semibold text-sm">{position.isin || position.ticker || "—"}</span>
            {position.name ? <span className="text-muted-foreground text-xs">{position.name}</span> : null}
          </div>
          <span className={cn("rounded-full border px-2.5 py-0.5 font-semibold text-xs", verdictToneClass(position.verdict_group))}>
            {position.verdict_label || "—"}
          </span>
        </div>
        <div className="flex flex-wrap items-center gap-2">
          <span className="rounded-full bg-muted px-2.5 py-0.5 font-medium text-xs text-muted-foreground">{position.product_label || "Option"}</span>
        </div>
        <div className="grid grid-cols-2 gap-x-4 gap-y-2 pt-1 sm:grid-cols-3 lg:grid-cols-5">
          <div className="min-w-0">
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">RM</p>
            <p className="text-sm">{position.rm_label || "—"}</p>
          </div>
          <div className="min-w-0">
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Client</p>
            <p className="break-words text-sm leading-snug" title={position.client_label || undefined}>
              {position.client_label || "—"}
            </p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Delivery</p>
            <p className="text-sm">
              <YesNo value={position.delivery} />
            </p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Interest</p>
            <p className="text-sm">
              <YesNo value={position.interest_received} />
              {position.interest_received && position.interest_amount != null ? (
                <span
                  className={cn(
                    "ml-1 tabular-nums",
                    position.interest_amount < 0
                      ? "text-red-600 dark:text-red-400"
                      : "text-muted-foreground",
                  )}
                >
                  {formatNumberFixed(position.interest_amount, 2)}
                </span>
              ) : null}
            </p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Shares</p>
            <p className="text-sm tabular-nums">
              {formatNumberFixed(position.delivered_qty ?? position.total_delivered_qty ?? 0, 2)}
            </p>
          </div>
        </div>
      </div>

      {events.length > 0 ? (
        <div className="px-4 py-3">
          <p className="mb-2 font-medium text-[11px] text-muted-foreground uppercase tracking-wider">
            Cash &amp; Delivery Events · {events.length} row{events.length === 1 ? "" : "s"}
          </p>
          <table className="w-full border-collapse text-sm">
            <thead>
              <tr className="bg-muted/40 text-left text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
                <th className="px-3 py-2">Date</th>
                <th className="px-3 py-2">Event</th>
                <th className="px-3 py-2">Underlying</th>
                <th className="px-3 py-2 text-right">Value</th>
                <th className="px-3 py-2">Detail</th>
              </tr>
            </thead>
            <tbody>
              {events.map((event, index) => (
                <EventRow key={event.event_id ?? `${position.position_key}-${index}`} event={event} />
              ))}
            </tbody>
          </table>
        </div>
      ) : (
        <p className="px-4 py-3 text-muted-foreground text-xs">No cash or delivery events recorded yet.</p>
      )}
    </div>
  );
}

export const OPTION_LEDGER_NOTE =
  "Outcome / Delivery / Interest reflect real ledger facts (option settlement + Transactions interest rows) — the same scorecard the Delivered Holdings History report uses.";

function useAssetAnalyticsOptions(tickerId: string, enabled = true) {
  const [loading, setLoading] = React.useState(enabled);
  const [error, setError] = React.useState<string | null>(null);
  const [positions, setPositions] = React.useState<DeliveredHoldingsPosition[]>([]);
  const [events, setEvents] = React.useState<DeliveredHoldingsEvent[]>([]);

  useRenderEffect([tickerId, enabled], () => {
    setLoading(enabled);
    setError(null);
  });

  React.useEffect(() => {
    if (!enabled) return;
    const controller = new AbortController();

    fetchAssetAnalyticsOptionsClient(tickerId, controller.signal)
      .then((data) => {
        setPositions(data.positions);
        setEvents(data.events);
      })
      .catch((err: unknown) => {
        if (controller.signal.aborted) return;
        setError(err instanceof Error ? err.message : "Could not load options.");
      })
      .finally(() => {
        if (!controller.signal.aborted) setLoading(false);
      });

    return () => controller.abort();
  }, [tickerId, enabled]);

  return { loading, error, positions, events };
}

type OptionBrief = {
  label: string;
  lots: number;
  delivery: boolean;
  interest: number;
  hasInterest: boolean;
  shares: number;
  outcome: string;
};

function briefOptions(positions: DeliveredHoldingsPosition[]): OptionBrief[] {
  const groups = new Map<string, OptionBrief>();
  for (const position of positions) {
    const label = position.product_label || "Option";
    let group = groups.get(label);
    if (!group) {
      group = {
        label,
        lots: 0,
        delivery: false,
        interest: 0,
        hasInterest: false,
        shares: 0,
        outcome: position.verdict_label || "",
      };
      groups.set(label, group);
    }
    group.lots += 1;
    group.delivery = group.delivery || Boolean(position.delivery);
    group.hasInterest = group.hasInterest || Boolean(position.interest_received);
    group.interest += Number(position.interest_amount ?? 0) || 0;
    group.shares += Number(position.delivered_qty ?? position.total_delivered_qty ?? 0) || 0;
    if (group.outcome && position.verdict_label && group.outcome !== position.verdict_label) {
      group.outcome = "";
    }
  }
  return Array.from(groups.values());
}

type StructureGroup = {
  key: string;
  isin: string;
  name: string;
  positions: DeliveredHoldingsPosition[];
  products: string[];
  clientCount: number;
  watchCount: number;
  okCount: number;
  expiredCount: number;
  potentialCount: number;
  liveCount: number;
  deliveryCount: number;
  earlyCount: number;
  interestCount: number;
  totalShares: number;
  totalInterest: number;
};

function structureVerdictClass(group: string | undefined): string {
  if (group === "watch") return "bg-rose-600/10 text-rose-700 ring-1 ring-inset ring-rose-600/20 dark:text-rose-300";
  if (group === "ok") return "bg-emerald-600/10 text-emerald-700 ring-1 ring-inset ring-emerald-600/20 dark:text-emerald-300";
  if (group === "expired") return "bg-indigo-600/10 text-indigo-700 ring-1 ring-inset ring-indigo-600/20 dark:text-indigo-300";
  if (group === "potential") return "bg-amber-600/10 text-amber-800 ring-1 ring-inset ring-amber-600/20 dark:text-amber-300";
  if (group === "live") return "bg-foreground/[0.06] text-foreground/80 ring-1 ring-inset ring-foreground/10";
  return "bg-muted text-muted-foreground ring-1 ring-inset ring-border/60";
}

function structureVerdictLabel(group: string | undefined): string {
  if (group === "watch") return "Needs review";
  if (group === "ok") return "Received";
  if (group === "expired") return "Expired";
  if (group === "potential") return "Potential";
  if (group === "live") return "Live";
  return group || "—";
}

function structureProductClass(product: string): string {
  if (/^accu$/i.test(product)) return "bg-emerald-600/10 text-emerald-700 ring-1 ring-inset ring-emerald-600/20 dark:text-emerald-300";
  if (/^decu$/i.test(product)) return "bg-rose-600/10 text-rose-700 ring-1 ring-inset ring-rose-600/20 dark:text-rose-300";
  if (/structure/i.test(product)) return "bg-violet-600/10 text-violet-700 ring-1 ring-inset ring-violet-600/20 dark:text-violet-300";
  return "bg-muted text-muted-foreground ring-1 ring-inset ring-border/60";
}

function parseClientLabel(raw: string): { name: string; handle: string } {
  const value = raw.trim();
  if (!value) return { name: "—", handle: "" };
  const match = value.match(/^(.*?)\s*\[([^\]]+)\]\s*$/);
  if (!match) return { name: value, handle: "" };
  return { name: match[1]?.trim() || value, handle: match[2]?.trim() || "" };
}

/**
 * `client_label` is a display string ("Name [subdomain]") and the backend only appends the
 * bracketed handle when the account has a subdomain set — accounts without one render as the
 * bare name, so two different clients can share an identical label. `user_id` is the actual
 * account primary key and is always distinct, so dedupe on that first.
 */
function clientIdentityKey(position: DeliveredHoldingsPosition): string {
  if (position.user_id !== null && position.user_id !== undefined) return `uid:${position.user_id}`;
  const label = String(position.client_label || position.user_label || "").trim();
  return label ? `label:${label}` : "";
}

function localToday(): string {
  const now = new Date();
  const month = String(now.getMonth() + 1).padStart(2, "0");
  const day = String(now.getDate()).padStart(2, "0");
  return `${now.getFullYear()}-${month}-${day}`;
}

type PanelStatus = "live" | "potential" | "expired";

const PANEL_FAMILIES = new Set(["structure", "accu", "decu"]);

function isoDay(value: unknown): string {
  const day = String(value ?? "").slice(0, 10);
  return /^\d{4}-\d{2}-\d{2}$/.test(day) ? day : "";
}

function isContractFamily(position: DeliveredHoldingsPosition): boolean {
  const family = String(position.product_family || "").toLowerCase();
  return family === "accu" || family === "decu";
}

/**
 * Live / Potential / Expired for a structure or accumulator on this underlying; null for other
 * families. A structure is expired once it settled (the backend's "expired" verdict, or a
 * delivery still under review) or passed its FVD — verdict_group is authoritative and must not
 * be overridden back to live just because the calendar FVD hasn't passed. An Accu / Decu is
 * expired once its contract matures. Mirrors the positions sheet.
 */
function panelStatus(position: DeliveredHoldingsPosition): PanelStatus | null {
  const family = String(position.product_family || "").toLowerCase();
  if (!PANEL_FAMILIES.has(family)) return null;
  const today = localToday();
  const lifecycle = String(position.lifecycle_status || "").toLowerCase();
  if (isContractFamily(position)) {
    const maturity = isoDay(position.maturity);
    if (maturity && maturity < today) return "expired";
    return lifecycle === "potential" ? "potential" : "live";
  }
  const verdict = String(position.verdict_group || "").toLowerCase();
  const fvd = isoDay(position.fvd);
  if (verdict === "expired" || verdict === "watch" || (fvd && fvd < today)) return "expired";
  return verdict === "potential" || lifecycle === "potential" ? "potential" : "live";
}

function groupStructurePositions(positions: DeliveredHoldingsPosition[]): StructureGroup[] {
  const map = new Map<string, StructureGroup>();
  const productSets = new Map<string, Set<string>>();
  const clientSets = new Map<string, Set<string>>();

  for (const position of positions) {
    // Accu / Decu group by contract (their `isin` is the underlying's display name).
    const contract = isContractFamily(position);
    const isin = String((contract ? position.contract_id : position.isin) || "").trim().toUpperCase() || "—";
    const key = isin !== "—" ? isin : `pk:${position.position_key}`;
    let group = map.get(key);
    if (!group) {
      const name = String((contract ? position.contract_name : position.structure_name) || position.name || "").trim();
      group = {
        key,
        isin,
        name: name.toUpperCase() === isin ? "" : name,
        positions: [],
        products: [],
        clientCount: 0,
        watchCount: 0,
        okCount: 0,
        expiredCount: 0,
        potentialCount: 0,
        liveCount: 0,
        deliveryCount: 0,
        earlyCount: 0,
        interestCount: 0,
        totalShares: 0,
        totalInterest: 0,
      };
      map.set(key, group);
      productSets.set(key, new Set());
      clientSets.set(key, new Set());
    }
    if (!group.name) {
      const name = String((contract ? position.contract_name : position.structure_name) || position.name || "").trim();
      if (name && name.toUpperCase() !== isin) group.name = name;
    }
    group.positions.push(position);
    if (position.verdict_group === "watch") group.watchCount += 1;
    else if (position.verdict_group === "ok") group.okCount += 1;
    else if (position.verdict_group === "expired") group.expiredCount += 1;
    else if (position.verdict_group === "potential") group.potentialCount += 1;
    else if (position.verdict_group === "live") group.liveCount += 1;
    if (position.delivery) group.deliveryCount += 1;
    if (position.early_delivery) group.earlyCount += 1;
    if (position.interest_received) group.interestCount += 1;
    group.totalShares += Number(position.delivered_qty ?? position.total_delivered_qty ?? 0) || 0;
    group.totalInterest += Number(position.interest_amount ?? position.total_interest_dividend ?? 0) || 0;
    const product = String(position.product_label || position.product_family || "").trim();
    if (product && !/^interest/i.test(product)) productSets.get(key)?.add(product);
    const clientKey = clientIdentityKey(position);
    if (clientKey) clientSets.get(key)?.add(clientKey);
  }

  const groups = Array.from(map.values());
  for (const group of groups) {
    group.products = Array.from(productSets.get(group.key) ?? []);
    group.clientCount = (clientSets.get(group.key) ?? new Set()).size;
  }
  groups.sort((a, b) => (a.name || a.isin).localeCompare(b.name || b.isin));
  return groups;
}

function formatEventDate(value: string): string {
  const parsed = new Date(`${value}T00:00:00`);
  if (Number.isNaN(parsed.getTime())) return value || "—";
  return format(parsed, "dd MMM yyyy");
}

function StructureEventDetail({
  position,
  events,
  underlyings,
}: {
  position: DeliveredHoldingsPosition;
  events: DeliveredHoldingsEvent[];
  underlyings: string[];
}) {
  const cashEvents = events.filter(
    (event) =>
      event.event_kind === "interest_dividend" ||
      /interest|dividend/i.test(String(event.received_type ?? "")),
  );
  const ledgerCash = cashEvents.reduce((sum, event) => sum + (Number(event.amount) || 0), 0);
  const prior = Number(position.coupon_prior ?? 0) || 0;
  const priorEst = Number(position.interest_prior_estimate ?? 0) || 0;
  const expected = Number(position.coupon_expected ?? 0) || 0;
  const matched = Number(position.coupon_received ?? 0) || 0;
  const showGap = prior > 0 && expected > 0;

  return (
    <div className="border-t border-border/60 bg-muted/10">
      {underlyings.length > 0 ? (
        <p className="px-3.5 pt-2 text-muted-foreground text-xs">{underlyings.join(" · ")}</p>
      ) : null}
      <div className="mx-3.5 my-2 overflow-hidden rounded-lg border border-border/60 bg-card">
        <div className="flex items-center gap-2 border-b border-border/60 bg-muted/40 px-3 py-1.5">
          <span className="font-semibold text-[10px] text-muted-foreground uppercase tracking-wide">Cash & delivery events</span>
          <span className="text-[10px] text-muted-foreground tabular-nums">
            {events.length} row{events.length === 1 ? "" : "s"}
          </span>
        </div>
        {showGap ? (
          <p className="border-b border-border/60 bg-muted/30 px-3 py-1.5 text-[11px] text-muted-foreground" title={position.interest_gap_note}>
            Master {matched}/{expected}
            {position.coupon_complete ? " ✓" : ""} · cash {formatNumberFixed(ledgerCash || position.interest_amount || 0, 2)} ({cashEvents.length}) · prior ~
            {formatNumberFixed(priorEst, 2)} ({prior} schedule-only — no cash rows)
          </p>
        ) : null}
        {events.length === 0 ? (
          <p className="px-3 py-2 text-muted-foreground text-xs">No ledger cash or delivery events listed.</p>
        ) : (
          <div className="overflow-x-auto">
            <table className="w-full min-w-[40rem] border-collapse text-xs">
              <thead>
                <tr className="bg-muted/20 text-left text-muted-foreground">
                  <th className="px-3 py-1.5 font-medium">Date</th>
                  <th className="px-3 py-1.5 font-medium">Event</th>
                  <th className="px-3 py-1.5 font-medium">Underlying</th>
                  <th className="px-3 py-1.5 text-right font-medium">Value</th>
                  <th className="px-3 py-1.5 font-medium">Detail</th>
                </tr>
              </thead>
              <tbody>
                {events.map((event, index) => {
                  const isCash =
                    event.event_kind === "interest_dividend" ||
                    /interest|dividend/i.test(String(event.received_type ?? ""));
                  const label = isCash ? "Cash received" : "Shares delivered";
                  const value = isCash
                    ? event.amount != null
                      ? formatNumberFixed(event.amount, 2)
                      : "—"
                    : event.delivered_qty != null
                      ? `${formatNumberFixed(event.delivered_qty, 2)} sh`
                      : "—";
                  const undName = String(event.underlying_name || "").trim();
                  const undIsin = String(event.underlying_isin || event.ticker || "").trim();
                  return (
                    <tr key={event.event_id ?? `${index}`} className="border-t border-border/60">
                      <td className="whitespace-nowrap px-3 py-1.5 tabular-nums">{formatEventDate(event.event_date)}</td>
                      <td className="px-3 py-1.5">
                        <span
                          className={cn(
                            "inline-flex rounded-full px-1.5 py-0.5 font-medium text-[10px]",
                            isCash ? "bg-emerald-600/15 text-emerald-800 dark:text-emerald-300" : "bg-muted text-foreground/80",
                          )}
                        >
                          {label}
                        </span>
                      </td>
                      <td className="px-3 py-1.5">
                        {isCash ? (
                          <span className="text-muted-foreground">—</span>
                        ) : (
                          <div>
                            <div>{undName || undIsin || "—"}</div>
                            {undIsin && undName && undName.toUpperCase() !== undIsin.toUpperCase() ? (
                              <div className="font-mono text-[10px] text-muted-foreground">{undIsin}</div>
                            ) : null}
                          </div>
                        )}
                      </td>
                      <td className="px-3 py-1.5 text-right font-medium tabular-nums">{value}</td>
                      <td className="px-3 py-1.5 text-muted-foreground">{event.delivered_label || "—"}</td>
                    </tr>
                  );
                })}
                {showGap && priorEst !== 0 ? (
                  <tr className="border-t border-border/60 bg-muted/30">
                    <td className="px-3 py-1.5 text-muted-foreground">Prior</td>
                    <td className="px-3 py-1.5">Prior interest (estimate)</td>
                    <td className="px-3 py-1.5 text-muted-foreground">—</td>
                    <td className="px-3 py-1.5 text-right tabular-nums">~{formatNumberFixed(priorEst, 2)}</td>
                    <td className="px-3 py-1.5 text-muted-foreground">
                      {prior} Master period{prior === 1 ? "" : "s"} — schedule ✓ only, no cash line
                    </td>
                  </tr>
                ) : null}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </div>
  );
}

/** Delivered Holdings families behind each panel: structures, or accumulators / decumulators. */
export type ScorecardFamily = "structure" | "accu";

function useStructureUnderlyings(tickerId: string, enabled: boolean, product: ScorecardFamily) {
  const [loading, setLoading] = React.useState(true);
  const [positions, setPositions] = React.useState<DeliveredHoldingsPosition[]>([]);
  const [events, setEvents] = React.useState<DeliveredHoldingsEvent[]>([]);

  useRenderEffect([tickerId, enabled, product], () => {
    setLoading(enabled);
    setPositions([]);
    setEvents([]);
  });

  React.useEffect(() => {
    if (!enabled) return;
    const controller = new AbortController();
    const tenant = resolveCustomerTenant();
    // product "accu" covers decumulators too.
    void fetchCustomerPortalWidget<{ positions?: DeliveredHoldingsPosition[]; events?: DeliveredHoldingsEvent[] }>({
      url: FRONTEND_ROUTES.customerPortal.deliveredHoldingsHistory.list(tenant),
      tenant,
      searchParams: { product, isin: tickerId },
      loadError: "Could not load holdings.",
      signal: controller.signal,
    }).then(({ data }) => {
      if (controller.signal.aborted) return;
      // A failed load shows the panel's empty state, as before.
      setPositions(Array.isArray(data?.positions) ? data.positions : []);
      setEvents(Array.isArray(data?.events) ? data.events : []);
      setLoading(false);
    });

    return () => controller.abort();
  }, [tickerId, enabled, product]);

  return { loading, positions, events };
}

function StructureInterestCell({ position }: { position: DeliveredHoldingsPosition }) {
  const hasInterest = Boolean(position.interest_received) || Number(position.interest_amount ?? 0) > 0;
  const expected = Number(position.coupon_expected ?? 0) || 0;
  const matched = Number(position.coupon_received ?? 0) || 0;
  const prior = Number(position.coupon_prior ?? 0) || 0;
  const priorEst = Number(position.interest_prior_estimate ?? 0) || 0;
  const amount = position.interest_amount ? formatNumberFixed(position.interest_amount, 2) : "";

  if (!hasInterest && expected <= 0) return <span>No</span>;

  return (
    <span className="inline-flex max-w-[14rem] flex-col gap-0.5 leading-tight">
      <span className="whitespace-nowrap">
        {hasInterest ? "Yes" : "No"}
        {expected > 0 ? (
          <span
            className={cn(
              "ml-1 tabular-nums",
              position.coupon_complete ? "text-emerald-800 dark:text-emerald-300" : "text-amber-800 dark:text-amber-300",
            )}
          >
            {matched}/{expected}
            {position.coupon_complete ? " ✓" : ""}
          </span>
        ) : null}
        {amount ? <span className="ml-1 text-muted-foreground tabular-nums">{amount}</span> : null}
      </span>
      {prior > 0 ? (
        <span className="text-[10px] text-amber-800 dark:text-amber-300">
          +prior ~{priorEst ? formatNumberFixed(priorEst, 2) : "—"} ({prior} sch.)
        </span>
      ) : null}
    </span>
  );
}

function StructureHoldingCells({ position, family }: { position: DeliveredHoldingsPosition; family: ScorecardFamily }) {
  const client = parseClientLabel(String(position.client_label || position.user_label || ""));
  const product = String(position.product_label || position.product_family || "").trim();
  const productType = String(position.product_type || "").trim();
  const underlyings =
    position.underlyings && position.underlyings.length > 0
      ? position.underlyings
      : [];

  return (
    <>
      <td className="max-w-[9rem] truncate px-3 py-2 align-top" title={position.rm_label || undefined}>
        {position.rm_label || "—"}
      </td>
      <td className="max-w-[12rem] px-3 py-2 align-top">
        <div className="truncate leading-snug" title={client.name}>
          {client.name}
        </div>
        {client.handle ? (
          <span className="mt-0.5 inline-block max-w-full truncate rounded-full bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
            {client.handle}
          </span>
        ) : null}
      </td>
      <td className="min-w-[10rem] px-3 py-2 align-top">
        <div className="flex flex-wrap items-center gap-1">
          <span className={cn("rounded-full px-2 py-0.5 font-semibold text-[10px]", structureProductClass(product))}>
            {product || "—"}
          </span>
          {productType && productType.toLowerCase() !== product.toLowerCase() ? (
            <span className="rounded-full border border-border/60 px-2 py-0.5 font-medium text-[10px] text-muted-foreground">{productType}</span>
          ) : null}
        </div>
        {underlyings.length > 0 ? (
          <div className="mt-1 space-y-0.5 text-[11px] text-muted-foreground leading-snug">
            {underlyings.map((leg, index) => (
              <div
                key={`${leg.isin || leg.name || index}`}
                className={cn(leg.delivered && "font-medium text-emerald-800 dark:text-emerald-300")}
              >
                {leg.name || leg.isin || leg.ticker || "—"}
                {leg.delivered ? " ✓" : ""}
              </div>
            ))}
          </div>
        ) : null}
      </td>
      <td className="px-3 py-2 align-top">
        <span
          className={cn(
            "inline-flex rounded-full px-2 py-0.5 font-semibold text-[10px]",
            structureVerdictClass(position.verdict_group),
          )}
          title={position.verdict_note}
        >
          {position.verdict_label || position.verdict_group || "—"}
        </span>
      </td>
      <td className="px-3 py-2 align-top">{position.delivery ? "Yes" : "No"}</td>
      <td className="px-3 py-2 align-top">
        {family === "accu" ? (
          <span className="whitespace-nowrap tabular-nums">
            {isoDay(position.maturity) ? formatEventDate(isoDay(position.maturity)) : "—"}
          </span>
        ) : (
          <StructureInterestCell position={position} />
        )}
      </td>
      <td className="px-3 py-2 text-right align-top tabular-nums">
        {formatNumberFixed(position.delivered_qty ?? position.total_delivered_qty ?? 0, 2)}
      </td>
    </>
  );
}

const SCORECARD_COPY: Record<
  ScorecardFamily,
  {
    title: string;
    unit: string;
    plural: string;
    summaryLabel: string;
    guide: string;
    icon: React.ComponentType<{ className?: string }>;
    iconClass: string;
  }
> = {
  structure: {
    title: "Structures",
    unit: "product",
    plural: "structures",
    summaryLabel: "Interest",
    guide:
      "Live = interest only (pre-FVD). Potential = awaiting FVD. Expired = past FVD — deliver if tagged, else money back. Needs review = delivery or early redemption without interest.",
    icon: Layers3,
    iconClass: "border-violet-600/15 bg-violet-600/10 text-violet-700 dark:text-violet-300",
  },
  accu: {
    title: "Accumulators & decumulators",
    unit: "contract",
    plural: "accumulators or decumulators",
    summaryLabel: "Delivered",
    guide:
      "Live = accumulating until maturity. Potential = on the Potential list. Expired = past maturity. Received = settled with shares delivered. Accu buys the underlying at the strike; Decu sells it.",
    icon: Repeat2,
    iconClass: "border-emerald-600/15 bg-emerald-600/10 text-emerald-700 dark:text-emerald-300",
  },
};

/** Placeholder rows shaped like the structure list while the scorecard loads. */
function StructureRowsSkeleton({ rows = 4 }: { rows?: number }) {
  return (
    <div className="divide-y divide-border/60" aria-busy="true" aria-label="Loading structures">
      {Array.from({ length: rows }, (_, index) => (
        <div key={index} className="flex items-center gap-3 border-l-2 border-l-muted px-4 py-3">
          <Skeleton className="size-6 shrink-0 rounded-full" />
          <div className="min-w-0 flex-1 space-y-2">
            <Skeleton className={cn("h-4", index % 2 === 0 ? "w-2/3" : "w-1/2")} />
            <div className="flex items-center gap-1.5">
              <Skeleton className="h-4 w-24" />
              <Skeleton className="h-3 w-20" />
            </div>
          </div>
          <div className="flex shrink-0 flex-col items-end gap-1.5">
            <Skeleton className="h-2.5 w-12" />
            <Skeleton className="h-4 w-16" />
            <Skeleton className="h-4 w-10 rounded-full" />
          </div>
        </div>
      ))}
    </div>
  );
}

function StructureScorecard({
  loading,
  tickerId,
  positions,
  events,
  includeExpired,
  onIncludeExpiredChange,
  family,
}: {
  loading: boolean;
  tickerId: string;
  positions: DeliveredHoldingsPosition[];
  events: DeliveredHoldingsEvent[];
  /** List expired holdings too (tagged Expired). */
  includeExpired: boolean;
  onIncludeExpiredChange?: (next: boolean) => void;
  family: ScorecardFamily;
}) {
  const copy = SCORECARD_COPY[family];
  const scoped = React.useMemo(() => {
    const needle = tickerId.trim().toUpperCase();
    if (!needle) return positions;
    const exact = positions.filter((position) => String(position.isin || "").trim().toUpperCase() === needle);
    return exact.length > 0 ? exact : positions;
  }, [positions, tickerId]);
  // Badges follow the Live / Potential / Expired status (verdict_label keeps the outcome detail).
  const listedPositions = React.useMemo(
    () =>
      scoped.flatMap((position) => {
        const status = panelStatus(position);
        if (status === null || (status === "expired" && !includeExpired)) return [];
        if (isContractFamily(position) !== (family === "accu")) return [];
        const label = status === "expired" && isContractFamily(position) ? "Expired" : position.verdict_label;
        return [{ ...position, verdict_group: status, verdict_label: label }];
      }),
    [scoped, includeExpired, family],
  );
  const groups = React.useMemo(() => groupStructurePositions(listedPositions), [listedPositions]);
  const expiredCount = listedPositions.filter((position) => position.verdict_group === "expired").length;
  const eventsByPosition = React.useMemo(() => {
    const map = new Map<string, DeliveredHoldingsEvent[]>();
    for (const event of events) {
      const key = event.position_key ?? "";
      const list = map.get(key) ?? [];
      list.push(event);
      map.set(key, list);
    }
    return map;
  }, [events]);
  const [openGroups, setOpenGroups] = React.useState<Set<string>>(() => new Set());
  const [expandedRow, setExpandedRow] = React.useState<string | null>(null);
  const [query, setQuery] = React.useState("");

  useRenderEffect([tickerId], () => {
    setOpenGroups(new Set());
    setExpandedRow(null);
    setQuery("");
  });

  const clientCount = new Set(listedPositions.map(clientIdentityKey).filter(Boolean)).size;
  const needle = query.trim().toLowerCase();
  const visibleGroups = groups.filter((group) => {
    if (!needle) return true;
    return `${group.name} ${group.isin}`.toLowerCase().includes(needle);
  });

  return (
    <div className="overflow-hidden rounded-2xl border border-border/60 bg-card shadow-sm">
      <div className="flex flex-col gap-3 border-b border-border/60 bg-gradient-to-b from-muted/40 to-transparent px-4 py-3.5 sm:flex-row sm:items-center sm:justify-between">
        <div className="flex min-w-0 items-center gap-3">
          <span className={cn("flex size-9 shrink-0 items-center justify-center rounded-xl border", copy.iconClass)}>
            <copy.icon className="size-4" />
          </span>
          <div className="min-w-0">
            <p className="font-semibold text-sm">{copy.title}</p>
            <p className="text-muted-foreground text-xs">
              {loading
                ? "Loading holdings…"
                : `${groups.length} ${copy.unit}${groups.length === 1 ? "" : "s"} · ${clientCount} client${clientCount === 1 ? "" : "s"}${
                    expiredCount > 0 ? ` · ${expiredCount} expired` : ""
                  }`}
            </p>
          </div>
        </div>
        {onIncludeExpiredChange ? <ShowExpiredToggle checked={includeExpired} onChange={onIncludeExpiredChange} /> : null}
      </div>
      {groups.length > 4 ? (
        <div className="relative border-b border-border/60 bg-muted/10">
          <Search className="pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-muted-foreground" />
          <input
            value={query}
            onChange={(event) => setQuery(event.target.value)}
            placeholder="Search name or ISIN"
            className="h-10 w-full bg-transparent pr-4 pl-10 text-sm outline-none placeholder:text-muted-foreground"
          />
        </div>
      ) : null}
      <details className="group border-b border-border/60 px-4 py-2.5 text-[11px] text-muted-foreground open:bg-muted/10">
        <summary className="flex cursor-pointer select-none items-center gap-1.5 font-medium text-foreground/80">
          <Info className="size-3.5 shrink-0 text-muted-foreground" />
          Outcome guide
          <ChevronRight className="ml-auto size-3 shrink-0 text-muted-foreground transition-transform group-open:rotate-90" />
        </summary>
        <p className="pt-1.5 pl-5 leading-relaxed">{copy.guide}</p>
      </details>
      {loading ? <StructureRowsSkeleton /> : null}
      {!loading && groups.length === 0 ? (
        <p className="px-3 py-6 text-center text-muted-foreground text-xs">
          {includeExpired ? `No ${copy.plural} for this underlying.` : `No live or potential ${copy.plural} for this underlying.`}
        </p>
      ) : null}
      {!loading && groups.length > 0 && visibleGroups.length === 0 ? (
        <p className="px-3 py-6 text-center text-muted-foreground text-xs">No {copy.plural} match this filter.</p>
      ) : null}
      <div className="divide-y divide-border/60">
      {visibleGroups.map((group) => {
        const open = openGroups.has(group.key);
        const title = group.name || group.isin;
        const ranked = (
          [
            ["live", group.liveCount],
            ["expired", group.expiredCount],
            ["potential", group.potentialCount],
            ["watch", group.watchCount],
            ["ok", group.okCount],
          ] as const
        ).filter(([, count]) => count > 0);
        const primary = ranked[0]?.[0];
        const accent =
          primary === "live" || primary === "ok"
            ? "border-l-emerald-500"
            : primary === "expired"
              ? "border-l-indigo-400"
              : primary === "potential"
                ? "border-l-amber-500"
                : primary === "watch"
                  ? "border-l-rose-500"
                  : "border-l-transparent";
        const maturity = family === "accu" ? isoDay(group.positions[0]?.maturity) : "";
        const facts = [
          group.deliveryCount > 0 ? `Delivered ${group.deliveryCount}/${group.positions.length}` : "",
          group.earlyCount > 0 ? `Early ${group.earlyCount}` : "",
          family === "accu" ? (maturity ? `Matures ${formatEventDate(maturity)}` : "") : group.totalShares > 0 ? `${formatNumberFixed(group.totalShares, 2)} sh` : "",
        ].filter(Boolean);
        const kinds = family === "accu" ? group.products.filter((product) => /^(accu|decu)$/i.test(product)) : [];
        return (
          <div key={group.key} className={cn("border-l-2 transition-colors", accent, open && "bg-muted/20")}>
            <button
              type="button"
              className="group flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/40"
              onClick={() =>
                setOpenGroups((current) => {
                  const next = new Set(current);
                  if (next.has(group.key)) next.delete(group.key);
                  else next.add(group.key);
                  return next;
                })
              }
            >
              <span
                className={cn(
                  "flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-all",
                  open ? "rotate-90 bg-foreground/10 text-foreground" : "group-hover:bg-muted",
                )}
              >
                <ChevronRight className="size-3.5" />
              </span>
              <div className="min-w-0 flex-1">
                <p className="truncate font-semibold text-sm" title={title}>
                  {title}
                </p>
                <p className="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-[11px] text-muted-foreground">
                  {kinds.map((kind) => (
                    <span key={kind} className={cn("rounded-full px-1.5 py-0.5 font-semibold text-[10px]", structureProductClass(kind))}>
                      {kind}
                    </span>
                  ))}
                  {group.isin !== "—" ? (
                    <span className="rounded-md bg-muted px-1.5 py-0.5 font-mono text-[10px] text-foreground/70">{group.isin}</span>
                  ) : null}
                  <span>
                    {group.positions.length} holding{group.positions.length === 1 ? "" : "s"}
                    {group.clientCount > 0 ? ` · ${group.clientCount} client${group.clientCount === 1 ? "" : "s"}` : ""}
                  </span>
                  {facts.length > 0 ? <span className="text-muted-foreground/80">· {facts.join(" · ")}</span> : null}
                </p>
              </div>
              <div className="flex shrink-0 flex-col items-end gap-1.5">
                <span className="flex flex-col items-end leading-tight">
                  <span className="text-[10px] text-muted-foreground uppercase tracking-wider">{copy.summaryLabel}</span>
                  <span className="font-semibold text-sm tabular-nums">
                    {family === "accu"
                      ? group.totalShares !== 0
                        ? `${formatNumberFixed(group.totalShares, 2)} sh`
                        : "—"
                      : group.totalInterest !== 0
                        ? formatNumberFixed(group.totalInterest, 2)
                        : "—"}
                  </span>
                </span>
                <span className="flex flex-wrap justify-end gap-1">
                  {ranked.map(([kind, count]) => (
                    <span key={kind} className={cn("rounded-full px-2 py-0.5 font-semibold text-[10px]", structureVerdictClass(kind))}>
                      {count > 1 ? `${count} ` : ""}
                      {structureVerdictLabel(kind)}
                    </span>
                  ))}
                </span>
              </div>
            </button>
            {open ? (
              <div className="overflow-x-auto border-t border-border/60 bg-muted/5">
                <table className="w-full min-w-[56rem] border-collapse text-xs">
                  <thead>
                    <tr className="bg-muted/30 text-left text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
                      <th className="w-8 px-2 py-2.5" />
                      <th className="px-3 py-2.5">RM</th>
                      <th className="px-3 py-2.5">Client</th>
                      <th className="px-3 py-2.5">{family === "accu" ? "Contract" : "Structure / Underlyings"}</th>
                      <th className="px-3 py-2.5">Outcome</th>
                      <th className="px-3 py-2.5">Delivery</th>
                      <th className="px-3 py-2.5">{family === "accu" ? "Maturity" : "Interest"}</th>
                      <th className="px-3 py-2.5 text-right">Shares</th>
                    </tr>
                  </thead>
                  <tbody>
                    {group.positions.map((position, index) => {
                      const rowOpen = expandedRow === position.position_key;
                      const rowEvents = eventsByPosition.get(position.position_key) ?? [];
                      return (
                        <React.Fragment key={position.position_key}>
                          <tr
                            className={cn(
                              "cursor-pointer border-t border-border/60 transition-colors hover:bg-muted/30",
                              index % 2 === 1 && "bg-muted/10",
                              rowOpen && "bg-muted/40",
                            )}
                            onClick={() => setExpandedRow(rowOpen ? null : position.position_key)}
                          >
                            <td className="px-2 py-2 align-top">
                              <ChevronRight
                                className={cn(
                                  "mt-0.5 size-4 text-muted-foreground transition-transform",
                                  rowOpen && "rotate-90 text-foreground",
                                )}
                              />
                            </td>
                            <StructureHoldingCells position={position} family={family} />
                          </tr>
                          {rowOpen ? (
                            <tr className="border-t border-border/60 bg-muted/20">
                              <td colSpan={8} className="p-0">
                                <StructureEventDetail position={position} events={rowEvents} underlyings={[]} />
                              </td>
                            </tr>
                          ) : null}
                        </React.Fragment>
                      );
                    })}
                  </tbody>
                </table>
              </div>
            ) : null}
          </div>
        );
      })}
      </div>
    </div>
  );
}

/**
 * Derivatives tab: one line per option contract type. With `scorecard`, the Structure tab's
 * structures or the Accu / Decu tab's contracts on this underlying instead.
 */
export function DerivativeLedgerBrief({
  tickerId,
  scorecard,
  includeExpired = false,
  onIncludeExpiredChange,
}: {
  tickerId: string;
  scorecard?: ScorecardFamily;
  /** Scorecards: also list expired holdings (tagged). */
  includeExpired?: boolean;
  onIncludeExpiredChange?: (next: boolean) => void;
}) {
  const { loading, error, positions } = useAssetAnalyticsOptions(tickerId, !scorecard);
  const holdings = useStructureUnderlyings(tickerId, Boolean(scorecard), scorecard ?? "structure");
  const optionLines = React.useMemo(() => briefOptions(positions), [positions]);

  if (scorecard) {
    return (
      <StructureScorecard
        loading={holdings.loading}
        tickerId={tickerId}
        positions={holdings.positions}
        events={holdings.events}
        includeExpired={includeExpired}
        onIncludeExpiredChange={onIncludeExpiredChange}
        family={scorecard}
      />
    );
  }

  return (
    <div className="overflow-hidden rounded-2xl border border-border/60 bg-card shadow-sm">
      <div className="flex items-start gap-2.5 border-b border-border/60 bg-gradient-to-b from-muted/40 to-transparent px-4 py-3">
        <Info className="mt-0.5 size-3.5 shrink-0 text-muted-foreground" />
        <p className="text-muted-foreground text-xs leading-relaxed">{OPTION_LEDGER_NOTE}</p>
      </div>
      {loading ? (
        <div className="divide-y divide-border/60" aria-busy="true" aria-label="Loading option scorecard">
          {Array.from({ length: 2 }, (_, index) => (
            <div key={index} className="grid grid-cols-2 gap-x-4 gap-y-3 px-4 py-3 sm:grid-cols-5">
              {Array.from({ length: 5 }, (_, cell) => (
                <div key={cell} className="space-y-1.5">
                  <Skeleton className="h-2.5 w-12" />
                  <Skeleton className="h-4 w-20" />
                </div>
              ))}
            </div>
          ))}
        </div>
      ) : null}
      {error ? <p className="px-4 py-3 text-destructive text-xs">{error}</p> : null}
      {optionLines.map((line) => (
        <div
          key={line.label}
          className="grid grid-cols-2 gap-x-4 gap-y-3 border-b border-border/60 px-4 py-3 last:border-b-0 sm:grid-cols-5"
        >
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Contract</p>
            <p className="font-medium text-sm">
              {line.label}
              {line.lots > 1 ? <span className="ml-1 font-normal text-muted-foreground text-xs">{line.lots} lots</span> : null}
            </p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Delivery</p>
            <p className="text-sm">{line.delivery ? "Delivered" : "No"}</p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Interest</p>
            <p className={cn("text-sm tabular-nums", line.interest < 0 && "text-red-600 dark:text-red-400")}>
              {line.hasInterest ? formatNumberFixed(line.interest, 2) : "—"}
            </p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Shares</p>
            <p className="text-sm tabular-nums">{formatNumberFixed(line.shares, 2)}</p>
          </div>
          <div>
            <p className="text-[10px] text-muted-foreground uppercase tracking-wider">Outcome</p>
            <p className="truncate text-sm" title={line.outcome || undefined}>
              {line.outcome || "—"}
            </p>
          </div>
        </div>
      ))}
    </div>
  );
}

export function AssetAnalyticsOptionsPanel({ tickerId }: { tickerId: string }) {
  const { loading, error, positions, events } = useAssetAnalyticsOptions(tickerId);

  const eventsByPosition = React.useMemo(() => {
    const map = new Map<string, DeliveredHoldingsEvent[]>();
    for (const event of events) {
      const key = event.position_key ?? "";
      const list = map.get(key) ?? [];
      list.push(event);
      map.set(key, list);
    }
    return map;
  }, [events]);

  if (loading) {
    return (
      <div className="flex items-center gap-2 py-10 text-sm text-muted-foreground">
        <Loader2 className="size-4 animate-spin" />
        Loading…
      </div>
    );
  }

  if (error) {
    return <p className="text-sm text-destructive">{error}</p>;
  }

  if (positions.length === 0) {
    return <p className="text-muted-foreground text-sm">No option positions found for this ISIN.</p>;
  }

  return (
    <div className="space-y-3">
      <p className="text-muted-foreground text-xs">{OPTION_LEDGER_NOTE}</p>
      {positions.map((position) => (
        <OptionPositionCard
          key={position.position_key}
          position={position}
          events={eventsByPosition.get(position.position_key) ?? []}
        />
      ))}
    </div>
  );
}
