const { useState, useEffect, useRef, useCallback } = React;

const API = (location.hostname === "localhost" || location.hostname === "127.0.0.1")
  ? "http://localhost:8787"
  : "https://api.kachina.app";

const NAV = [
  ["chat", "Explorer"],
  ["library", "Bibliothèque"],
  ["favorites", "Favoris"],
  ["activity", "Historique"],
  ["themes", "Thématiques"],
  ["keys", "Clés API"],
  ["connectors", "Connecteurs"],
];

const ICONS = {
  chat: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M8 12h8M12 8v8"/></svg>,
  library: <svg viewBox="0 0 24 24"><path d="M4 19V5h6v14H4zM14 19V8h6v11h-6z"/></svg>,
  favorites: <svg viewBox="0 0 24 24"><path d="M12 20s-7-4.4-7-10a4 4 0 0 1 7-2 4 4 0 0 1 7 2c0 5.6-7 10-7 10z"/></svg>,
  activity: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 8v5l3 2"/></svg>,
  themes: <svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>,
  keys: <svg viewBox="0 0 24 24"><circle cx="8" cy="12" r="3"/><path d="M11 12h9v3"/></svg>,
  connectors: <svg viewBox="0 0 24 24"><path d="M8 8h8v8H8zM4 12h4M16 12h4"/></svg>,
};

const SUGGESTS = [
  ["clock", "Synthétiser", "Résume ce que le cerveau sait déjà sur un sujet."],
  ["bulb", "Injecter un fait", "Range une info durable dans la bibliothèque."],
  ["image", "Lire un fichier", "Dépose une photo, un PDF ou un Word."],
];

const SUGGEST_ICONS = {
  clock: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 8v5l3 2"/></svg>,
  bulb: <svg viewBox="0 0 24 24"><path d="M9 18h6M10 21h4M8 10a4 4 0 1 1 8 0c0 2-1.2 3-2 4H10c-.8-1-2-2-2-4z"/></svg>,
  image: <svg viewBox="0 0 24 24"><rect x="4" y="5" width="16" height="14" rx="2"/><circle cx="9" cy="10" r="1.5"/><path d="M7 16l4-4 3 3 3-2 3 3"/></svg>,
};

function sessionToken() {
  return localStorage.getItem("kachina_token") || "";
}

async function api(path, opts = {}) {
  const headers = { ...(opts.headers || {}) };
  if (!(opts.body instanceof FormData)) headers["content-type"] = "application/json";
  const tok = sessionToken();
  if (tok) headers.authorization = "Bearer " + tok;
  const res = await fetch(API + path, {
    credentials: "include",
    headers,
    ...opts,
    body: opts.body && typeof opts.body !== "string" && !(opts.body instanceof FormData)
      ? JSON.stringify(opts.body)
      : opts.body,
  });
  const ct = res.headers.get("content-type") || "";
  const data = ct.includes("json") ? await res.json() : await res.text();
  if (!res.ok) throw Object.assign(new Error((data && data.error) || res.statusText), { data, status: res.status });
  return data;
}

function rel(d) {
  if (!d) return "—";
  const t = new Date(String(d).includes("T") ? d : d.replace(" ", "T") + "Z").getTime();
  const s = (Date.now() - t) / 1000;
  if (s < 60) return "à l’instant";
  if (s < 3600) return `il y a ${Math.floor(s / 60)} min`;
  if (s < 86400) return `il y a ${Math.floor(s / 3600)} h`;
  return `il y a ${Math.floor(s / 86400)} j`;
}

