// Auteur : Simon-Pierre Boucher — contact@spboucher.ai
/**
 * Ka — interface de conversation en streaming avec l'agent IA.
 * Lit le flux SSE de /api/ka : deltas de texte, activité d'outils, fin.
 * Le fil a son PROPRE défilement (jamais la page) et l'auto-scroll ne suit
 * que si l'utilisateur est déjà au bas du fil — zéro sursaut pendant le stream.
 */
"use client";
import { useEffect, useRef, useState } from "react";
import { useLang } from "./LangContext";

interface Msg {
  role: "user" | "assistant";
  content: string;
  tools?: string[];
}

const TOOL_LABELS: Record<string, { fr: string; en: string }> = {
  chercher_plex: { fr: "Recherche dans le registre", en: "Searching the registry" },
  evaluer_plex: { fr: "Évaluation du plex", en: "Valuing the plex" },
  proforma_investisseur: { fr: "Calcul du pro forma", en: "Building the pro forma" },
  comparables_detailles: { fr: "Analyse des comparables", en: "Analyzing comparables" },
  indice_marche_plex: { fr: "Lecture du marché des plex", en: "Reading the plex market" },
  stats_municipalite: { fr: "Statistiques municipales", en: "Municipal statistics" },
  stats_provinciales: { fr: "Statistiques provinciales", en: "Provincial statistics" },
  evaluer_parc: { fr: "Évaluation du parc", en: "Evaluating the portfolio" },
  comparer_plex: { fr: "Comparaison des plex", en: "Comparing plexes" },
  estimation_manuelle: { fr: "Estimation par caractéristiques", en: "Estimating from specs" },
  chercher_plex_secteur: { fr: "Balayage du secteur", en: "Scanning the area" },
  liens_rapports: { fr: "Préparation des rapports", en: "Preparing reports" },
};

/* ------------------------------------------------------------ markdown */

function inline(s: string, key: string): React.ReactNode[] {
  const parts: React.ReactNode[] = [];
  const rx = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)|\*\*([^*]+)\*\*|\*([^*]+)\*/g;
  let last = 0;
  let m: RegExpExecArray | null;
  let i = 0;
  while ((m = rx.exec(s))) {
    if (m.index > last) parts.push(s.slice(last, m.index));
    if (m[1] && m[2])
      parts.push(
        <a
          key={`${key}-${i++}`}
          href={m[2]}
          target="_blank"
          rel="noopener noreferrer"
          className="font-bold underline decoration-[var(--lime)] decoration-2 underline-offset-2"
        >
          {m[1]}
        </a>
      );
    else if (m[3]) parts.push(<strong key={`${key}-${i++}`}>{m[3]}</strong>);
    else if (m[4]) parts.push(<em key={`${key}-${i++}`}>{m[4]}</em>);
    last = m.index + m[0].length;
  }
  if (last < s.length) parts.push(s.slice(last));
  return parts;
}

