"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { ApiError, get, post, put } from "@/lib/api";
import { Badge, Field, Notice, Spinner, Tabs } from "@/components/ui";
import { DataGrid, type ColDef } from "@/components/DataGrid";

type RuleSetVersion = {
  id: number;
  version: number;
  active: boolean;
  payload: any;
  createdBy: string;
  note: string | null;
  createdAt: string;
};
type RulesResponse = {
  rules: Record<string, { active: any; activeVersion: string; versions: RuleSetVersion[] }>;
  types: string[];
};

const TYPE_LABELS: Record<string, string> = {
  attribute: "Attribute manipulation",
  image_manipulation: "Image manipulation",
  image_creation: "Image creation",
  image_specs: "Image specs",
  tone_of_voice: "Tone of voice",
};

export default function RulesPanel() {
  const [data, setData] = useState<RulesResponse | null>(null);
  const [error, setError] = useState<string | null>(null);

  async function load() {
    try {
      setData(await get<RulesResponse>("rules"));
    } catch (e) {
      setError((e as Error).message);
    }
  }
  useEffect(() => {
    load();
  }, []);

  if (error) return <Notice kind="error">Could not load rules: {error}</Notice>;
  if (!data) return <Spinner label="Loading manipulation rules…" />;
  const rules = data;

  return (
    <>
      <Tabs
        variant="pill"
        tabs={rules.types.map((t) => ({
          id: t,
          label: TYPE_LABELS[t] || t,
          render: () => {
            const current = rules.rules[t];
            return (
              <>
                {t === "attribute" ? (
                  <AttributeManipulationEditor payload={current.active} onSaved={load} />
                ) : t === "image_specs" ? (
                  <ImageSpecsEditor payload={current.active} onSaved={load} />
                ) : (
                  <JsonEditor type={t} payload={current.active} onSaved={load} />
                )}
                <VersionHistory type={t} versions={current.versions} activeVersion={current.activeVersion} onChanged={load} />
              </>
            );
          },
        }))}
      />
    </>
  );
}