function Login({ onIn }) {
  const [password, setPassword] = useState("");
  const [err, setErr] = useState("");
  const [busy, setBusy] = useState(false);
  async function submit(e) {
    e.preventDefault();
    setBusy(true); setErr("");
    try {
      const data = await api("/v1/auth/login", { method: "POST", body: { password: password.trim() } });
      if (data.token) localStorage.setItem("kachina_token", data.token);
      onIn();
    } catch (ex) {
      setErr(ex && ex.status === 401 ? "Mot de passe incorrect." : "Impossible de joindre l’API. Réessaie.");
    } finally { setBusy(false); }
  }
  return (
    <div className="login">
      <form className="login-card" onSubmit={submit}>
        <img className="brand-mark" src="assets/img/mark.svg" alt="" />
        <h1>Kachina</h1>
        <p>Le cerveau central. Une mémoire, tous tes agents.</p>
        <label className="field">
          <span>Accès</span>
          <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} autoFocus />
        </label>
        {err && <div className="err">{err}</div>}
        <button className="btn" disabled={busy || !password}>{busy ? "Ouverture…" : "Entrer"}</button>
      </form>
    </div>
  );
}

function isDocFile(file) {
  const name = (file.name || "").toLowerCase();
  const type = (file.type || "").toLowerCase();
  return (
    name.endsWith(".pdf") || name.endsWith(".doc") || name.endsWith(".docx") || name.endsWith(".odt") ||
    name.endsWith(".txt") || name.endsWith(".md") ||
    type.includes("pdf") || type.includes("msword") || type.includes("wordprocessing") || type.includes("opendocument.text")
  );
}

function AskBox({ text, setText, busy, onSend, onPickFile, compact }) {
  const fileRef = useRef(null);
  return (
    <form className="ask" onSubmit={onSend}>
      <textarea
        value={text}
        placeholder="Ask me anything…"
        onChange={(e) => setText(e.target.value)}
        onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) onSend(e); }}
      />
      <div className="ask-tools">
        <button type="button" className="btn btn-lavender" onClick={() => fileRef.current && fileRef.current.click()}>Joindre un fichier</button>
        <button type="button" className="icon-btn" title="Joindre" onClick={() => fileRef.current && fileRef.current.click()}>
          <svg viewBox="0 0 24 24"><path d="M8 12l6-6a3 3 0 1 1 4 4l-8 8a4 4 0 0 1-6-6l8-8"/></svg>
        </button>
        <input
          ref={fileRef}
          type="file"
          accept="image/*,.pdf,.doc,.docx,.odt,.txt,.md,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
          hidden
          onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) onPickFile(f); e.target.value = ""; }}
        />
        <button className="mic" disabled={busy || !text.trim()} title="Envoyer">{busy ? "…" : "↑"}</button>
      </div>
      {!compact && (
        <div className="ask-foot">
          <span>+ Prompts sauvés</span>
          <button type="button" className="ask-attach" onClick={() => fileRef.current && fileRef.current.click()}>Joindre PDF / Word</button>
        </div>
      )}
    </form>
  );
}

