"use client";

import { useEffect, useMemo, useState } from "react";
import { get, post } from "@/lib/api";
import { Notice, PageHeader, Spinner } from "@/components/ui";
import { DataGrid, type ColDef } from "@/components/DataGrid";

type Attr = { code: string; label: string };
type Cat = { code: string; label: string; level: string };
type Product = { id: string; sku: string; productName: string; template: string; type: string; status: string };
type PreviewResp = { ok: boolean; count: number; capped?: boolean; products: Product[]; error?: string };
type RunResp = { ok: boolean; queued?: number; skipped?: number; selected?: number; detail?: string; error?: string };

type Rule = { field: string; operator: string; value: string };

const TEMPLATES = ["Physical Product", "Coupon", "Voucher"];
const TYPES = ["Simple Product", "Product with Variant", "Grouping Product"];

// Operators mirror the backend's applyOperator(). `noValue` operators ignore the value box.
const OPERATORS: { value: string; label: string; noValue?: boolean }[] = [
  { value: "=", label: "equals" },
  { value: "!=", label: "does not equal" },
  { value: "contains", label: "contains" },
  { value: "in", label: "in list (comma)" },
  { value: ">", label: "greater than" },
  { value: "<", label: "less than" },
  { value: ">=", label: "≥" },
  { value: "<=", label: "≤" },
  { value: "set", label: "is set", noValue: true },
  { value: "empty", label: "is empty", noValue: true },
];

const isNoValue = (op: string) => OPERATORS.find((o) => o.value === op)?.noValue ?? false;
const newRule = (): Rule => ({ field: "status", operator: "=", value: "" });

// Optional product video(s) (Vertex Veo). Pick any subset — each selected style produces
// its own clip. The backend encodes each as a "video:<mode>" scope token.
const VIDEO_MODES: { value: string; label: string; hint: string }[] = [
  { value: "studio_3d", label: "Studio 3D turntable", hint: "360° rotation on a seamless white studio background." },
  { value: "lifestyle", label: "Lifestyle scene", hint: "The product shown naturally in a realistic everyday setting." },
];

