"use client";

import { useState } from "react";
import Link from "next/link";
import { ArrowRight, ArrowRightLeft, Bot, ChevronRight, Clock, Dot, Pencil, Plus, Send, ShieldAlert, Trash2 } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { formatDate, formatDateTime, humanize } from "../lib/format";

// Icon + colour per kind of action (ActivityFeed::kindFor). The label is read out for screen readers.
export const KIND_STYLES = {
  created: { icon: Plus, label: "Created", className: "bg-success/10 text-success" },
  updated: { icon: Pencil, label: "Updated", className: "bg-muted text-muted-foreground" },
  status: { icon: ArrowRightLeft, label: "Status change", className: "bg-info/10 text-info" },
  deleted: { icon: Trash2, label: "Removed or cancelled", className: "bg-destructive/10 text-destructive" },
  handover: { icon: Send, label: "Handed over or sent", className: "bg-primary/10 text-primary" },
  security: { icon: ShieldAlert, label: "Security", className: "bg-warning/15 text-warning" },
  other: { icon: Dot, label: "Other", className: "bg-muted text-muted-foreground" },
};

const CHANNEL_BADGES = { ai: "via AI assistant", system: "System", api: "via API", portal: "via portal", support: "Platform support" };
// No staff actor: platform support acting from the admin panel, or the system itself.
const actorName = (entry) => entry.actor?.name || (entry.channel === "support" ? "Platform support" : "System");
const PHRASAL = new Set(["over", "up", "out", "back"]);
const IRREGULAR_PAST = new Set(["sent", "set", "made", "built", "paid", "put", "split", "shared", "won", "lost"]);
const ISO_DATE = /^\d{4}-\d{2}-\d{2}(T[\d:.]+Z?)?$/;
const ENUM_WORD = /^[a-z]+(_[a-z]+)*$/;
// Fields whose values are enum words ("third_party" -> "Third party"); other text (names, notes) is shown as written.
const ENUM_FIELD = /(^|_)(status|type|kind|direction|method|model|visibility|result|outcome|severity|category|decision|mode|plan|role|department|stage|priority)$/;

const isPast = (word) => /^[a-z]+ed$/.test(word) || IRREGULAR_PAST.has(word);
const lcFirst = (text) => (/^[A-Z][a-z]/.test(text) ? text.charAt(0).toLowerCase() + text.slice(1) : text);

/**
 * Turn a log summary into the verb phrase after the person's name:
 *   "Created draft INV-00002 for 300.00." -> "created draft INV-00002 for 300.00"
 *   "Shipment SHP-1 marked shipped."      -> "marked shipment SHP-1 shipped"
 *   "Inspection report handed over to …"  -> "handed over inspection report to …"
 * Returns null when the summary has no past-tense verb near the start.
 */
function verbPhrase(summary) {
  const text = String(summary || "").trim().replace(/\.$/, "");
  if (!text) return null;
  const words = text.split(/\s+/);
  const first = words[0].toLowerCase();
  if (isPast(first)) return lcFirst(text);
  for (let index = 1; index < Math.min(words.length, 7); index += 1) {
    const match = words[index].match(/^([a-z]+)([:,]?)$/);
    if (!match || !isPast(match[1])) continue;
    const phrasal = PHRASAL.has(words[index + 1]) ? ` ${words[index + 1]}` : "";
    const subject = [lcFirst(words[0]), ...words.slice(1, index)].join(" ") + match[2];
    const rest = words.slice(index + (phrasal ? 2 : 1)).join(" ");
    return [`${match[1]}${phrasal}`, subject, rest].filter(Boolean).join(" ");
  }
  return null;
}

/** Verb and noun from an action code: "shipment.document_handed_over" -> { verb: "handed over", noun: "document" }. */
export function actionParts(action) {
  const segments = String(action || "").split(".");
  const words = segments[segments.length - 1].split("_");
  let verbAt = words.findIndex((word) => isPast(word) || ["complete", "ex"].includes(word));
  if (verbAt < 0) verbAt = words.length;
  const verbWords = words.slice(verbAt);
  const verb = verbWords.join(" ") === "ex factory" ? "marked ex-factory" : verbWords.join(" ") === "complete" ? "completed" : verbWords.join(" ") || "updated";
  const nounWords = words.slice(0, verbAt);
  const noun = (nounWords.length ? nounWords : (segments[segments.length - 2] || "record").split("_")).join(" ");
  return { verb, noun: humanize(noun).toLowerCase() };
}