/** Rendu markdown minimal et sûr : gras, italique, listes, liens, TABLEAUX, titres. */
function renderMd(text: string): React.ReactNode[] {
  const blocks: React.ReactNode[] = [];
  const lines = text.split("\n");
  let list: string[] = [];
  let table: string[][] = [];
  let k = 0;

  const flushList = () => {
    if (!list.length) return;
    blocks.push(
      <ul key={`ul-${k++}`} className="my-1.5 space-y-1 pl-1">
        {list.map((li, j) => (
          <li key={j} className="flex gap-2">
            <span className="mt-[7px] h-[6px] w-[6px] shrink-0 rounded-full bg-[var(--lime)]" />
            <span className="min-w-0">{inline(li, `li-${k}-${j}`)}</span>
          </li>
        ))}
      </ul>
    );
    list = [];
  };

  const flushTable = () => {
    if (!table.length) return;
    const [head, ...rows] = table;
    blocks.push(
      <div
        key={`tw-${k++}`}
        className="my-2 max-w-full overflow-x-auto rounded-lg border-[1.5px] border-[var(--line)] bg-surface"
      >
        <table className="w-full border-collapse text-[12px]">
          <thead>
            <tr>
              {head.map((c, j) => (
                <th
                  key={j}
                  className="vp-mono whitespace-nowrap border-b-[1.5px] border-[var(--line)] bg-surface-2 px-2.5 py-1.5 text-left text-[9px] font-bold uppercase tracking-[0.08em] text-ink-3"
                >
                  {inline(c, `th-${k}-${j}`)}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((row, ri) => (
              <tr key={ri}>
                {row.map((c, j) => (
                  <td
                    key={j}
                    className={`whitespace-nowrap px-2.5 py-1.5 align-top ${
                      ri < rows.length - 1 ? "border-b border-[var(--line)]" : ""
                    }`}
                  >
                    {inline(c, `td-${k}-${ri}-${j}`)}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
    table = [];
  };

  for (const line of lines) {
    const t = line.trim();
    // ligne de tableau |a|b|  (ignore la ligne séparatrice |---|---|)
    if (t.startsWith("|") && t.endsWith("|") && t.length > 2) {
      const cells = t
        .slice(1, -1)
        .split("|")
        .map((c) => c.trim());
      if (!cells.every((c) => /^:?-{2,}:?$/.test(c))) {
        flushList();
        table.push(cells);
      }
      continue;
    }
    flushTable();
    if (/^[-•]\s+/.test(t)) {
      list.push(t.replace(/^[-•]\s+/, ""));
      continue;
    }
    flushList();
    if (t === "" || t === "---") continue;
    if (t.startsWith("[réf:")) continue; // réf machine pour la continuité — jamais affichée
    if (/^#{1,4}\s+/.test(t)) {
      blocks.push(
        <p key={`h-${k++}`} className="vp-display mt-2 text-[15px] font-bold">
          {inline(t.replace(/^#{1,4}\s+/, ""), `h-${k}`)}
        </p>
      );
    } else {
      blocks.push(
        <p key={`p-${k++}`} className="my-1.5">
          {inline(t, `p-${k}`)}
        </p>
      );
    }
  }
  flushList();
  flushTable();
  return blocks;
}

/* ------------------------------------------------------------ composant */

export default function KaChat() {
  const { lang } = useLang();
  const fr = lang === "fr";
  const [messages, setMessages] = useState<Msg[]>([]);
  const [input, setInput] = useState("");
  const [busy, setBusy] = useState(false);
  const [activeTool, setActiveTool] = useState<string | null>(null);
  const feedRef = useRef<HTMLDivElement>(null);
  const stickBottom = useRef(true);

  // auto-scroll DU FIL seulement (jamais la page), et seulement si déjà en bas
  useEffect(() => {
    const el = feedRef.current;
    if (el && stickBottom.current) el.scrollTop = el.scrollHeight;
  }, [messages, activeTool]);

  const onFeedScroll = () => {
    const el = feedRef.current;
    if (!el) return;
    stickBottom.current = el.scrollHeight - el.scrollTop - el.clientHeight < 140;
  };

  const suggestions = fr
    ? [
        "Combien vaut le triplex au 2075 rue Grandjean à Québec ?",
        "Trouve-moi un triplex à Trois-Rivières sous 700 000 $",
        "Fais-moi le pro forma d'un duplex à Sherbrooke",
        "Que valent tous les plex du Québec ?",
      ]
    : [
        "How much is the triplex at 2075 rue Grandjean in Québec City worth?",
        "Find me a triplex in Trois-Rivières under $700,000",
        "Build the pro forma for a duplex in Sherbrooke",
        "What are all Québec plexes worth?",
      ];

  async function send(text: string) {
    const content = text.trim();
    if (!content || busy) return;
    setInput("");
    setBusy(true);
    setActiveTool(null);
    stickBottom.current = true;

    const history = [...messages, { role: "user" as const, content }];
    setMessages([...history, { role: "assistant", content: "", tools: [] }]);

    try {
      const res = await fetch("/api/ka", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          messages: history.map(({ role, content }) => ({ role, content })),
        }),
      });
      if (!res.ok || !res.body) throw new Error(String(res.status));

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      const patch = (fn: (m: Msg) => Msg) =>
        setMessages((prev) => {
          const next = [...prev];
          next[next.length - 1] = fn(next[next.length - 1]);
          return next;
        });

      for (;;) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const events = buffer.split("\n\n");
        buffer = events.pop() ?? "";
        for (const ev of events) {
          const line = ev.split("\n").find((l) => l.startsWith("data: "));
          if (!line) continue;
          let data: {
            type: string;
            text?: string;
            name?: string;
            message?: string;
            refs?: { id: string; adresse: string; pdf?: string; pdf_pro?: string }[];
          };
          try {
            data = JSON.parse(line.slice(6));
          } catch {
            continue;
          }
          if (data.type === "text" && data.text) {
            setActiveTool(null);
            patch((m) => ({ ...m, content: m.content + data.text }));
          } else if (data.type === "tool" && data.name) {
            setActiveTool(data.name);
            patch((m) => ({
              ...m,
              tools: m.tools?.includes(data.name!) ? m.tools : [...(m.tools ?? []), data.name!],
            }));
          } else if (data.type === "refs" && Array.isArray(data.refs)) {
            const lines = (
              data.refs as { id: string; adresse: string; pdf?: string; pdf_pro?: string }[]
            )
              .map(
                (r) =>
                  `[réf: ${r.adresse} → id ${r.id}` +
                  (r.pdf ? ` | rapport standard: ${r.pdf} | rapport pro: ${r.pdf_pro}` : "") +
                  "]"
              )
              .join("\n");
            patch((m) => ({ ...m, content: m.content + "\n\n" + lines }));
          } else if (data.type === "error") {
            patch((m) => ({
              ...m,
              content: m.content + (m.content ? "\n\n" : "") + `*${data.message}*`,
            }));
          }
        }
      }
    } catch {
      setMessages((prev) => {
        const next = [...prev];
        const last = next[next.length - 1];
        if (last?.role === "assistant" && !last.content) {
          last.content = fr
            ? "*Connexion interrompue — réessaie dans un instant.*"
            : "*Connection lost — try again in a moment.*";
        }
        return next;
      });
    } finally {
      setActiveTool(null);
      setBusy(false);
    }
  }

  const toolLabel = (name: string) =>
    TOOL_LABELS[name]?.[fr ? "fr" : "en"] ?? name.replace(/_/g, " ");

  return (
    <div className="mx-auto flex w-full max-w-[860px] flex-col">
      {/* fil de conversation — défilement interne, hauteur stable */}
      <div
        ref={feedRef}
        onScroll={onFeedScroll}
        className="vp-card flex h-[56dvh] flex-col gap-4 overflow-y-auto overscroll-contain p-3.5 sm:h-[58vh] sm:gap-5 sm:p-6"
      >
        {messages.length === 0 && (
          <div className="my-auto text-center">
            <div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl border-[1.5px] border-ink bg-ink shadow-[5px_5px_0_var(--lime)] sm:h-16 sm:w-16">
              <span className="vp-display text-[22px] font-bold text-lime sm:text-[26px]">Ka</span>
            </div>
            <p className="vp-display px-2 text-[17px] font-bold sm:text-[19px]">
              {fr ? "Dis-moi quel plex t'intéresse." : "Tell me which plex you're curious about."}
            </p>
            <p className="mx-auto mt-1.5 max-w-[440px] px-3 text-[13px] text-ink-2 sm:text-[13.5px]">
              {fr
                ? "Je fouille 393 867 plex, 91 000 ventes réelles et je calcule des pro forma complets, en direct, pendant qu'on jase."
                : "I search 393,867 plexes, 91k real sales and build full pro formas, live, while we chat."}
            </p>
            <div className="mx-auto mt-4 flex max-w-[560px] flex-col items-stretch gap-2 px-1 sm:mt-5 sm:flex-row sm:flex-wrap sm:justify-center">
              {suggestions.map((s) => (
                <button
                  key={s}
                  onClick={() => send(s)}
                  className="rounded-full border-[1.5px] border-ink bg-surface px-3.5 py-2 text-[12.5px] font-semibold transition-all hover:bg-lime-soft hover:shadow-[3px_3px_0_var(--ink)] active:translate-y-[1px]"
                >
                  {s}
                </button>
              ))}
            </div>
          </div>
        )}

        {messages.map((m, i) => (
          <div key={i} className={m.role === "user" ? "flex justify-end" : "flex justify-start"}>
            {m.role === "user" ? (
              <div className="max-w-[88%] rounded-2xl rounded-br-md border-[1.5px] border-ink bg-ink px-3.5 py-2.5 text-[13.5px] font-medium text-paper sm:text-[14px]">
                {m.content}
              </div>
            ) : (
              <div className="flex w-full max-w-full gap-2 sm:max-w-[94%] sm:gap-3">
                <div className="mt-0.5 hidden h-8 w-8 shrink-0 items-center justify-center rounded-lg border-[1.5px] border-ink bg-ink sm:flex">
                  <span className="vp-display text-[12px] font-bold text-lime">Ka</span>
                </div>
                <div className="min-w-0 flex-1">
                  {m.tools && m.tools.length > 0 && (
                    <div className="mb-1.5 flex flex-wrap gap-1.5">
                      {m.tools.map((t) => (
                        <span
                          key={t}
                          className="vp-mono inline-flex items-center gap-1.5 rounded-full border border-[var(--line)] bg-surface-2 px-2.5 py-[3px] text-[9.5px] uppercase tracking-[0.05em] text-ink-2"
                        >
                          <span
                            className={`h-[6px] w-[6px] rounded-full ${
                              busy && i === messages.length - 1 && activeTool === t
                                ? "animate-pulse bg-[var(--lime)]"
                                : "bg-[var(--green)]"
                            }`}
                          />
                          {toolLabel(t)}
                        </span>
                      ))}
                    </div>
                  )}
                  <div className="text-[13.5px] leading-relaxed sm:text-[14px]">
                    {m.content ? (
                      renderMd(m.content)
                    ) : busy && i === messages.length - 1 ? (
                      <span className="inline-flex gap-1 py-1">
                        <span className="h-2 w-2 animate-bounce rounded-full bg-ink-3 [animation-delay:0ms]" />
                        <span className="h-2 w-2 animate-bounce rounded-full bg-ink-3 [animation-delay:120ms]" />
                        <span className="h-2 w-2 animate-bounce rounded-full bg-ink-3 [animation-delay:240ms]" />
                      </span>
                    ) : null}
                  </div>
                </div>
              </div>
            )}
          </div>
        ))}
      </div>

      {/* zone de saisie — toujours visible sous le fil */}
      <form
        onSubmit={(e) => {
          e.preventDefault();
          send(input);
        }}
        className="mt-3 flex gap-2 sm:mt-4 sm:gap-2.5"
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder={fr ? "Adresse ou question…" : "Address or question…"}
          className="vp-input min-w-0 flex-1"
          maxLength={2000}
          aria-label={fr ? "Message à Ka" : "Message to Ka"}
        />
        <button
          type="submit"
          disabled={busy || !input.trim()}
          className="btn btn-primary shrink-0 px-4 disabled:opacity-50 sm:px-5"
          aria-label={fr ? "Envoyer" : "Send"}
        >
          <span className="hidden sm:inline">{busy ? (fr ? "Ka réfléchit…" : "Thinking…") : fr ? "Envoyer" : "Send"}</span>
          <span className="sm:hidden">{busy ? "…" : "↑"}</span>
        </button>
      </form>

      <p className="vp-mono mt-2.5 px-2 text-center text-[9.5px] uppercase leading-relaxed tracking-[0.08em] text-ink-3 sm:mt-3 sm:text-[10px]">
        {fr
          ? "Ka est une IA (Claude d'Anthropic) — estimations indicatives, pas un évaluateur agréé"
          : "Ka is an AI (Anthropic's Claude) — indicative estimates, not a chartered appraiser"}
      </p>
    </div>
  );
}
