/**
 * Search-box.ai
 * Author: Simon-Pierre Boucher
 * Contact: contact@spboucher.ai
 * File: apps/web/components/SessionView.tsx
 * Description: Live research view — event-sourced UI (bloom graph, pastille timeline, claims, answer).
 */

"use client";

import { useEffect, useMemo, useReducer, useRef, useState } from "react";
import type { ResearchEventPayload, CitationMapEntry } from "@search-box/events";
import type { Claim, Contradiction, SessionStatus, Source } from "@search-box/shared";
import { AnswerPanel } from "./AnswerPanel";
import { ResearchGraph } from "./ResearchGraph";
import { Glyph, Pastille, type PastilleKind } from "./Pastille";

/* ------------------------------- view state ------------------------------- */

interface FeedItem {
  key: string;
  kind: PastilleKind;
  label: string;
  title: string;
  detail?: string;
  quote?: string;
  stance?: string;
  status?: string;
  confidence?: number;
  objectives?: string[];
  pending?: boolean;
}

interface ViewState {
  status: SessionStatus;
  feed: FeedItem[];
  objectives: string[];
  answer: string;
  answerDone: boolean;
  citations: CitationMapEntry[];
  sources: Map<string, Source>;
  claims: Map<string, Claim>;
  contradictions: Contradiction[];
  error: string | null;
  seq: number;
}

function initialState(status: SessionStatus): ViewState {
  return {
    status,
    feed: [],
    objectives: [],
    answer: "",
    answerDone: false,
    citations: [],
    sources: new Map(),
    claims: new Map(),
    contradictions: [],
    error: null,
    seq: 0
  };
}

function reduce(state: ViewState, ev: { seq: number; payload: ResearchEventPayload }): ViewState {
  const p = ev.payload;
  const next: ViewState = { ...state, seq: ev.seq };
  const push = (item: FeedItem) => {
    next.feed = [...next.feed, item];
  };

  switch (p.type) {
    case "session.status":
      next.status = p.status;
      break;
    case "session.failed":
      next.error = p.error;
      push({ key: `e${ev.seq}`, kind: "failed", label: "failed", title: p.error });
      break;
    case "session.completed":
      push({ key: `e${ev.seq}`, kind: "done", label: "research complete", title: "Answer delivered with full provenance." });
      break;
    case "plan.updated":
      next.objectives = p.objectives;
      push({ key: `e${ev.seq}`, kind: "plan", label: "plan", title: p.publicReason, objectives: p.objectives });
      break;
    case "thought":
      push({ key: `e${ev.seq}`, kind: "thought", label: "reasoning", title: p.publicReason });
      break;
    case "action.started":
      push({
        key: p.actionId,
        kind: p.kind === "search" ? "search" : "fetch",
        label: p.kind === "search" ? "searching" : "reading",
        title: p.label,
        pending: true
      });
      break;
    case "action.completed":
      next.feed = next.feed.map((item) =>
        item.key === p.actionId
          ? {
              ...item,
              pending: false,
              label: p.ok ? (item.kind === "search" ? "searched" : "read source") : `${item.kind} failed`,
              kind: p.ok && item.kind === "fetch" ? "read" : item.kind,
              detail: p.summary
            }
          : item
      );
      break;
    case "source.added":
    case "source.updated": {
      const sources = new Map(next.sources);
      sources.set(p.source.id, p.source);
      next.sources = sources;
      break;
    }
    case "claim.added": {
      const claims = new Map(next.claims);
      claims.set(p.claim.id, p.claim);
      next.claims = claims;
      push({ key: `e${ev.seq}`, kind: "claim", label: "new claim", title: p.claim.text });
      break;
    }
    case "claim.updated": {
      const claims = new Map(next.claims);
      claims.set(p.claim.id, p.claim);
      next.claims = claims;
      push({
        key: `e${ev.seq}`,
        kind: "belief",
        label: "belief update",
        title: p.claim.text,
        status: p.claim.status,
        confidence: p.claim.confidence,
        detail: p.claim.publicReason ?? undefined
      });
      break;
    }
    case "evidence.added":
      push({
        key: `e${ev.seq}`,
        kind: "evidence",
        label: `evidence · ${p.evidence.stance}`,
        title: p.sourceTitle ?? p.sourceUrl,
        quote: p.evidence.quote,
        stance: p.evidence.stance
      });
      break;
    case "contradiction.added":
      next.contradictions = [...next.contradictions, p.contradiction];
      push({ key: `e${ev.seq}`, kind: "contradiction", label: "contradiction", title: p.contradiction.description });
      break;
    case "synthesis.started":
      push({ key: `e${ev.seq}`, kind: "synthesis", label: "writing", title: "Evidence base closed — writing the answer." });
      break;
    case "answer.delta":
      next.answer = state.answer + p.delta;
      break;
    case "answer.completed":
      next.answer = p.answer;
      next.answerDone = true;
      next.citations = p.citations;
      break;
    default:
      break;
  }
  return next;
}