function ChatView({ onDropReady, seed }) {
  const [messages, setMessages] = useState([]);
  const [text, setText] = useState(seed || "");
  const [cid, setCid] = useState("");
  const [busy, setBusy] = useState(false);
  const [vision, setVision] = useState(null);
  const box = useRef(null);
  const empty = messages.length === 0 && !vision;

  useEffect(() => { box.current && (box.current.scrollTop = box.current.scrollHeight); }, [messages]);

  const ingestFile = useCallback(async (file) => {
    const doc = isDocFile(file);
    const path = doc ? "/v1/ingest/document" : "/v1/ingest/image";
    setVision(doc ? "Lecture du document…" : "Lecture de l’image…");
    try {
      const fd = new FormData();
      fd.append("file", file);
      const tok = sessionToken();
      const res = await fetch(API + path, {
        method: "POST",
        credentials: "include",
        headers: tok ? { authorization: "Bearer " + tok } : {},
        body: fd,
      });
      const data = await res.json();
      if (!res.ok) {
        throw new Error(data.error === "unsupported_type" ? "Ce format n’est pas lisible. Envoie un PDF, un .docx ou une image." : (data.error || "fichier"));
      }
      const kind = doc ? "Document" : "Image";
      setMessages((m) => [...m, {
        role: "assistant",
        content: `${kind} rangé dans « ${data.memory.theme} » — ${data.memory.title}\n\n${data.memory.content}`,
      }]);
    } catch (ex) {
      setMessages((m) => [...m, { role: "assistant", content: "Je n’ai pas pu lire le fichier. " + ((ex && ex.message) || "") }]);
    } finally {
      setVision(null);
    }
  }, []);

  useEffect(() => { onDropReady(ingestFile); }, [ingestFile, onDropReady]);

  async function send(e) {
    e.preventDefault();
    const message = text.trim();
    if (!message || busy) return;
    setText("");
    setMessages((m) => [...m, { role: "user", content: message }]);
    setBusy(true);
    try {
      const data = await api("/v1/chat", { method: "POST", body: { message, conversation_id: cid || undefined } });
      setCid(data.conversation_id);
      setMessages((m) => [...m, { role: "assistant", content: data.reply }]);
    } catch (ex) {
      setMessages((m) => [...m, { role: "assistant", content: "Je n’ai pas pu répondre. " + (ex.message || "") }]);
    } finally { setBusy(false); }
  }

  return (
    <div className="chat">
      {empty ? (
        <div className="hero">
          <div className="hello">Bonjour, <em>Yannick</em></div>
          <h1>How can I assist you today?</h1>
          <AskBox text={text} setText={setText} busy={busy} onSend={send} onPickFile={ingestFile} />
          <div className="suggests">
            {SUGGESTS.map(([icon, title, desc]) => (
              <button key={title} className="suggest" type="button" onClick={() => setText(title + " — ")}>
                <i className="suggest-ico">{SUGGEST_ICONS[icon]}</i>
                <strong>{title}</strong>
                <span>{desc}</span>
              </button>
            ))}
          </div>
        </div>
      ) : (
        <>
          <div className="msgs" ref={box}>
            {messages.map((m, i) => (
              <div key={i} className={"bubble " + m.role}>
                <div className="who">{m.role === "user" ? "Toi" : "Kachina"}</div>
                {m.content}
              </div>
            ))}
            {vision && <div className="bubble assistant"><div className="who">Vision</div>{vision}</div>}
          </div>
          <div className="composer-wrap">
            <AskBox text={text} setText={setText} busy={busy} onSend={send} onPickFile={ingestFile} compact />
          </div>
        </>
      )}
    </div>
  );
}

function Heart({ on, onClick }) {
  return (
    <button className={"heart" + (on ? " on" : "")} type="button" title={on ? "Retirer des favoris" : "Mettre en favori"} onClick={onClick} aria-label="Favori">
      {on ? "♥" : "♡"}
    </button>
  );
}

function LibraryView({ favoritesOnly }) {
  const [items, setItems] = useState([]);
  const [q, setQ] = useState("");
  const [theme, setTheme] = useState("");
  async function load() {
    const qs = new URLSearchParams();
    if (q) qs.set("q", q);
    if (theme) qs.set("theme", theme);
    if (favoritesOnly) qs.set("favorite", "1");
    const data = await api("/v1/memories?" + qs.toString());
    setItems(data.items || []);
  }
  useEffect(() => { load().catch(() => {}); }, [theme, favoritesOnly]);
  async function toggleFav(m) {
    const data = await api("/v1/memories/" + m.id + "/favorite", { method: "POST", body: { favorite: !m.favorite } });
    setItems((cur) => {
      const next = cur.map((x) => (x.id === m.id ? data.memory : x));
      return favoritesOnly ? next.filter((x) => x.favorite) : next;
    });
  }
  return (
    <div className="grid">
      <div className="row">
        <input placeholder={favoritesOnly ? "Chercher dans les favoris" : "Chercher dans la bibliothèque"} value={q} onChange={(e) => setQ(e.target.value)} onKeyDown={(e) => e.key === "Enter" && load()} style={{ flex: 1, background: "var(--side)", border: "1px solid var(--line)", borderRadius: 12, padding: "0.7rem 0.9rem" }} />
        <button className="btn" onClick={load}>Chercher</button>
      </div>
      <div className="cards">
        {items.map((m) => (
          <article key={m.id} className="card">
            <div className="card-top">
              <div className="theme-bar" style={{ flex: 1 }} />
              <Heart on={!!m.favorite} onClick={() => toggleFav(m)} />
            </div>
            <div className="muted">{m.theme} · {m.kind}</div>
            <h3>{m.title}</h3>
            <p>{m.content.slice(0, 180)}{m.content.length > 180 ? "…" : ""}</p>
            <div className="row">
              <small className="muted">{rel(m.updated_at)}</small>
              <button className="btn btn-ghost" onClick={async () => { if (confirm("Oublier cette fiche ?")) { await api("/v1/memories/" + m.id, { method: "DELETE" }); load(); } }}>Oublier</button>
            </div>
          </article>
        ))}
        {!items.length && <p className="muted">{favoritesOnly ? "Aucun favori pour l’instant. Clique le cœur sur une fiche." : "La bibliothèque est vide. Parle au cerveau ou dépose une image."}</p>}
      </div>
    </div>
  );
}

