/**
 * WorthDoing.ai
 * Author: Simon-Pierre Boucher
 * Contact: contact@spboucher.ai
 * File: src/components/wd/Timeline.tsx
 * Description: Live investigation timeline — renders real AgentEvents as they stream in.
 */
"use client";

import { memo } from "react";
import { cn } from "@/lib/utils";
import type { UiAgentEvent } from "@/lib/ui/api";

type Tone = "verdict" | "signal" | "rust" | "tele" | "ink" | "soft";

const TONE_DOT: Record<Tone, string> = {
  verdict: "bg-verdict",
  signal: "bg-signal",
  rust: "bg-rust",
  tele: "bg-tele",
  ink: "bg-ink",
  soft: "bg-ink-soft/50",
};

function describe(e: UiAgentEvent): { tone: Tone; label: string; body?: string; mono?: boolean } | null {
  const p = e.payload as Record<string, never> & Record<string, unknown>;
  switch (e.type) {
    case "investigation.started":
      return { tone: "ink", label: "Investigation started", body: String(p.objective ?? "") };
    case "agent.plan":
      return { tone: "ink", label: "Agent", body: String(p.text ?? "") };
    case "phase.changed":
      return { tone: "signal", label: `Phase → ${String(p.to)}` };
    case "search.started":
      return { tone: "tele", label: `Searching (${String(p.intent)})`, body: `“${String(p.query)}”`, mono: true };
    case "search.completed":
      return { tone: "tele", label: `Search returned ${String(p.resultCount)} results`, body: `“${String(p.query)}”`, mono: true };
    case "scrape.started":
      return { tone: "tele", label: "Reading", body: String(p.url), mono: true };
    case "scrape.completed": {
      const cached = p.fromCache ? " (cache)" : "";
      return { tone: "tele", label: `Read ${String(p.wordCount)} words${cached}`, body: String(p.title ?? p.url), mono: false };
    }
    case "scrape.failed":
      return { tone: "rust", label: "Scrape failed", body: String(p.url), mono: true };
    case "crawl.started":
      return { tone: "tele", label: `Crawling up to ${String(p.limit)} pages`, body: String(p.url), mono: true };
    case "crawl.completed":
      return { tone: "tele", label: `Crawled ${String(p.pageCount)} pages`, body: String(p.url), mono: true };
    case "crawl.failed":
      return { tone: "rust", label: "Crawl failed", body: String(p.url), mono: true };
    case "extract.started":
      return { tone: "tele", label: "Extracting structured data", body: (p.urls as string[])?.join(", "), mono: true };
    case "extract.completed":
      return { tone: "tele", label: "Extraction complete" };
    case "extract.failed":
      return { tone: "rust", label: "Extraction failed" };
    case "hypothesis.created":
      return { tone: "verdict", label: `Hypothesis formed · ${Math.round(Number(p.confidence) * 100)}%`, body: String(p.title) };
    case "hypothesis.updated": {
      const delta = Number(p.confidenceDelta ?? 0);
      const arrow = delta > 0 ? "▲" : delta < 0 ? "▼" : "→";
      const tone: Tone = delta < 0 ? "signal" : "verdict";
      return {
        tone,
        label: `Hypothesis ${arrow} ${Math.round(Number(p.confidence) * 100)}% (${delta >= 0 ? "+" : ""}${Math.round(delta * 100)})`,
        body: `${String(p.title)} — ${String(p.rationale ?? "")}`,
      };
    }
    case "hypothesis.rejected":
      return { tone: "rust", label: "Hypothesis rejected", body: `${String(p.title)} — ${String(p.reason ?? "")}` };
    case "evidence.saved": {
      const kind = String(p.kind);
      const tone: Tone = kind === "contradict" ? "rust" : kind === "support" ? "verdict" : "soft";
      return { tone, label: `Evidence saved (${kind})`, body: String(p.summary) };
    }
    case "opportunity.created":
      return {
        tone: "verdict",
        label: `Opportunity detected · Worth ${String(p.worthScore)}`,
        body: String(p.title),
      };
    case "report.started":
      return { tone: "ink", label: "Writing report", body: String(p.title) };
    case "report.completed":
      return { tone: "ink", label: `Report complete (${String(p.citations)} citations)`, body: String(p.title) };
    case "budget.updated":
      return null; // reflected in the stats bar, not the feed
    case "agent.error":
      return { tone: "rust", label: `Error in ${String(p.tool ?? "step")}`, body: String(p.message ?? "") };
    case "investigation.completed":
      return { tone: "ink", label: `Investigation complete — ${String(p.outcome ?? "")}`, body: String(p.conclusion ?? "") };
    case "investigation.failed":
      return { tone: "rust", label: "Investigation failed", body: String(p.message ?? "") };
    case "report.delta":
      return null; // streamed into the report pane, not the feed
    default:
      return null;
  }
}

export const Timeline = memo(function Timeline({ events }: { events: UiAgentEvent[] }) {
  const items = events
    .map((e) => ({ e, d: describe(e) }))
    .filter((x): x is { e: UiAgentEvent; d: NonNullable<ReturnType<typeof describe>> } => x.d !== null);

  if (items.length === 0) {
    return (
      <div className="flex flex-col items-center gap-2 py-16 text-center">
        <span className="wd-live-dot h-2 w-2 rounded-full bg-verdict" />
        <p className="font-mono text-xs text-ink-soft">waiting for the agent’s first move…</p>
      </div>
    );
  }

  return (
    <ol className="relative space-y-0">
      {items.map(({ e, d }) => (
        <li key={e.seq} className="wd-enter relative flex gap-3 pb-4 last:pb-0">
          <div className="flex flex-col items-center">
            <span className={cn("mt-1.5 h-2 w-2 shrink-0 rounded-full", TONE_DOT[d.tone])} />
            <span className="mt-1 w-px flex-1 bg-line" />
          </div>
          <div className="min-w-0 flex-1 pb-1">
            <div className="flex flex-wrap items-baseline gap-x-2">
              <span className="text-[13px] font-medium text-ink">{d.label}</span>
              <span className="font-mono text-[10px] text-ink-soft/70">
                {new Date(e.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" })}
              </span>
            </div>
            {d.body && (
              <p
                className={cn(
                  "mt-0.5 break-words text-[13px] leading-relaxed text-ink-soft",
                  d.mono && "font-mono text-xs",
                )}
              >
                {d.body.length > 600 ? `${d.body.slice(0, 600)}…` : d.body}
              </p>
            )}
          </div>
        </li>
      ))}
    </ol>
  );
});