function plural(noun, count) {
  if (count === 1) return noun;
  if (/[^aeiou]y$/.test(noun)) return `${noun.slice(0, -1)}ies`;
  if (/(s|x|ch|sh)$/.test(noun)) return `${noun}es`;
  return `${noun}s`;
}

function RecordLink({ code, route }) {
  if (!route) return <span className="font-medium text-foreground">{code}</span>;
  return <Link href={route} className="rounded-sm font-medium text-foreground underline decoration-border underline-offset-4 hover:decoration-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none">{code}</Link>;
}

/** Sentence text with record codes turned into links; codes missing from the text are appended ("… on PO-…"). */
function Linked({ text, target, parent }) {
  const refs = [target, parent].filter((ref) => ref?.code);
  const nodes = [];
  let rest = text;
  const appended = [];
  refs.forEach((ref) => {
    const at = rest.indexOf(ref.code);
    if (at < 0) { appended.push({ ...ref, isParent: ref === parent }); return; }
    nodes.push(rest.slice(0, at), <RecordLink key={ref.code} code={ref.code} route={ref.route} />);
    rest = rest.slice(at + ref.code.length);
  });
  nodes.push(rest);
  appended.forEach((ref) => nodes.push(ref.isParent ? " on " : " ", <RecordLink key={`+${ref.code}`} code={ref.code} route={ref.route} />));
  return nodes;
}

const targetRef = (entry) => (entry.targetCode ? { code: entry.targetCode, route: entry.targetRoute } : null);

/** The one-line sentence of an entry: "Rafa created draft INV-00002 … on PO-…". */
export function EntrySentence({ entry, withActor = true }) {
  const phrase = verbPhrase(entry.summary);
  const { verb, noun } = actionParts(entry.action);
  const phraseText = phrase || `${verb} ${noun}`;
  const text = withActor ? phraseText : phraseText.charAt(0).toUpperCase() + phraseText.slice(1);
  return <p className="min-w-0 text-sm leading-relaxed break-words">
    {withActor && <span className="font-medium">{actorName(entry)} </span>}
    <span className="text-foreground/90"><Linked text={text} target={targetRef(entry)} parent={entry.parent} /></span>
    {!phrase && entry.summary && <span className="block text-xs text-muted-foreground">{entry.summary}</span>}
  </p>;
}

/** "Rafa handed over 5 documents on SHP-…" for a burst of the same action. */
function BurstSentence({ entries }) {
  const [first] = entries;
  const { verb, noun } = actionParts(first.action);
  const sameTarget = entries.every((entry) => entry.targetCode === first.targetCode);
  const ref = sameTarget && first.targetCode ? targetRef(first) : first.parent;
  return <p className="min-w-0 text-sm leading-relaxed break-words">
    <span className="font-medium">{actorName(first)} </span>
    <span className="text-foreground/90">{verb} {entries.length} {plural(noun, entries.length)}{ref?.code && <> on <RecordLink code={ref.code} route={ref.route} /></>}</span>
  </p>;
}

const relativeFormatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
const clockFormatter = new Intl.DateTimeFormat("en-US", { timeStyle: "short", timeZone: "Asia/Dhaka" });

/** "just now", "5 min ago", "3 h ago" within a day; the clock time (Dhaka) for older entries. */
function timeLabel(value) {
  const date = new Date(value);
  const minutes = Math.round((Date.now() - date.getTime()) / 60_000);
  if (minutes < 1) return "just now";
  if (minutes < 60) return `${minutes} min ago`;
  if (minutes < 12 * 60) return relativeFormatter.format(-Math.round(minutes / 60), "hour").replace("hours", "h").replace("hour", "h");
  return clockFormatter.format(date);
}