function JsonEditor({ type, payload, onSaved }: { type: string; payload: any; onSaved: () => void }) {
  const [text, setText] = useState(JSON.stringify(payload, null, 2));
  const [note, setNote] = useState("");
  const [msg, setMsg] = useState<string | null>(null);
  const [errs, setErrs] = useState<string[]>([]);
  const [busy, setBusy] = useState(false);

  useEffect(() => setText(JSON.stringify(payload, null, 2)), [payload]);

  async function save() {
    setBusy(true);
    setMsg(null);
    setErrs([]);
    let parsed: any;
    try {
      parsed = JSON.parse(text);
    } catch {
      setErrs(["Invalid JSON."]);
      setBusy(false);
      return;
    }
    try {
      await put(`rules/${type}`, { payload: parsed, note });
      setMsg("Saved as a new active version.");
      onSaved();
    } catch (e) {
      setErrs(e instanceof ApiError && e.errors.length ? e.errors : [(e as Error).message]);
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="card">
      {msg && <div className="mb-3"><Notice kind="success">{msg}</Notice></div>}
      {errs.length > 0 && <div className="mb-3"><Notice kind="error"><ul className="list-disc pl-5">{errs.map((e, i) => <li key={i}>{e}</li>)}</ul></Notice></div>}
      <Field label="Ruleset payload (JSON)" htmlFor="rule-json">
        <textarea id="rule-json" rows={16} value={text} onChange={(e) => setText(e.target.value)} className="input font-mono text-xs" />
      </Field>
      <div className="mt-3 flex items-end gap-3">
        <div className="flex-1">
          <Field label="Version note (optional)" htmlFor="rule-note">
            <input id="rule-note" value={note} onChange={(e) => setNote(e.target.value)} className="input" />
          </Field>
        </div>
        <button className="btn-primary" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save new version"}</button>
      </div>
    </div>
  );
}

type AttrRow = { code: string; label: string; include: boolean; rule: string };

/**
 * Unified Attribute manipulation: one versioned config merging the in-research
 * switch (per attribute) with a verbalized per-attribute rule, plus a general
 * (cross-attribute) rules block. Edit inline; all changes save as ONE new version.
 */
function AttributeManipulationEditor({ payload, onSaved }: { payload: any; onSaved: () => void }) {
  const [rows, setRows] = useState<AttrRow[] | null>(null);
  const [general, setGeneral] = useState<Record<string, any>>({});
  const [note, setNote] = useState("");
  const [msg, setMsg] = useState<string | null>(null);
  const [errs, setErrs] = useState<string[]>([]);
  const [busy, setBusy] = useState(false);
  const rowsRef = useRef<AttrRow[] | null>(null);
  rowsRef.current = rows;

  useEffect(() => {
    get<{ attributes: { code: string; label: string; includeInResearch: boolean; rule?: string }[] }>("attributes")
      .then((d) => setRows(d.attributes.map((a) => ({ code: a.code, label: a.label, include: a.includeInResearch !== false, rule: a.rule ?? "" }))))
      .catch((e) => setErrs([(e as Error).message]));
    setGeneral({ ...(payload?.general ?? {}) });
  }, [payload]);

  const columnDefs = useMemo<ColDef[]>(
    () => [
      { field: "label", headerName: "Attribute", flex: 1, minWidth: 170, editable: false, cellClass: "flex items-center font-medium text-slate-800" },
      { field: "code", headerName: "Code", width: 170, editable: false, cellClass: "flex items-center font-mono text-[11px] text-slate-400" },
      { field: "include", headerName: "In research", width: 120, editable: true, cellDataType: "boolean" },
      {
        field: "rule",
        headerName: "Custom rule (verbalized)",
        flex: 2,
        minWidth: 280,
        editable: true,
        cellEditor: "agLargeTextCellEditor",
        cellEditorPopup: true,
        cellEditorParams: { rows: 8, maxLength: 4000 },
        wrapText: true,
        autoHeight: true,
        cellClass: "whitespace-pre-wrap py-1 text-xs leading-snug text-slate-600",
        valueFormatter: (p: any) => p.value || "—",
      },
    ],
    [],
  );

  async function save() {
    const current = rowsRef.current;
    if (!current) return;
    setBusy(true);
    setMsg(null);
    setErrs([]);
    const attributes: Record<string, { include: boolean; rule: string }> = {};
    for (const r of current) attributes[r.code] = { include: !!r.include, rule: r.rule ?? "" };
    try {
      await put("rules/attribute", { payload: { attributes, general }, note });
      setNote("");
      setMsg("Saved as a new active version.");
      onSaved();
    } catch (e) {
      setErrs(e instanceof ApiError && e.errors.length ? e.errors : [(e as Error).message]);
    } finally {
      setBusy(false);
    }
  }

  if (errs.length > 0 && !rows) return <Notice kind="error">{errs.join(" ")}</Notice>;
  if (!rows) return <Spinner label="Loading attributes…" />;

  const GENERAL_FIELDS: [string, string][] = [
    ["units", "Units"],
    ["casing", "Casing"],
    ["currency_formatting", "Currency formatting"],
    ["category_mapping", "Category mapping"],
  ];

  return (
    <div className="space-y-6">
      {msg && <Notice kind="success">{msg}</Notice>}
      {errs.length > 0 && <Notice kind="error"><ul className="list-disc pl-5">{errs.map((e, i) => <li key={i}>{e}</li>)}</ul></Notice>}

      <div className="card">
        <p className="mb-3 text-sm text-slate-600">
          Per attribute, toggle <strong>In research</strong> and add a <strong>verbalized rule</strong> (double-click the
          rule cell for a larger editor). Edits across attributes and the general rules below are stored together as
          <strong> one new version</strong>.
        </p>
        <DataGrid
          rowData={rows}
          columnDefs={columnDefs}
          autoHeight
          getRowId={(p: any) => String(p.data.code)}
          stopEditingWhenCellsLoseFocus
        />
      </div>

      <div className="card space-y-3">
        <h3 className="font-semibold text-slate-900">General rules <span className="font-normal text-slate-500">(cross-attribute)</span></h3>
        <Field label="General (free text)" htmlFor="gen-additional" hint="Free-form general rules applied to every attribute, beside the specific ones below.">
          <textarea
            id="gen-additional"
            rows={4}
            className="input text-sm"
            placeholder="e.g. Always prefer Swiss-German spelling; never invent specifications that aren't in a source…"
            value={general.additional_rules ?? ""}
            onChange={(e) => setGeneral((g) => ({ ...g, additional_rules: e.target.value }))}
          />
        </Field>
        {GENERAL_FIELDS.map(([key, label]) => (
          <Field key={key} label={label} htmlFor={`gen-${key}`}>
            <textarea id={`gen-${key}`} rows={2} className="input text-sm" value={general[key] ?? ""} onChange={(e) => setGeneral((g) => ({ ...g, [key]: e.target.value }))} />
          </Field>
        ))}
        <Field label="Forbidden phrases (comma-separated)" htmlFor="gen-fp">
          <input
            id="gen-fp"
            className="input text-sm"
            value={(general.forbidden_phrases ?? []).join(", ")}
            onChange={(e) => setGeneral((g) => ({ ...g, forbidden_phrases: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) }))}
          />
        </Field>
      </div>

      <div className="card flex flex-wrap items-end gap-3">
        <div className="flex-1">
          <Field label="Version note (optional)" htmlFor="attr-note">
            <input id="attr-note" value={note} onChange={(e) => setNote(e.target.value)} className="input" />
          </Field>
        </div>
        <button className="btn-primary" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save new version"}</button>
      </div>
    </div>
  );
}