/* --------------------------------- view ---------------------------------- */

type Tab = "live" | "answer" | "evidence";

export function SessionView({
  id,
  question,
  initialStatus
}: {
  id: string;
  question: string;
  initialStatus: SessionStatus;
}) {
  const [state, dispatch] = useReducer(
    (s: ViewState, ev: { seq: number; payload: ResearchEventPayload }) => reduce(s, ev),
    initialStatus,
    initialState
  );
  const [tab, setTab] = useState<Tab>("live");
  const feedEndRef = useRef<HTMLDivElement>(null);
  const autoTabbed = useRef(false);

  // Event-sourced: replay from seq 0; EventSource reconnects with Last-Event-ID.
  useEffect(() => {
    const es = new EventSource(`/api/research/${id}/stream?from=0`);
    es.onmessage = (msg) => {
      try {
        const payload = JSON.parse(msg.data) as ResearchEventPayload;
        dispatch({ seq: Number(msg.lastEventId || 0), payload });
      } catch {
        // ignore malformed frames
      }
    };
    es.addEventListener("end", () => es.close());
    return () => es.close();
  }, [id]);

  useEffect(() => {
    if (state.answer.length > 0 && !autoTabbed.current) {
      autoTabbed.current = true;
      if (window.innerWidth < 1024) setTab("answer");
    }
  }, [state.answer]);

  useEffect(() => {
    if (tab === "live") feedEndRef.current?.scrollIntoView({ behavior: "smooth", block: "nearest" });
  }, [state.feed.length, tab]);

  const claims = useMemo(() => [...state.claims.values()], [state.claims]);

  const stats = useMemo(() => {
    const fetched = [...state.sources.values()].filter((s) => s.status === "fetched").length;
    const avg = claims.length > 0 ? claims.reduce((a, c) => a + c.confidence, 0) / claims.length : null;
    return { sources: fetched, claims: claims.length, contradictions: state.contradictions.length, confidence: avg };
  }, [state.sources, claims, state.contradictions]);

  const live = state.status === "running" || state.status === "synthesizing" || state.status === "pending";

  const citedSources = useMemo(() => {
    if (state.citations.length > 0) return state.citations;
    return [...state.sources.values()]
      .filter((s) => s.citationIndex !== null)
      .sort((a, b) => (a.citationIndex ?? 0) - (b.citationIndex ?? 0))
      .map((s) => ({ index: s.citationIndex as number, sourceId: s.id, url: s.url, title: s.title }));
  }, [state.citations, state.sources]);

  const phaseLabel =
    state.status === "failed"
      ? "failed"
      : state.status === "completed"
        ? "complete"
        : state.status === "synthesizing"
          ? "writing answer"
          : "researching";

  return (
    <main>
      <div className="question-head">
        <div className="kicker">research session</div>
        <h1>{question}</h1>
      </div>

      <div className="statsbar" role="status">
        <span className={`stat-seg phase ${state.status === "completed" ? "done" : ""} ${state.status === "failed" ? "dead" : ""}`}>
          <span className={`pulse ${live ? "" : "still"}`} />
          <b>{phaseLabel}</b>
        </span>
        <span className="stat-seg">
          <Pastille kind="source" size="sm" />
          sources <b>{stats.sources}</b>
        </span>
        <span className="stat-seg">
          <Pastille kind="claim" size="sm" />
          claims <b>{stats.claims}</b>
        </span>
        <span className="stat-seg">
          <Pastille kind="contradiction" size="sm" />
          contradictions <b>{stats.contradictions}</b>
        </span>
        <span className="stat-seg">
          <Pastille kind="confidence" size="sm" />
          confidence <b>{stats.confidence === null ? "—" : `${Math.round(stats.confidence * 100)}%`}</b>
        </span>
      </div>

      {(state.objectives.length > 0 || claims.length > 0) && (
        <ResearchGraph
          objectives={state.objectives}
          claims={claims}
          live={live}
          hasContradiction={state.contradictions.length > 0}
        />
      )}

      {state.error && (
        <div className="error-box">
          <Pastille kind="failed" />
          research failed: {state.error}
        </div>
      )}

      <div className="tabbar">
        {(
          [
            ["live", "plan"],
            ["answer", "synthesis"],
            ["evidence", "evidence"]
          ] as Array<[Tab, PastilleKind]>
        ).map(([t, icon]) => (
          <button key={t} className={tab === t ? "active" : ""} onClick={() => setTab(t)}>
            <Glyph kind={icon} size={13} />
            {t}
          </button>
        ))}
      </div>

      <div className="panes">
        <section className={`pane ${tab === "live" ? "visible" : ""}`} aria-label="Live research feed">
          <h2>
            <Glyph kind="plan" size={13} /> Live research
          </h2>
          <div className="tl">
            {state.feed.map((item) => (
              <TimelineRow key={item.key} item={item} />
            ))}
            {state.feed.length === 0 && (
              <div className="tl-empty">
                <Pastille kind="search" spinning={live} />
                waiting for the first research event…
              </div>
            )}
            <div ref={feedEndRef} />
          </div>
        </section>

        <section className={`pane ${tab === "answer" ? "visible" : ""}`} aria-label="Answer">
          <h2>
            <Glyph kind="synthesis" size={13} /> Answer
          </h2>
          <AnswerPanel markdown={state.answer} done={state.answerDone} live={live} />
        </section>

        <section className={`pane evidence-pane ${tab === "evidence" ? "visible" : ""}`} aria-label="Claims and sources">
          {claims.length > 0 && (
            <div className="board">
              <h2>
                <Glyph kind="claim" size={13} /> Claims under investigation
              </h2>
              <div className="claims-grid">
                {claims.map((c) => (
                  <ClaimCard key={c.id} claim={c} />
                ))}
              </div>
            </div>
          )}

          <div className="board">
            <h2>
              <Glyph kind="source" size={13} /> Sources{" "}
              {citedSources.length > 0 ? `— ${citedSources.length} cited` : `— ${state.sources.size} seen`}
            </h2>
            <ul className="source-list">
              {(citedSources.length > 0
                ? citedSources.map((c) => ({
                    key: c.sourceId,
                    idx: String(c.index),
                    title: c.title ?? c.url,
                    url: c.url,
                    anchor: `src-${c.index}`
                  }))
                : [...state.sources.values()]
                    .filter((s) => s.status === "fetched")
                    .map((s) => ({
                      key: s.id,
                      idx: null as string | null,
                      title: s.title ?? s.url,
                      url: s.url,
                      anchor: undefined as string | undefined
                    }))
              ).map((s) => (
                <li key={s.key} id={s.anchor}>
                  <a className="source-card" href={s.url} target="_blank" rel="noreferrer noopener">
                    <DomainTile url={s.url} />
                    <span className="meta">
                      <span className="title">{s.title}</span>
                      <span className="domain">{hostname(s.url)}</span>
                    </span>
                    {s.idx && <span className="cite-idx">[{s.idx}]</span>}
                  </a>
                </li>
              ))}
            </ul>
          </div>
        </section>
      </div>
    </main>
  );
}