function ThemesView({ themes, onOpen }) {
  return (
    <div className="grid">
      <div className="cards">
        {(themes || []).map((t) => (
          <article key={t.slug} className="card" onClick={() => onOpen(t.slug)} style={{ cursor: "pointer" }}>
            <div className="theme-bar" style={{ background: t.color }} />
            <h3>{t.name}</h3>
            <p>{t.description}</p>
            <div className="row">
              <strong>{t.count} fiche{t.count > 1 ? "s" : ""}</strong>
              <small className="muted">{t.last_at ? rel(t.last_at) : "vierge"}</small>
            </div>
          </article>
        ))}
      </div>
    </div>
  );
}

function KeysView() {
  const [items, setItems] = useState([]);
  const [name, setName] = useState("");
  const [hint, setHint] = useState("");
  const [fresh, setFresh] = useState(null);
  async function load() { setItems((await api("/v1/keys")).items || []); }
  useEffect(() => { load().catch(() => {}); }, []);
  return (
    <div className="grid">
      <p className="muted">Une clé par agent. Chaque connexion est journalisée. Donne la même mémoire à Grok, Claude, ChatGPT, Cursor.</p>
      <div className="form-grid">
        <label className="field"><span>Nom</span><input value={name} onChange={(e) => setName(e.target.value)} placeholder="Claude bureau" /></label>
        <label className="field"><span>Agent</span><input value={hint} onChange={(e) => setHint(e.target.value)} placeholder="Claude" /></label>
      </div>
      <button className="btn" onClick={async () => {
        const data = await api("/v1/keys", { method: "POST", body: { name, agent_hint: hint || name } });
        setFresh(data.key.token); setName(""); setHint(""); load();
      }}>Créer une clé</button>
      {fresh && <div className="secret">Copie-la maintenant, elle ne réapparaîtra plus.<br />{fresh}</div>}
      <table className="table">
        <thead><tr><th>Nom</th><th>Préfixe</th><th>Dernier agent</th><th>Vu</th><th></th></tr></thead>
        <tbody>
          {items.map((k) => (
            <tr key={k.id}>
              <td>{k.name}</td>
              <td>{k.prefix}…</td>
              <td>{k.last_agent || "—"}</td>
              <td>{rel(k.last_used_at)}</td>
              <td><button className="btn btn-ghost" onClick={async () => { await api("/v1/keys/" + k.id, { method: "DELETE" }); load(); }}>Révoquer</button></td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function ConnectorsView() {
  const [items, setItems] = useState([]);
  const [form, setForm] = useState({ name: "", kind: "http", business: "", url: "", secret: "" });
  async function load() { setItems((await api("/v1/connectors")).items || []); }
  useEffect(() => { load().catch(() => {}); }, []);
  const set = (k) => (e) => setForm({ ...form, [k]: e.target.value });
  return (
    <div className="grid">
      <p className="muted">Branche tes MCP et APIs métier. Le cerveau sait qui fait quoi, et quand le connecteur a répondu.</p>
      <div className="form-grid">
        <label className="field"><span>Nom</span><input value={form.name} onChange={set("name")} placeholder="Restaurateur API" /></label>
        <label className="field"><span>Business</span><input value={form.business} onChange={set("business")} placeholder="Restaurateur" /></label>
        <label className="field"><span>Type</span>
          <select value={form.kind} onChange={set("kind")}><option value="http">API HTTP</option><option value="mcp">MCP</option></select>
        </label>
        <label className="field"><span>URL</span><input value={form.url} onChange={set("url")} placeholder="https://…" /></label>
      </div>
      <label className="field"><span>Secret (optionnel)</span><input value={form.secret} onChange={set("secret")} placeholder="Bearer / clé" /></label>
      <button className="btn" onClick={async () => { await api("/v1/connectors", { method: "POST", body: form }); setForm({ name: "", kind: "http", business: "", url: "", secret: "" }); load(); }}>Ajouter</button>
      <table className="table">
        <thead><tr><th>Nom</th><th>Business</th><th>Type</th><th>État</th><th>Dernier contact</th><th></th></tr></thead>
        <tbody>
          {items.map((x) => (
            <tr key={x.id}>
              <td>{x.name}</td>
              <td>{x.business || "—"}</td>
              <td>{x.kind}</td>
              <td>{x.status}{x.last_error ? " · " + x.last_error.slice(0, 40) : ""}</td>
              <td>{rel(x.last_sync_at)}</td>
              <td className="row">
                <button className="btn btn-ghost" onClick={async () => { await api("/v1/connectors/" + x.id + "/ping", { method: "POST" }); load(); }}>Ping</button>
                <button className="btn btn-ghost" onClick={async () => { await api("/v1/connectors/" + x.id, { method: "DELETE" }); load(); }}>Retirer</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function ActivityView() {
  const [agents, setAgents] = useState({ items: [], summary: [] });
  const [acts, setActs] = useState([]);
  useEffect(() => {
    api("/v1/agents").then(setAgents).catch(() => {});
    api("/v1/activity").then((d) => setActs(d.items || [])).catch(() => {});
  }, []);
  return (
    <div className="grid">
      <div className="cards">
        {(agents.summary || []).map((s) => (
          <article key={s.agent_name} className="card">
            <h3>{s.agent_name}</h3>
            <p>{s.n} connexion{s.n > 1 ? "s" : ""} · dernière {rel(s.last_at)}</p>
          </article>
        ))}
      </div>
      <table className="table">
        <thead><tr><th>Quand</th><th>Qui</th><th>Agent</th><th>Action</th><th>Ressource</th></tr></thead>
        <tbody>
          {acts.map((a) => (
            <tr key={a.id}>
              <td>{rel(a.created_at)}</td>
              <td>{a.actor}</td>
              <td>{a.agent || "—"}</td>
              <td>{a.action}</td>
              <td>{a.resource || "—"}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

const LAUNCH_GAMES = ["pairs", "sequence", "hunt", "odd", "rain"];

function pickLaunchGame() {
  return LAUNCH_GAMES[Math.floor(Math.random() * LAUNCH_GAMES.length)];
}

function GameIco({ kind, lit, dim }) {
  return (
    <span className={"g-ico" + (lit ? " lit" : "") + (dim ? " dim" : "")} aria-hidden>
      {kind === "baguette" ? "🥖" : "🍣"}
    </span>
  );
}

function GamePairs({ onWin }) {
  const deck = useRef(null);
  if (!deck.current) {
    const base = [
      ["s1", "sushi"], ["s1", "sushi"],
      ["s2", "sushi"], ["s2", "sushi"],
      ["b1", "baguette"], ["b1", "baguette"],
    ];
    deck.current = base.map(([pair, kind], i) => ({ id: i, pair, kind })).sort(() => Math.random() - 0.5);
  }
  const [open, setOpen] = useState([]);
  const [done, setDone] = useState([]);
  const lock = useRef(false);
  function flip(id) {
    if (lock.current || done.includes(id) || open.includes(id)) return;
    const next = [...open, id];
    setOpen(next);
    if (next.length < 2) return;
    const [a, b] = next.map((i) => deck.current.find((c) => c.id === i));
    lock.current = true;
    setTimeout(() => {
      if (a.pair === b.pair) {
        const now = [...done, a.id, b.id];
        setDone(now);
        if (now.length >= 6) onWin();
      }
      setOpen([]);
      lock.current = false;
    }, 420);
  }
  return (
    <div className="g-grid g-grid-3">
      {deck.current.map((c) => {
        const show = open.includes(c.id) || done.includes(c.id);
        return (
          <button key={c.id} type="button" className={"g-tile" + (done.includes(c.id) ? " ok" : "")} onClick={() => flip(c.id)}>
            {show ? <GameIco kind={c.kind} /> : <span className="g-back">?</span>}
          </button>
        );
      })}
    </div>
  );
}

function GameSequence({ onWin }) {
  const seq = useRef(null);
  if (!seq.current) seq.current = Array.from({ length: 4 }, () => (Math.random() > 0.5 ? "sushi" : "baguette"));
  const [phase, setPhase] = useState("show");
  const [lit, setLit] = useState(-1);
  const [step, setStep] = useState(0);
  const [round, setRound] = useState(0);
  useEffect(() => {
    setPhase("show");
    setLit(-1);
    let i = 0;
    const t = setInterval(() => {
      setLit(i);
      i += 1;
      if (i > seq.current.length) {
        clearInterval(t);
        setLit(-1);
        setPhase("play");
      }
    }, 620);
    return () => clearInterval(t);
  }, [round]);
  function tap(kind) {
    if (phase !== "play") return;
    if (seq.current[step] !== kind) {
      setStep(0);
      setRound((r) => r + 1);
      return;
    }
    const n = step + 1;
    if (n >= seq.current.length) onWin();
    else setStep(n);
  }
  return (
    <div>
      <div className="g-seq">
        {seq.current.map((kind, i) => <GameIco key={i} kind={kind} lit={lit === i} dim={phase === "play"} />)}
      </div>
      <div className="g-row">
        <button type="button" className="g-tile big" disabled={phase !== "play"} onClick={() => tap("sushi")}><GameIco kind="sushi" /></button>
        <button type="button" className="g-tile big" disabled={phase !== "play"} onClick={() => tap("baguette")}><GameIco kind="baguette" /></button>
      </div>
    </div>
  );
}

function GameHunt({ onWin }) {
  const cells = useRef(null);
  if (!cells.current) {
    cells.current = Array.from({ length: 9 }, (_, i) => ({ id: i, kind: i < 4 ? "sushi" : "baguette" })).sort(() => Math.random() - 0.5);
  }
  const [caught, setCaught] = useState([]);
  const need = cells.current.filter((c) => c.kind === "sushi").length;
  function tap(c) {
    if (c.kind !== "sushi" || caught.includes(c.id)) return;
    const now = [...caught, c.id];
    setCaught(now);
    if (now.length >= need) onWin();
  }
  return (
    <div className="g-grid g-grid-3">
      {cells.current.map((c) => (
        <button key={c.id} type="button" className={"g-tile" + (caught.includes(c.id) ? " ok" : "")} onClick={() => tap(c)}>
          <GameIco kind={c.kind} dim={caught.includes(c.id)} />
        </button>
      ))}
    </div>
  );
}

function GameOdd({ onWin }) {
  const board = useRef(null);
  if (!board.current) {
    const majority = Math.random() > 0.5 ? "sushi" : "baguette";
    const odd = majority === "sushi" ? "baguette" : "sushi";
    const oddAt = Math.floor(Math.random() * 9);
    board.current = { oddAt, cells: Array.from({ length: 9 }, (_, i) => (i === oddAt ? odd : majority)) };
  }
  const [miss, setMiss] = useState(false);
  return (
    <div className="g-grid g-grid-3">
      {board.current.cells.map((kind, i) => (
        <button key={i} type="button" className={"g-tile" + (miss && i !== board.current.oddAt ? " shake" : "")} onClick={() => {
          if (i === board.current.oddAt) onWin();
          else { setMiss(true); setTimeout(() => setMiss(false), 280); }
        }}>
          <GameIco kind={kind} />
        </button>
      ))}
    </div>
  );
}

function GameRain({ onWin }) {
  const [score, setScore] = useState(0);
  const [item, setItem] = useState(() => (Math.random() > 0.45 ? "sushi" : "baguette"));
  const [key, setKey] = useState(0);
  function next() {
    setItem(Math.random() > 0.45 ? "sushi" : "baguette");
    setKey((k) => k + 1);
  }
  function tap() {
    if (item === "sushi") {
      const n = score + 1;
      setScore(n);
      if (n >= 5) { onWin(); return; }
    } else {
      setScore((s) => Math.max(0, s - 1));
    }
    next();
  }
  return (
    <div className="g-rain">
      <p className="g-score">{score} / 5 sushis</p>
      <button type="button" key={key} className="g-tile rain" onClick={tap}>
        <GameIco kind={item} />
      </button>
    </div>
  );
}

const GAME_META = {
  pairs: { title: "Paires", hint: "Retourne les paires sushi / baguette." },
  sequence: { title: "Séquence", hint: "Rejoue l’ordre après le flash." },
  hunt: { title: "Chasse", hint: "Touche tous les sushis. Pas les baguettes." },
  odd: { title: "L’intrus", hint: "Trouve l’icône qui n’est pas comme les autres." },
  rain: { title: "Pluie", hint: "Tape uniquement les sushis. 5 pour gagner." },
};

const GAME_VIEWS = {
  pairs: GamePairs,
  sequence: GameSequence,
  hunt: GameHunt,
  odd: GameOdd,
  rain: GameRain,
};

function LaunchGame({ onDone }) {
  const [kind] = useState(pickLaunchGame);
  const [won, setWon] = useState(false);
  const View = GAME_VIEWS[kind];
  const meta = GAME_META[kind];
  function win() {
    if (won) return;
    setWon(true);
    setTimeout(onDone, 850);
  }
  return (
    <div className="launch-game">
      <div className="launch-game-card">
        <p className="launch-game-kicker">Mini-jeu du lancement</p>
        <h2>{won ? "Bien joué 🍣" : meta.title}</h2>
        <p className="muted">{won ? "Le cerveau s’ouvre." : meta.hint}</p>
        {!won && <View onWin={win} />}
        <button type="button" className="btn btn-ghost launch-skip" onClick={onDone}>Passer</button>
      </div>
    </div>
  );
}

function App() {
  const [ready, setReady] = useState(false);
  const [me, setMe] = useState(null);
  const [play, setPlay] = useState(true);
  const [page, setPage] = useState("chat");
  const [themes, setThemes] = useState([]);
  const [convs, setConvs] = useState([]);
  const [q, setQ] = useState("");
  const [chatKey, setChatKey] = useState(0);
  const [drop, setDrop] = useState(false);
  const ingestRef = useRef(null);

  const boot = useCallback(async () => {
    try {
      const data = await api("/v1/auth/me");
      setMe(data.actor);
      const lib = await api("/v1/library");
      setThemes(lib.themes || []);
      const hist = await api("/v1/conversations").catch(() => ({ items: [] }));
      setConvs(hist.items || []);
    } catch { setMe(null); }
    setReady(true);
  }, []);

  useEffect(() => { boot(); }, [boot]);

  useEffect(() => {
    function over(e) { if (e.dataTransfer?.types?.includes("Files")) { e.preventDefault(); setDrop(true); } }
    function leave(e) { if (!e.relatedTarget) setDrop(false); }
    function dropFile(e) {
      e.preventDefault(); setDrop(false);
      const file = e.dataTransfer.files?.[0];
      if (file && ingestRef.current && (file.type.startsWith("image/") || isDocFile(file))) ingestRef.current(file);
    }
    window.addEventListener("dragover", over);
    window.addEventListener("dragleave", leave);
    window.addEventListener("drop", dropFile);
    return () => {
      window.removeEventListener("dragover", over);
      window.removeEventListener("dragleave", leave);
      window.removeEventListener("drop", dropFile);
    };
  }, []);

  if (play) return <LaunchGame onDone={() => setPlay(false)} />;
  if (!ready) return <div className="login"><p className="muted">Ouverture du cerveau…</p></div>;
  if (!me) return <Login onIn={boot} />;

  const filtered = (themes || []).filter((t) => !q || t.name.toLowerCase().includes(q.toLowerCase()));

  return (
    <div className="app-frame">
      <div className="shell">
        <aside className="side">
          <div className="side-brand">
            <img className="brand-mark" src="assets/img/mark.svg" alt="" />
            <strong>Kachina</strong>
          </div>
          <button className="btn btn-new" onClick={() => { setPage("chat"); setChatKey((k) => k + 1); }}>+ New chat</button>
          <label className="search">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
            <input placeholder="Search" value={q} onChange={(e) => setQ(e.target.value)} />
            <kbd>⌘K</kbd>
          </label>
          <nav className="nav">
            {NAV.map(([id, label]) => (
              <button key={id} className={page === id ? "active" : ""} onClick={() => setPage(id)}>
                {ICONS[id]} {label}
              </button>
            ))}
          </nav>
          <div className="hist">
            <div className="hist-label">Thématiques</div>
            {filtered.map((t) => (
              <button key={t.slug} onClick={() => setPage("library")}>{t.name} · {t.count}</button>
            ))}
            {convs.length > 0 && <div className="hist-label">Aujourd’hui</div>}
            {convs.slice(0, 8).map((c) => (
              <button key={c.id} onClick={() => { setPage("chat"); setChatKey((k) => k + 1); }}>{c.title || "Sans titre"}</button>
            ))}
          </div>
          <div className="user-card">
            <div className="avatar" />
            <div>
              <strong>Yannick</strong>
              <small>Cerveau central</small>
            </div>
            <button className="out" title="Sortir" onClick={async () => { await api("/v1/auth/logout", { method: "POST" }); localStorage.removeItem("kachina_token"); setMe(null); }}>⎋</button>
          </div>
        </aside>
        <section className="main">
          <header className="top">
            <div className="brand-mini">
              <img src="assets/img/mark.svg" alt="" />
              {NAV.find((n) => n[0] === page)?.[1] || "Kachina"}
            </div>
            <div className="top-actions">
              <span className="chip">{me.agent === "owner" ? "Toi" : me.agent}</span>
              <a className="chip" href="https://app-kachina-docs.pages.dev" target="_blank" rel="noreferrer">Docs</a>
            </div>
          </header>
          {page === "chat" && <ChatView key={chatKey} onDropReady={(fn) => { ingestRef.current = fn; }} />}
          {page === "library" && <LibraryView />}
          {page === "favorites" && <LibraryView favoritesOnly />}
          {page === "themes" && <ThemesView themes={themes} onOpen={() => setPage("library")} />}
          {page === "keys" && <KeysView />}
          {page === "connectors" && <ConnectorsView />}
          {page === "activity" && <ActivityView />}
        </section>
      </div>
      {drop && <div className="drop"><div className="drop-card"><h2>Lâche le fichier</h2><p className="muted">Image, PDF ou Word : je le lis et j’en tire les faits utiles.</p></div></div>}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("app")).render(<App />);