function ImageSpecsEditor({ payload, onSaved }: { payload: any; onSaved: () => void }) {
  const [spec, setSpec] = useState<any>(payload);
  const [overlays, setOverlays] = useState<string[]>(payload.forbidden_overlays || []);
  const [newOverlay, setNewOverlay] = useState("");
  const [msg, setMsg] = useState<string | null>(null);
  const [errs, setErrs] = useState<string[]>([]);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    setSpec(payload);
    setOverlays(payload.forbidden_overlays || []);
  }, [payload]);

  function setPath(path: string[], value: any) {
    setSpec((prev: any) => {
      const next = structuredClone(prev);
      let node = next;
      for (let i = 0; i < path.length - 1; i++) node = node[path[i]] ??= {};
      node[path[path.length - 1]] = value;
      return next;
    });
  }

  const previewName = useMemo(() => {
    const pat = spec.file_naming_pattern || "";
    return pat
      .replaceAll("{sku}", "DAN_000004354")
      .replaceAll("{productId}", "12345")
      .replaceAll("{productSKU}", "SKU-12345")
      .replaceAll("{productGTIN}", "4012345678901")
      .replaceAll("{position}", "1")
      .replaceAll("{type}", "hero")
      .replaceAll("{ISO8601Timestamp}", "2026-06-05T12-00-00+00-00")
      .replaceAll("{ext}", spec.encoding?.format || "webp");
  }, [spec]);

  async function save() {
    setBusy(true);
    setMsg(null);
    setErrs([]);
    const payloadToSave = { ...spec, forbidden_overlays: overlays };
    try {
      await put("rules/image_specs", { payload: payloadToSave, note: "Image specs update" });
      setMsg("Image specs saved as a new active version.");
      onSaved();
    } catch (e) {
      setErrs(e instanceof ApiError && e.errors.length ? e.errors : [(e as Error).message]);
    } finally {
      setBusy(false);
    }
  }

  const num = (path: string[], v: any) => setPath(path, v === "" ? "" : Number(v));

  return (
    <div className="card space-y-6">
      {msg && <Notice kind="success">{msg}</Notice>}
      {errs.length > 0 && <Notice kind="error"><ul className="list-disc pl-5">{errs.map((e, i) => <li key={i}>{e}</li>)}</ul></Notice>}

      <fieldset className="grid gap-4 sm:grid-cols-4">
        <legend className="mb-1 text-sm font-semibold text-slate-900">Product canvas</legend>
        <Field label="Aspect ratio" htmlFor="pc-ar"><input id="pc-ar" className="input" value={spec.product_canvas?.aspect_ratio ?? ""} onChange={(e) => setPath(["product_canvas", "aspect_ratio"], e.target.value)} /></Field>
        <Field label="Width (px)" htmlFor="pc-w"><input id="pc-w" type="number" className="input" value={spec.product_canvas?.width ?? ""} onChange={(e) => num(["product_canvas", "width"], e.target.value)} /></Field>
        <Field label="Height (px)" htmlFor="pc-h"><input id="pc-h" type="number" className="input" value={spec.product_canvas?.height ?? ""} onChange={(e) => num(["product_canvas", "height"], e.target.value)} /></Field>
        <Field label="Padding (%)" htmlFor="pc-p" hint="0–50"><input id="pc-p" type="number" className="input" value={spec.product_canvas?.padding_percent ?? ""} onChange={(e) => num(["product_canvas", "padding_percent"], e.target.value)} /></Field>
      </fieldset>

      <fieldset className="grid gap-4 sm:grid-cols-4">
        <legend className="mb-1 text-sm font-semibold text-slate-900">Mood canvas</legend>
        <Field label="Aspect ratio" htmlFor="mc-ar"><input id="mc-ar" className="input" value={spec.mood_canvas?.aspect_ratio ?? ""} onChange={(e) => setPath(["mood_canvas", "aspect_ratio"], e.target.value)} /></Field>
        <Field label="Width (px)" htmlFor="mc-w"><input id="mc-w" type="number" className="input" value={spec.mood_canvas?.width ?? ""} onChange={(e) => num(["mood_canvas", "width"], e.target.value)} /></Field>
        <Field label="Height (px)" htmlFor="mc-h"><input id="mc-h" type="number" className="input" value={spec.mood_canvas?.height ?? ""} onChange={(e) => num(["mood_canvas", "height"], e.target.value)} /></Field>
      </fieldset>

      <fieldset className="grid gap-4 sm:grid-cols-3">
        <legend className="mb-1 text-sm font-semibold text-slate-900">Image counts (min / max)</legend>
        {["hero", "additional", "mood"].map((cat) => (
          <div key={cat} className="flex items-end gap-2">
            <Field label={`${cat} min`} htmlFor={`${cat}-min`}><input id={`${cat}-min`} type="number" min="0" className="input" value={spec.counts?.[cat]?.min ?? ""} onChange={(e) => num(["counts", cat, "min"], e.target.value)} /></Field>
            <Field label={`${cat} max`} htmlFor={`${cat}-max`}><input id={`${cat}-max`} type="number" min="0" className="input" value={spec.counts?.[cat]?.max ?? ""} onChange={(e) => num(["counts", cat, "max"], e.target.value)} /></Field>
          </div>
        ))}
      </fieldset>

      <fieldset className="grid gap-4 sm:grid-cols-4">
        <legend className="mb-1 text-sm font-semibold text-slate-900">Encoding &amp; file-size guard</legend>
        <Field label="Format" htmlFor="enc-f"><input id="enc-f" className="input" value={spec.encoding?.format ?? ""} onChange={(e) => setPath(["encoding", "format"], e.target.value)} /></Field>
        <Field label="Max bytes" htmlFor="g-max"><input id="g-max" type="number" className="input" value={spec.file_size_guard?.max_bytes ?? ""} onChange={(e) => num(["file_size_guard", "max_bytes"], e.target.value)} /></Field>
        <Field label="WebP quality floor" htmlFor="g-floor" hint="1–100"><input id="g-floor" type="number" min="1" max="100" className="input" value={spec.file_size_guard?.webp_quality_floor ?? ""} onChange={(e) => num(["file_size_guard", "webp_quality_floor"], e.target.value)} /></Field>
        <Field label="Alpha behaviour" htmlFor="enc-a">
          <select id="enc-a" className="input" value={spec.encoding?.alpha_behaviour ?? "pre-composite-onto-white"} onChange={(e) => setPath(["encoding", "alpha_behaviour"], e.target.value)}>
            <option value="pre-composite-onto-white">pre-composite onto white</option>
            <option value="preserve-alpha">preserve alpha</option>
          </select>
        </Field>
      </fieldset>

      <Field label="File-naming pattern" htmlFor="naming" hint="Must contain a product identifier ({sku}) and {ext}." >
        <input id="naming" className="input font-mono text-xs" value={spec.file_naming_pattern ?? ""} onChange={(e) => setPath(["file_naming_pattern"], e.target.value)} />
      </Field>
      <p className="-mt-3 text-xs text-slate-500">Preview: <span className="font-mono">{previewName}</span></p>

      <fieldset>
        <legend className="mb-1 text-sm font-semibold text-slate-900">Forbidden overlays (“do not include”)</legend>
        <ul className="flex flex-wrap gap-2">
          {overlays.map((o, i) => (
            <li key={i} className="badge bg-slate-100 text-slate-700">
              {o}
              <button className="ml-2 text-danger" aria-label={`Remove ${o}`} onClick={() => setOverlays(overlays.filter((_, j) => j !== i))}>×</button>
            </li>
          ))}
        </ul>
        <div className="mt-2 flex gap-2">
          <input aria-label="New forbidden overlay" className="input flex-1" value={newOverlay} onChange={(e) => setNewOverlay(e.target.value)} placeholder="e.g. discount badge" />
          <button className="btn-secondary" onClick={() => { if (newOverlay.trim()) { setOverlays([...overlays, newOverlay.trim()]); setNewOverlay(""); } }}>Add</button>
        </div>
      </fieldset>

      <button className="btn-primary" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save new version"}</button>
    </div>
  );
}