export default function TargetedPage() {
  const [attrs, setAttrs] = useState<Attr[]>([]);
  const [cats, setCats] = useState<Cat[]>([]);
  const [statuses, setStatuses] = useState<string[]>([]);
  const [loadError, setLoadError] = useState<string | null>(null);

  // Filter — a connector (and/or) over 1..n rules.
  const [connector, setConnector] = useState<"and" | "or">("and");
  const [rules, setRules] = useState<Rule[]>([newRule()]);

  // Attribute scope (what gets researched) + matched products + grid selection.
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [videoModes, setVideoModes] = useState<Set<string>>(new Set()); // selected video styles (empty = none)
  const [videoEnabled, setVideoEnabled] = useState(true); // Video-creation service on?
  const [attrQuery, setAttrQuery] = useState("");
  const [preview, setPreview] = useState<PreviewResp | null>(null);
  const [previewSeq, setPreviewSeq] = useState(0); // remounts the grid so it re-selects-all on each preview
  const [previewBusy, setPreviewBusy] = useState(false);
  const [selectedIds, setSelectedIds] = useState<string[]>([]);
  const [runResult, setRunResult] = useState<RunResp | null>(null);
  const [runBusy, setRunBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    get<{ attributes: Attr[] }>("attributes").then((d) => setAttrs(d.attributes)).catch((e) => setLoadError((e as Error).message));
    get<{ categoryRules: Cat[] }>("category-rules").then((d) => setCats(d.categoryRules)).catch(() => {});
    get<{ byStatus: Record<string, number> }>("ergonode/stats").then((d) => setStatuses(Object.keys(d.byStatus ?? {}))).catch(() => {});
    get<{ services: { name: string; enabled: boolean }[] }>("services")
      .then((d) => setVideoEnabled(d.services?.find((s) => s.name === "video-creation")?.enabled ?? false))
      .catch(() => {});
  }, []);

  const updateRule = (i: number, patch: Partial<Rule>) =>
    setRules((rs) => rs.map((r, j) => (j === i ? { ...r, ...patch } : r)));
  const addRule = () => setRules((rs) => [...rs, newRule()]);
  const removeRule = (i: number) => setRules((rs) => (rs.length <= 1 ? rs : rs.filter((_, j) => j !== i)));

  // Per-field value suggestions (datalist) — still free-text, so contains/in keep working.
  const suggestionsFor = (field: string): string[] => {
    if (field === "status") return statuses;
    if (field === "template") return TEMPLATES;
    if (field === "type") return TYPES;
    if (field === "category") return cats.map((c) => c.code);
    return [];
  };
  const placeholderFor = (rule: Rule): string => {
    if (rule.operator === "in") return "value1, value2, value3";
    if ([">", "<", ">=", "<="].includes(rule.operator)) return "number";
    if (rule.field === "category") return "category code";
    return "value";
  };

  async function runPreview() {
    setPreviewBusy(true);
    setError(null);
    setRunResult(null);
    setSelectedIds([]);
    try {
      const cleaned = rules.filter((r) => r.field.trim() !== "");
      const resp = await post<PreviewResp>("targeted/preview", { connector, rules: cleaned });
      setPreview(resp);
      setPreviewSeq((n) => n + 1);
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setPreviewBusy(false);
    }
  }

  async function run() {
    const wantsVideo = videoModes.size > 0;
    if (selected.size === 0 && !wantsVideo) {
      setError("Select at least one attribute to enrich, or choose a product video.");
      return;
    }
    if (selectedIds.length === 0) {
      setError("Select at least one product (tick rows, or use the header checkbox).");
      return;
    }
    const scopeParts = [
      selected.size > 0 ? `${selected.size} attribute(s)` : null,
      wantsVideo ? `${videoModes.size} video(s)` : null,
    ].filter(Boolean).join(" + ");
    if (!window.confirm(
      `Queue ${selectedIds.length} selected product(s) for targeted enrichment on ${scopeParts}?\n\n` +
      "Only the selected scope is researched, manipulated, scored and written back — each product's workflow status is left unchanged. " +
      (wantsVideo && !videoEnabled ? "⚠ Video generation is currently OFF — the video step is skipped until you enable it under Admin Settings → Services. " : "") +
      "Processing runs on the Worker (start it under Admin Settings → Services)."
    )) return;
    setRunBusy(true);
    setError(null);
    try {
      setRunResult(await post<RunResp>("targeted/run", { productIds: selectedIds, attributes: [...selected], videoModes: [...videoModes] }));
    } catch (e) {
      setError((e as Error).message);
    } finally {
      setRunBusy(false);
    }
  }

  const toggle = (code: string) =>
    setSelected((s) => {
      const n = new Set(s);
      n.has(code) ? n.delete(code) : n.add(code);
      return n;
    });

  const toggleVideo = (mode: string) =>
    setVideoModes((s) => {
      const n = new Set(s);
      n.has(mode) ? n.delete(mode) : n.add(mode);
      return n;
    });

  const visibleAttrs = useMemo(() => {
    const q = attrQuery.trim().toLowerCase();
    return q ? attrs.filter((a) => a.label.toLowerCase().includes(q) || a.code.toLowerCase().includes(q)) : attrs;
  }, [attrs, attrQuery]);

  const productCols = useMemo<ColDef[]>(
    () => [
      { field: "sku", headerName: "SKU", flex: 1, minWidth: 170 },
      { field: "productName", headerName: "Product name", flex: 1, minWidth: 180, valueFormatter: (p: any) => p.value || "—" },
      { field: "template", headerName: "Template", width: 150 },
      { field: "type", headerName: "Type", width: 175 },
      { field: "status", headerName: "Status", width: 130 },
    ],
    [],
  );

  if (loadError) return <Notice kind="error">Could not load: {loadError}</Notice>;
  if (attrs.length === 0) return <Spinner label="Loading…" />;

  const products = preview?.ok ? preview.products : [];

  return (
    <>
      <PageHeader
        title="Targeted Enrichment"
        description="Build a filter from one or more rules, review the matching products and pick exactly which to run, then enrich a chosen set of attributes — a partial run that researches, manipulates, scores and writes back only those attributes, leaving each product's workflow status unchanged."
      />

      {error && <div className="mb-4"><Notice kind="error">{error}</Notice></div>}

      <div className="grid gap-6 lg:grid-cols-2">
        {/* 1 — Filter rules */}
        <section className="card" aria-labelledby="flt-h">
          <h2 id="flt-h" className="mb-1 font-semibold text-slate-900">1 · Filter rules</h2>
          <div className="mb-3 flex items-center gap-2 text-sm text-slate-600">
            <span>Match</span>
            <select
              className="input w-auto py-1"
              value={connector}
              onChange={(e) => setConnector(e.target.value as "and" | "or")}
              aria-label="Rule connector"
            >
              <option value="and">ALL</option>
              <option value="or">ANY</option>
            </select>
            <span>of the following rules:</span>
          </div>

          <div className="space-y-2">
            {rules.map((rule, i) => (
              <div key={i}>
                {i > 0 && (
                  <div className="py-0.5 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
                    {connector === "and" ? "and" : "or"}
                  </div>
                )}
                <div className="flex flex-wrap items-center gap-2">
                  <select
                    className="input min-w-[140px] flex-1"
                    value={rule.field}
                    onChange={(e) => updateRule(i, { field: e.target.value })}
                    aria-label="Field"
                  >
                    <optgroup label="Product fields">
                      <option value="status">Status</option>
                      <option value="template">Template</option>
                      <option value="type">Product type</option>
                      <option value="category">Category</option>
                      <option value="sku">SKU</option>
                    </optgroup>
                    <optgroup label="Attributes">
                      {attrs.map((a) => <option key={a.code} value={a.code}>{a.label}</option>)}
                    </optgroup>
                  </select>
                  <select
                    className="input w-40"
                    value={rule.operator}
                    onChange={(e) => updateRule(i, { operator: e.target.value })}
                    aria-label="Operator"
                  >
                    {OPERATORS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
                  </select>
                  {isNoValue(rule.operator) ? (
                    <span className="min-w-[140px] flex-1 px-1 text-sm italic text-slate-400">no value needed</span>
                  ) : (
                    <>
                      <input
                        list={`sug-${i}`}
                        className="input min-w-[140px] flex-1"
                        value={rule.value}
                        onChange={(e) => updateRule(i, { value: e.target.value })}
                        placeholder={placeholderFor(rule)}
                        aria-label="Value"
                      />
                      <datalist id={`sug-${i}`}>
                        {suggestionsFor(rule.field).map((s) => <option key={s} value={s} />)}
                      </datalist>
                    </>
                  )}
                  <button
                    type="button"
                    className="px-1 text-slate-400 hover:text-danger disabled:opacity-30"
                    onClick={() => removeRule(i)}
                    disabled={rules.length <= 1}
                    aria-label="Remove rule"
                    title="Remove rule"
                  >
                    ✕
                  </button>
                </div>
              </div>
            ))}
          </div>

          <div className="mt-3 flex flex-wrap items-center gap-3">
            <button type="button" className="text-sm font-medium text-brand hover:underline" onClick={addRule}>
              + Add rule
            </button>
            <span className="grow" />
            <button type="button" className="btn-secondary" onClick={runPreview} disabled={previewBusy}>
              {previewBusy ? "Matching…" : "Preview matches"}
            </button>
          </div>
          {preview && preview.ok && (
            <p className="mt-2 text-sm font-medium text-slate-700">
              {preview.count} product{preview.count === 1 ? "" : "s"} match
              {preview.capped && <span className="font-normal text-slate-500"> · showing the first {products.length}</span>}
            </p>
          )}
          {preview && !preview.ok && <p className="mt-2 text-sm text-danger">{preview.error}</p>}
        </section>

        {/* 2 — Select attributes */}
        <section className="card" aria-labelledby="attr-h">
          <h2 id="attr-h" className="mb-1 font-semibold text-slate-900">
            2 · Attributes to enrich <span className="font-normal text-slate-500">({selected.size} selected)</span>
          </h2>
          <p className="mb-3 text-sm text-slate-500">Only these attributes are (re)researched and written back.</p>
          <input className="input mb-2" placeholder="Search attributes…" value={attrQuery} onChange={(e) => setAttrQuery(e.target.value)} />
          <div className="max-h-72 overflow-auto rounded-sm border border-slate-200">
            {visibleAttrs.map((a) => (
              <label key={a.code} className="flex cursor-pointer items-center gap-2 px-2 py-1 text-sm hover:bg-slate-50">
                <input type="checkbox" checked={selected.has(a.code)} onChange={() => toggle(a.code)} />
                <span className="text-slate-700">{a.label}</span>
                <span className="font-mono text-[10px] text-slate-400">{a.code}</span>
              </label>
            ))}
          </div>
          {selected.size > 0 && (
            <button type="button" className="mt-2 text-xs text-slate-500 hover:underline" onClick={() => setSelected(new Set())}>Clear selection</button>
          )}

          <div className="mt-4 border-t border-slate-200 pt-3">
            <h3 className="text-sm font-semibold text-slate-900">
              Product video <span className="font-normal text-slate-500">(optional, {videoModes.size} selected)</span>
            </h3>
            <p className="mb-2 text-xs text-slate-500">Pick any styles — each generates its own clip (Vertex Veo) per selected product.</p>
            <div className="space-y-1.5">
              {VIDEO_MODES.map((m) => (
                <label key={m.value} className="flex cursor-pointer items-start gap-2 text-sm">
                  <input type="checkbox" className="mt-0.5" checked={videoModes.has(m.value)} onChange={() => toggleVideo(m.value)} />
                  <span>
                    <span className="text-slate-700">{m.label}</span>
                    <span className="block text-xs text-slate-500">{m.hint}</span>
                  </span>
                </label>
              ))}
            </div>
            {videoModes.size > 0 && !videoEnabled && (
              <p className="mt-2 rounded-md bg-amber-50 px-3 py-2 text-xs text-amber-800">
                Video generation is currently <strong>off</strong>. Queued videos are skipped until you enable
                “Video generation (Veo)” under Admin Settings → Services.
              </p>
            )}
          </div>
        </section>
      </div>

      {/* 3 — Review + run */}
      <section className="card mt-6" aria-labelledby="run-h">
        <h2 id="run-h" className="mb-1 font-semibold text-slate-900">3 · Review &amp; run</h2>
        <p className="mb-3 text-sm text-slate-500">
          Tick the rows to include — use the header checkbox to select all, or untick the few you want to skip.
        </p>
        {runResult && (
          <div className="mb-4">
            <Notice kind={runResult.ok ? "success" : "error"}>{runResult.ok ? runResult.detail : runResult.error}</Notice>
          </div>
        )}
        {preview && preview.ok && products.length > 0 && (
          <>
            <div className="mb-2 flex items-center justify-between text-sm">
              <span className="text-slate-600">
                {products.length} product{products.length === 1 ? "" : "s"} shown
                {preview.capped && <> (of {preview.count} matched)</>}
              </span>
              <span className="font-medium text-slate-800">{selectedIds.length} selected</span>
            </div>
            <DataGrid
              key={previewSeq}
              rowData={products}
              columnDefs={productCols}
              height={460}
              getRowId={(p: any) => String(p.data.id)}
              rowSelection={{ mode: "multiRow", checkboxes: true, headerCheckbox: true, enableClickSelection: false }}
              onFirstDataRendered={(e: any) => window.setTimeout(() => e.api.selectAll(), 0)}
              onSelectionChanged={(e: any) => setSelectedIds(e.api.getSelectedRows().map((r: Product) => r.id))}
            />
          </>
        )}
        {preview && preview.ok && preview.count === 0 && <Notice kind="info">No products match these rules.</Notice>}
        {!preview && <p className="text-sm text-slate-500">Define rules above and press <strong>Preview matches</strong> to see products here.</p>}
        <div className="mt-4 flex flex-wrap items-center gap-3">
          <button
            type="button"
            className="btn-primary"
            onClick={run}
            disabled={runBusy || (selected.size === 0 && videoModes.size === 0) || selectedIds.length === 0}
          >
            {runBusy ? "Queuing…" : `Run targeted enrichment${selectedIds.length ? ` (${selectedIds.length})` : ""}`}
          </button>
          <span className="text-xs text-slate-500">Processing happens on the Worker (Admin Settings → Services).</span>
        </div>
      </section>
    </>
  );
}