function TimelineRow({ item }: { item: FeedItem }) {
  return (
    <div className={`tl-row ${item.kind === "contradiction" ? "is-contradiction" : ""}`}>
      <Pastille kind={item.kind} spinning={item.pending === true} />
      <div className="tl-card">
        <div className="tl-head">
          {item.label}
          {item.status && (
            <span className={`status-chip ${item.status}`}>
              {item.status}
              {item.confidence !== undefined ? ` · ${Math.round(item.confidence * 100)}%` : ""}
            </span>
          )}
        </div>
        <div className="tl-body">
          {item.title}
          {item.detail && (
            <>
              {" "}
              <span className="mono">— {item.detail}</span>
            </>
          )}
          {item.objectives && (
            <ul className="tl-objectives">
              {item.objectives.map((o, i) => (
                <li key={i}>{o}</li>
              ))}
            </ul>
          )}
          {item.quote && (
            <div className={`tl-quote ${item.stance === "contradicts" ? "contradicts" : ""}`}>“{item.quote}”</div>
          )}
        </div>
      </div>
    </div>
  );
}

function ClaimCard({ claim }: { claim: Claim }) {
  return (
    <div className="claim-card">
      <div className="tl-head">
        <span className={`status-chip ${claim.status}`}>{claim.status}</span>
      </div>
      <div className="txt">{claim.text}</div>
      {claim.publicReason && <div className="why">{claim.publicReason}</div>}
      <div className={`meter ${claim.status}`}>
        <span className="bar">
          <span className="fill" style={{ width: `${Math.round(claim.confidence * 100)}%` }} />
        </span>
        <span className="pct">{Math.round(claim.confidence * 100)}%</span>
      </div>
    </div>
  );
}

/** Home-made favicon substitute: deterministic hue tile from the domain. */
function DomainTile({ url }: { url: string }) {
  const domain = hostname(url);
  let hash = 0;
  for (let i = 0; i < domain.length; i++) hash = (hash * 31 + domain.charCodeAt(i)) | 0;
  const hue = Math.abs(hash) % 360;
  return (
    <span className="tile" style={{ background: `hsl(${hue} 42% 42%)` }}>
      {domain.replace(/^www\./, "").charAt(0)}
    </span>
  );
}

function hostname(url: string): string {
  try {
    return new URL(url).hostname;
  } catch {
    return url;
  }
}