function VersionHistory({ type, versions, activeVersion, onChanged }: { type: string; versions: RuleSetVersion[]; activeVersion: string; onChanged: () => void }) {
  async function activate(v: number) {
    await post(`rules/${type}/activate/${v}`);
    onChanged();
  }
  const columnDefs: ColDef[] = [
    {
      headerName: "Version",
      width: 140,
      cellRenderer: (p: any) => (
        <span>v{p.data.version} {p.data.active ? <Badge kind="ok">active</Badge> : null}</span>
      ),
    },
    { field: "createdBy", headerName: "By", flex: 1, minWidth: 120 },
    { field: "note", headerName: "Note", flex: 1, minWidth: 160, valueFormatter: (p: any) => p.value || "—" },
    { field: "createdAt", headerName: "Created", width: 190, valueFormatter: (p: any) => new Date(p.value).toLocaleString() },
    {
      headerName: "",
      colId: "activate",
      width: 120,
      sortable: false,
      filter: false,
      cellRenderer: (p: any) =>
        p.data.active ? null : (
          <button type="button" className="btn-secondary px-3 py-1 text-xs" onClick={() => p.context.activate(p.data.version)}>
            Activate
          </button>
        ),
    },
  ];
  return (
    <section className="card mt-6" aria-labelledby="vh">
      <h2 id="vh" className="mb-3 font-semibold text-slate-900">Version history — <span className="font-normal text-slate-500">{activeVersion}</span></h2>
      <DataGrid rowData={versions} columnDefs={columnDefs} autoHeight context={{ activate }} getRowId={(p: any) => String(p.data.id)} />
    </section>
  );
}