export function EntryTime({ value, className }) {
  if (!value) return null;
  return <Tooltip>
    <TooltipTrigger asChild>
      <time dateTime={value} tabIndex={0} className={cn("rounded-sm text-xs whitespace-nowrap text-muted-foreground tabular-nums focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none", className)}>{timeLabel(value)}</time>
    </TooltipTrigger>
    <TooltipContent>{formatDateTime(value)} (Asia/Dhaka)</TooltipContent>
  </Tooltip>;
}

export function KindIcon({ kind, count }) {
  const style = KIND_STYLES[kind] || KIND_STYLES.other;
  const Icon = style.icon;
  return <span className={cn("relative mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full", style.className)}>
    <Icon className="size-4" aria-hidden="true" />
    <span className="sr-only">{style.label}{count ? `, ${count} entries` : ""}</span>
    {count > 1 && <span aria-hidden="true" className="absolute -right-1.5 -bottom-1.5 min-w-5 rounded-full border bg-background px-1 text-center text-xs leading-4 font-semibold text-foreground tabular-nums">{count}</span>}
  </span>;
}

function EntryBadges({ entry, showArea }) {
  const channel = CHANNEL_BADGES[entry.channel];
  const items = [
    showArea && entry.areaLabel && <span key="area">{entry.areaLabel}</span>,
    channel && <Badge key="channel" variant="info" className="gap-1">{entry.channel === "ai" && <Bot />}{channel}{entry.channel === "portal" && entry.portalUser?.name ? ` · ${entry.portalUser.name}${entry.portalUser.party ? ` (${entry.portalUser.party?.name || entry.portalUser.party})` : ""}` : ""}</Badge>,
    entry.sensitivity && entry.sensitivity !== "normal" && <Badge key="security" variant="warning" className="gap-1"><ShieldAlert /> {humanize(entry.sensitivity)}</Badge>,
  ].filter(Boolean);
  return items;
}

// Change values: dates, numbers, booleans, lists, and enum words formatted for people.
function formatValue(value, field = "") {
  if (value === null || value === undefined || value === "") return null;
  if (typeof value === "boolean") return value ? "Yes" : "No";
  if (Array.isArray(value)) return value.length ? value.map((item) => formatValue(item, field) ?? "—").join(", ") : null;
  if (typeof value === "number" || (/^-?\d+(\.\d+)?$/.test(String(value)))) return Number(value).toLocaleString("en-US", { maximumFractionDigits: 4 });
  const text = String(value);
  if (ISO_DATE.test(text)) return /T00:00:00/.test(text) || !text.includes("T") ? formatDate(text.slice(0, 10)) : formatDateTime(text);
  if (ENUM_FIELD.test(field) && ENUM_WORD.test(text)) {
    if (/(^|_)direction$/.test(field) && DIRECTION_LABELS[text]) return DIRECTION_LABELS[text];
    return ACRONYMS[text] || humanize(text);
  }
  return text;
}

// Codes that read wrong when title-cased ("Tt", "Lc").
const ACRONYMS = { tt: "TT", lc: "L/C", dp: "D/P", da: "D/A", fob: "FOB", cif: "CIF", cfr: "CFR", exw: "EXW", ddp: "DDP", qa: "QA", qc: "QC", mcp: "MCP", api: "API" };
const DIRECTION_LABELS = { in: "Received", out: "Paid", receivable: "Receivable", payable: "Payable", third_party: "Third party (tracked)" };

function ChangeValue({ value, field }) {
  const text = formatValue(value, field);
  return text === null ? <span className="text-muted-foreground">—</span> : <span className="break-words">{text}</span>;
}

function ChangesList({ changes, isNew }) {
  return <dl className="mt-1 grid gap-x-4 gap-y-1.5 rounded-lg border bg-muted/30 p-3 text-xs sm:grid-cols-[minmax(8rem,auto)_1fr]">
    {changes.map((change) => <div key={change.field} className="contents">
      <dt className="text-muted-foreground">{change.label}</dt>
      <dd className="flex flex-wrap items-center gap-1.5">
        {!isNew && <><span className="text-muted-foreground line-through decoration-muted-foreground/50"><ChangeValue value={change.from} field={change.field} /></span><ArrowRight className="size-3 text-muted-foreground" aria-label="changed to" /></>}
        <span className="font-medium"><ChangeValue value={change.to} field={change.field} /></span>
      </dd>
    </div>)}
  </dl>;
}

