// ---------------------------------------------------------------------------
// home/TemplatePicker.tsx — WAVE 23 item 7 (R10, contract C12, wiring W23-W6).
//
// A template is a bundle of SAVED VIEWS applied to a database you already have —
// it does not create a table. So the picker asks two questions in the order a
// person actually answers them: WHICH DATABASE, then WHICH TEMPLATE. The second
// list is a function of the first, because the server filters templates by the
// target's real columns (`view_templates.missing_columns`), and offering one the
// apply door would refuse is a control that lies.
//
// It renders inside the New-database dialog's "From a template" mode rather than
// as a third modal: one dialog, several doors (DESIGN.md §4 — new elements join
// the existing rhythm).
// ---------------------------------------------------------------------------

import { useEffect, useState } from "react";
import { API_V1, CREDENTIALS } from "../apiContract";
import type { DatabaseEntry } from "../shell/nav";

/** One row of `GET /templates?table=` — the server's vocabulary, read as strings (the wave-9
 *  law: no client union over a server list). */
interface TemplateRow {
  key: string;
  label: string;
  desc: string;
  source: string;
  views: number;
  alert: boolean;
}

type Load =
  | { phase: "idle" }
  | { phase: "loading" }
  | { phase: "ready"; rows: TemplateRow[] }
  | { phase: "error"; message: string };

function parseRows(body: unknown): TemplateRow[] {
  const raw = (body as { templates?: unknown } | null)?.templates;
  if (!Array.isArray(raw)) return [];
  const out: TemplateRow[] = [];
  for (const item of raw) {
    if (!item || typeof item !== "object") continue;
    const t = item as Record<string, unknown>;
    const key = typeof t.key === "string" ? t.key : "";
    const label = typeof t.label === "string" ? t.label : "";
    if (!key || !label) continue;
    out.push({
      key,
      label,
      desc: typeof t.desc === "string" ? t.desc : "",
      source: typeof t.source === "string" ? t.source : "",
      views: typeof t.views === "number" ? t.views : 0,
      alert: t.alert === true,
    });
  }
  return out;
}

export default function TemplatePicker({
  entries,
  onToast,
  onDone,
}: {
  /**
   * The databases this session may open — the shaped, server-filtered nav, minus the surfaces.
   *
   * ⭐ WAVE 25 (D-54) — the "minus Automation" in that sentence used to be a promise the CALLER
   * had to keep; it is now the type. `DatabaseEntry[]` can only come from `databaseEntries()`,
   * so a template can never be offered for application to a surface that has no table under it.
   */
  entries: DatabaseEntry[];
  onToast: (message: string) => void;
  /** Applied: close the dialog and open the database, where the new views now are. */
  onDone: (tableKey: string) => void;
}) {
  // Only real destinations: a group head is a folder label with no table behind it.
  const tables = entries.filter((e) => e.kind === "native");
  const [table, setTable] = useState(tables[0]?.key ?? "");
  const [load, setLoad] = useState<Load>({ phase: "idle" });
  const [busy, setBusy] = useState("");

  useEffect(() => {
    if (!table) {
      setLoad({ phase: "ready", rows: [] });
      return;
    }
    let dead = false;
    setLoad({ phase: "loading" });
    void (async () => {
      try {
        const res = await fetch(
          `${API_V1}/templates?table=${encodeURIComponent(table)}`,
          { credentials: CREDENTIALS }
        );
        const body = (await res.json().catch(() => null)) as
          | { error?: { message?: string } }
          | null;
        if (dead) return;
        if (!res.ok) {
          setLoad({
            phase: "error",
            message: body?.error?.message || `The server answered ${res.status}.`,
          });
          return;
        }
        setLoad({ phase: "ready", rows: parseRows(body) });
      } catch {
        if (!dead) setLoad({ phase: "error", message: "Cannot reach the server." });
      }
    })();
    return () => {
      dead = true;
    };
  }, [table]);

  const apply = (key: string) => {
    setBusy(key);
    void (async () => {
      try {
        const res = await fetch(
          `${API_V1}/templates/${encodeURIComponent(key)}/apply`,
          {
            method: "POST",
            credentials: CREDENTIALS,
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ table }),
          }
        );
        const body = (await res.json().catch(() => null)) as
          | { views?: { name?: string }[]; alerted?: boolean; error?: { message?: string } }
          | null;
        setBusy("");
        if (!res.ok) {
          // ⚠ THE SERVER'S SENTENCE, VERBATIM. A refusal here names the COLUMNS the target is
          // missing, which is the only thing that tells the reader they picked the wrong
          // database — replacing it with "could not apply" would throw that away.
          onToast(body?.error?.message || `The server answered ${res.status}.`);
          return;
        }
        const n = (body?.views ?? []).length;
        onToast(
          `${n} view${n === 1 ? "" : "s"} added.` +
            (body?.alerted ? " An alert is now watching the first one." : "")
        );
        onDone(table);
      } catch {
        setBusy("");
        onToast("Cannot reach the server.");
      }
    })();
  };

  // ⛔ A WORKSPACE WITH NO DATABASES IS NOT "NO TEMPLATE FITS". Without this branch the select
  // renders empty, `table` is "", the fetch is skipped, and the reader is told
  // "No template fits this database's columns." about a database they do not have — a false
  // sentence, on the FIRST thing a brand-new workspace can click (Home's "Start with templates"
  // card sits on exactly the empty state the tenant hero used to own). The distinction is the
  // same one the Database flyout already makes between a search that matched nothing and a
  // workspace that holds nothing; this branch is the third place it has to be made.
  if (tables.length === 0) {
    return (
      <p className="home-tpl-note">
        A template adds views to a database. Create one first — "Blank" above.
      </p>
    );
  }

  return (
    <div className="home-tpl">
      <label className="home-tpl-target">
        <span className="home-tpl-label">Apply to</span>
        {/* ⚠ A `<select>` MUST CARRY ITS VALUE. A select rendered without one shows its FIRST
            option while the state says something else — the wave-22 scar ([[cg-condition-builder-items]]);
            the control then reads as "customer_data" and applies to whatever `table` happens to
            hold. Bound, not defaulted. */}
        <select
          className="home-tpl-select"
          value={table}
          onChange={(e) => setTable(e.target.value)}
        >
          {tables.map((t) => (
            <option key={t.key} value={t.key}>
              {t.label}
            </option>
          ))}
        </select>
      </label>

      {load.phase === "loading" ? (
        <div className="home-tpl-note">
          <span className="lp-spin" role="status" aria-label="Loading" />
        </div>
      ) : null}
      {load.phase === "error" ? <p className="shell-newdb-err">{load.message}</p> : null}
      {load.phase === "ready" && load.rows.length === 0 ? (
        // ONE line (R13). And it says the RIGHT thing: nothing fits THIS database, which is not
        // the same as "there are no templates".
        <p className="home-tpl-note">No template fits this database's columns.</p>
      ) : null}

      {load.phase === "ready" && load.rows.length > 0 ? (
        <ul className="home-tpl-list">
          {load.rows.map((row) => (
            <li key={row.key} className="home-tpl-row">
              <span className="home-tpl-text">
                <span className="home-tpl-name">{row.label}</span>
                <span className="home-tpl-desc">{row.desc}</span>
              </span>
              <button
                type="button"
                className="login-submit home-tpl-apply"
                disabled={!!busy}
                onClick={() => apply(row.key)}
              >
                {busy === row.key ? "Adding…" : `Add ${row.views}`}
              </button>
            </li>
          ))}
        </ul>
      ) : null}
    </div>
  );
}