// Small inline disclosure button used in the meta line ("Show changes (3)", "Show 5 entries").
function DisclosureButton({ open, children }) {
  return <CollapsibleTrigger asChild>
    <Button variant="ghost" size="sm" className="h-6 gap-1 px-1.5 text-xs font-normal text-muted-foreground hover:text-foreground" aria-expanded={open}>
      <ChevronRight className={cn("size-3.5 transition-transform", open && "rotate-90")} />{children}
    </Button>
  </CollapsibleTrigger>;
}

function MetaLine({ entry, showArea, children, action }) {
  const badges = EntryBadges({ entry, showArea });
  if (!badges.length && !children && !action) return null;
  return <div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">{children}{badges}{action}</div>;
}

/** One activity entry; "Show changes" (previous → new) or "Show details" (values of a new record) when the log has them. */
export function EntryRow({ entry, showArea, nested = false }) {
  const [open, setOpen] = useState(false);
  const changes = entry.changes || [];
  const isNew = changes.every((change) => change.from === null || change.from === undefined);
  return <li className={cn("flex gap-3", nested ? "py-2" : "px-4 py-3")}>
    {!nested && <KindIcon kind={entry.kind} />}
    <Collapsible open={open} onOpenChange={setOpen} className="grid min-w-0 flex-1 gap-1">
      <div className="flex items-start gap-3">
        <div className="min-w-0 flex-1"><EntrySentence entry={entry} withActor={!nested} /></div>
        <EntryTime value={entry.createdAt} className="hidden pt-0.5 sm:block" />
      </div>
      <MetaLine
        entry={entry}
        showArea={showArea && !nested}
        action={changes.length > 0 && <DisclosureButton open={open}>{open ? "Hide" : "Show"} {isNew ? "details" : "changes"} <span className="tabular-nums">({changes.length})</span></DisclosureButton>}
      >
        <EntryTime value={entry.createdAt} className="sm:hidden" />
      </MetaLine>
      {changes.length > 0 && <CollapsibleContent><ChangesList changes={changes} isNew={isNew} /></CollapsibleContent>}
    </Collapsible>
  </li>;
}

/** A burst of the same action (grouped in useActivityFeed) with an expandable list of its entries. */
export function BurstRow({ group, showArea }) {
  const [open, setOpen] = useState(false);
  const [newest] = group.entries;
  const oldest = group.entries[group.entries.length - 1];
  const started = clockFormatter.format(new Date(oldest.createdAt));
  const ended = clockFormatter.format(new Date(newest.createdAt));
  return <li className="flex gap-3 px-4 py-3">
    <KindIcon kind={newest.kind} count={group.entries.length} />
    <Collapsible open={open} onOpenChange={setOpen} className="grid min-w-0 flex-1 gap-1">
      <div className="flex items-start gap-3">
        <div className="min-w-0 flex-1"><BurstSentence entries={group.entries} /></div>
        <EntryTime value={newest.createdAt} className="hidden pt-0.5 sm:block" />
      </div>
      <MetaLine entry={newest} showArea={showArea} action={<DisclosureButton open={open}>{open ? "Hide" : "Show"} {group.entries.length} entries</DisclosureButton>}>
        <EntryTime value={newest.createdAt} className="sm:hidden" />
        {started !== ended && <span className="inline-flex items-center gap-1"><Clock className="size-3" aria-hidden="true" />{started} – {ended}</span>}
      </MetaLine>
      <CollapsibleContent>
        <ol className="mt-1 divide-y border-l pl-3" aria-label={`${group.entries.length} grouped entries`}>
          {group.entries.map((entry) => <EntryRow key={entry.id} entry={entry} nested />)}
        </ol>
      </CollapsibleContent>
    </Collapsible>
  </li>;
}
