/* global React, Icon */
const { useState: useStateW, useMemo: useMemoW, useEffect: useEffectW } = React;

// ==================== WORKFLOW / VALIDATION ====================
function Workflow({ t, lang, isSuperAdmin, actingOrgId, activeOrgId, myOrgId }) {
  const { data: liveItemsRaw, realtime, refresh: refreshQueue } = window.melr.useValidationQueue();
  const LiveBadge = window.melr.LiveBadge;
  // Multi-org: narrow the validation queue to the effective org so the
  // counts/lists reflect the current context (super-admin acting > active
  // multi-org > home). validation_items rows expose organization_id
  // directly; falls back to no filter when effOrg is undefined.
  const effOrg = (isSuperAdmin && actingOrgId) ? actingOrgId : (activeOrgId || myOrgId);
  const liveItems = (effOrg && liveItemsRaw)
    ? liveItemsRaw.filter((it) => !it.organization_id || it.organization_id === effOrg)
    : (liveItemsRaw || []);
  // Current user id — used by the "À mon action / Que j'ai soumis" filters
  const [currentUserId, setCurrentUserId] = useStateW(null);
  useEffectW(() => {
    let cancelled = false;
    window.melr.currentProfile().then((p) => { if (!cancelled && p) setCurrentUserId(p.id); });
    return () => { cancelled = true; };
  }, []);
  // Show fixture items only when there is no live data, or when the user
  // explicitly toggles them on. Once at least one live item exists, the
  // demos hide automatically so the UI reflects real state.
  const [showDemo, setShowDemo] = useStateW(false);

  // Templates modal (Configurer flux)
  const [tplOpen, setTplOpen] = useStateW(false);

  // Bulk-action mode — when on, the queue shows checkboxes and the
  // toolbar offers Approve all / Send back all / Reject all on the
  // currently selected items.
  const [bulkMode, setBulkMode] = useStateW(false);
  const [bulkSelected, setBulkSelected] = useStateW({});  // { itemId: true }
  const [bulkBusy, setBulkBusy] = useStateW(false);
  const toggleBulkItem = (id) => setBulkSelected((s) => {
    const next = { ...s };
    if (next[id]) delete next[id]; else next[id] = true;
    return next;
  });
  // UUID regex — used to tell live (database) items from fixture items.
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  // Validation queue — cross-module items pending approval
  const FIXTURE_ITEMS = [
    { id: "VAL-2026-117", type: "indicator", title: lang === "fr" ? "Mise à jour T1 — Taux de couverture vaccinale Penta-3" : "Q1 update — Penta-3 immunization coverage", project: "P-241", module: lang === "fr" ? "Indicateurs" : "Indicators", value: "78.4%", prev: "72.1%", who: "Aïssata Diallo", role: lang === "fr" ? "Chef de projet" : "Project lead", submitted: lang === "fr" ? "il y a 2 h" : "2 h ago", step: 1, sla: 2, prio: "high", evidence: 3, comments: 2 },
    { id: "VAL-2026-116", type: "exante", title: lang === "fr" ? "Évaluation ex ante — P-415 Télémédecine CI" : "Ex-ante appraisal — P-415 Telemedicine CI", project: "P-415", module: lang === "fr" ? "Ex ante" : "Ex-ante", value: "VAN +1.84 M€", prev: "TRI 12.6%", who: "Yao Konan", role: lang === "fr" ? "Chef de projet" : "Project lead", submitted: lang === "fr" ? "il y a 6 h" : "6 h ago", step: 2, sla: 5, prio: "high", evidence: 12, comments: 7 },
    { id: "VAL-2026-115", type: "dqa", title: lang === "fr" ? "Audit DQA — 4 sites avec écart > 10%" : "DQA audit — 4 sites with gap > 10%", project: "P-188", module: lang === "fr" ? "Audit données" : "Data audit", value: "9 / 18 " + (lang === "fr" ? "indicateurs" : "indicators"), prev: lang === "fr" ? "score 78%" : "score 78%", who: "Modou Sarr", role: lang === "fr" ? "Chef de projet" : "Project lead", submitted: lang === "fr" ? "hier, 17:42" : "yesterday, 5:42 pm", step: 1, sla: 3, prio: "med", evidence: 8, comments: 4 },
    { id: "VAL-2026-114", type: "report", title: lang === "fr" ? "Rapport Q4 2025 — P-241 Sahel" : "Q4 2025 Report — P-241 Sahel", project: "P-241", module: "Reporting", value: "AFD-RP · 38 p.", prev: lang === "fr" ? "11 indicateurs" : "11 indicators", who: "Khadija Diabaté", role: lang === "fr" ? "Responsable S&E" : "M&E officer", submitted: lang === "fr" ? "hier, 09:18" : "yesterday, 9:18 am", step: 3, sla: 7, prio: "high", evidence: 24, comments: 11 },
    { id: "VAL-2026-113", type: "indicator", title: lang === "fr" ? "Baseline — 12 indicateurs Tchad WASH" : "Baseline — 12 WASH indicators Chad", project: "P-377", module: "Baseline", value: "12 / 12", prev: lang === "fr" ? "19 sites" : "19 sites", who: "Mahamat Saleh", role: lang === "fr" ? "Chef de projet" : "Project lead", submitted: lang === "fr" ? "il y a 2 j" : "2 d ago", step: 1, sla: 4, prio: "med", evidence: 19, comments: 1 },
    { id: "VAL-2026-112", type: "indicator", title: lang === "fr" ? "Correction — Mortalité maternelle Q3" : "Correction — Q3 maternal mortality", project: "P-302", module: lang === "fr" ? "Indicateurs" : "Indicators", value: "127 / 100k", prev: "142 / 100k", who: "Fatima Ould Cheikh", role: lang === "fr" ? "Chef de projet" : "Project lead", submitted: lang === "fr" ? "il y a 3 j" : "3 d ago", step: 2, sla: 1, prio: "high", evidence: 5, comments: 8 },
    { id: "VAL-2026-111", type: "exante", title: lang === "fr" ? "Révision matrice MPR — P-462" : "MPR matrix revision — P-462", project: "P-462", module: lang === "fr" ? "Ex ante" : "Ex-ante", value: "32 " + (lang === "fr" ? "indicateurs" : "indicators"), prev: lang === "fr" ? "6 résultats" : "6 outcomes", who: "Karim Bensaad", role: lang === "fr" ? "Responsable S&E" : "M&E officer", submitted: lang === "fr" ? "il y a 4 j" : "4 d ago", step: 1, sla: 0, prio: "high", evidence: 14, comments: 5 },
    { id: "VAL-2026-110", type: "report", title: lang === "fr" ? "Rapport annuel 2025 — Portefeuille AFD" : "Annual 2025 report — AFD portfolio", project: "—", module: "Reporting", value: lang === "fr" ? "9 projets · 247 sites" : "9 projects · 247 sites", prev: "—", who: "Khadija Diabaté", role: lang === "fr" ? "Responsable S&E" : "M&E officer", submitted: lang === "fr" ? "il y a 5 j" : "5 d ago", step: 4, sla: 12, prio: "high", evidence: 47, comments: 23 },
  ];

  // Map object_type → UI bucket (drives icon + chain definition). Default
  // chain lengths kept here so liveMapped doesn't reach forward into CHAIN
  // (which is declared further down in this function).
  const typeBucket = (ot) => {
    const o = String(ot || "").toLowerCase();
    if (o.includes("sat") || o.includes("dvt") || o.includes("dqa") || o.includes("audit")) return "dqa";
    if (o.includes("exante")) return "exante";
    if (o.includes("report")) return "report";
    return "indicator";
  };
  const DEFAULT_CHAIN_LEN = { indicator: 4, exante: 4, dqa: 3, report: 5 };
  const liveMapped = (liveItems || []).map((it) => {
    const bucket = typeBucket(it.object_type);
    return {
      id: it.id,
      isLive: true,
      state: it.state,
      type: bucket,
      object_type: it.object_type,
      title: it.title || "",
      project: "—",
      module: it.object_type || "—",
      value: "—",
      prev: "—",
      who: "—",
      role: "—",
      submitted: new Date(it.submitted_at).toLocaleString(lang === "fr" ? "fr-FR" : "en-US"),
      submitted_at: it.submitted_at,
      due_at: it.due_at,
      submitted_by: it.submitted_by,
      assignee_id: it.current_assignee_id,
      assignee: it.assignee || null,
      step: it.current_step || 0,
      total_steps: it.total_steps || DEFAULT_CHAIN_LEN[bucket] || 1,
      sla: it.due_at ? Math.max(0, Math.ceil((new Date(it.due_at) - new Date()) / 86400000)) : 0,
      prio: it.priority || "med",
      evidence: 0,
      comments: 0,
    };
  });
  const fixtureMarked = FIXTURE_ITEMS.map((it) => ({ ...it, isLive: false }));
  // Only show fixtures when there are no live items (or the user opted in).
  const ITEMS = (liveMapped.length > 0 && !showDemo)
    ? liveMapped
    : (liveMapped.length === 0 ? fixtureMarked : [...liveMapped, ...fixtureMarked]);

  // Live counts — computed from real rows only (excludes fixtures)
  const sevenDaysAgo = new Date(Date.now() - 7 * 86400000);
  const liveOpen     = liveMapped.filter((it) => ["submitted", "in_review", "sent_back"].includes(it.state));
  // "Awaiting me" — precise when the item is assigned to me, falls back to
  // open items where I am not the submitter (broad bucket) when nothing is
  // assigned yet.
  const liveAwaiting = liveOpen.filter((it) =>
    currentUserId != null && it.assignee_id === currentUserId
  );
  const liveAwaitingFallback = liveOpen.filter((it) =>
    currentUserId != null && !it.assignee_id && it.submitted_by !== currentUserId
  );
  const liveAwaitingCount = liveAwaiting.length + liveAwaitingFallback.length;
  const liveMine     = liveMapped.filter((it) => currentUserId != null && it.submitted_by === currentUserId);
  const liveOverdue  = liveOpen.filter((it) => it.due_at && new Date(it.due_at) < new Date());
  const liveApproved7 = liveMapped.filter((it) => it.state === "approved" && new Date(it.submitted_at) >= sevenDaysAgo);
  const liveRejected7 = liveMapped.filter((it) => it.state === "rejected" && new Date(it.submitted_at) >= sevenDaysAgo);

  const FILTERS = [
    { k: "all",       l: lang === "fr" ? "Tous" : "All",                  c: ITEMS.length },
    { k: "mine",      l: lang === "fr" ? "À mon action" : "Awaiting me",  c: liveAwaitingCount },
    { k: "submitted", l: lang === "fr" ? "Que j'ai soumis" : "I submitted", c: liveMine.length },
    { k: "overdue",   l: lang === "fr" ? "En retard" : "Overdue",         c: liveOverdue.length },
    { k: "approved",  l: lang === "fr" ? "Approuvés (7j)" : "Approved (7d)", c: liveApproved7.length },
    { k: "rejected",  l: lang === "fr" ? "Rejetés (7j)" : "Rejected (7d)",  c: liveRejected7.length },
  ];
  const [f, setF] = useStateW("all");
  const [sel, setSel] = useStateW(ITEMS[0] ? ITEMS[0].id : null);
  const selected = useMemoW(() => ITEMS.find((x) => x.id === sel) || ITEMS[0] || null, [sel, ITEMS]);

  // Filter the displayed queue
  const filteredItems = useMemoW(() => {
    if (f === "all") return ITEMS;
    if (f === "mine")      return [...liveAwaiting, ...liveAwaitingFallback];
    if (f === "submitted") return liveMine;
    if (f === "overdue")   return liveOverdue;
    if (f === "approved")  return liveApproved7;
    if (f === "rejected")  return liveRejected7;
    return ITEMS;
  }, [f, ITEMS, liveAwaiting.length, liveMine.length, liveOverdue.length, liveApproved7.length, liveRejected7.length]);

  const TYPE_ICON = { indicator: "trending", exante: "scale", dqa: "database", report: "fileText" };
  const TYPE_TONE = { indicator: "accent", exante: "violet", dqa: "amber", report: "teal" };

  // Workflow chain — defined per item type
  const CHAIN = {
    indicator: [
      { r: lang === "fr" ? "Saisie terrain" : "Field data", who: "submitter", t: lang === "fr" ? "Soumet" : "Submits" },
      { r: lang === "fr" ? "S&E" : "M&E", who: "S. Touré", t: lang === "fr" ? "Vérifie cohérence" : "Coherence check" },
      { r: lang === "fr" ? "Chef de projet" : "Project lead", who: "A. Diallo", t: lang === "fr" ? "Valide" : "Validates" },
      { r: lang === "fr" ? "Audit" : "Audit", who: "—", t: lang === "fr" ? "Échantillonné DQA" : "DQA-sampled" },
    ],
    exante: [
      { r: lang === "fr" ? "Chef de projet" : "Project lead", who: "submitter", t: lang === "fr" ? "Rédige & soumet" : "Drafts & submits" },
      { r: lang === "fr" ? "Comité technique" : "Technical committee", who: lang === "fr" ? "3 membres" : "3 members", t: lang === "fr" ? "Revue MPR + analyse fi." : "MPR + financial review" },
      { r: lang === "fr" ? "Direction" : "Direction", who: "D. Ndiaye", t: lang === "fr" ? "Décision Go / No-Go" : "Go / No-Go decision" },
      { r: lang === "fr" ? "Bailleur" : "Donor", who: lang === "fr" ? "AFD" : "AFD", t: lang === "fr" ? "Avis final" : "Final opinion" },
    ],
    dqa: [
      { r: lang === "fr" ? "S&E" : "M&E", who: "submitter", t: lang === "fr" ? "Lance audit" : "Launches audit" },
      { r: lang === "fr" ? "Chef de projet" : "Project lead", who: "M. Sarr", t: lang === "fr" ? "Plan d'action" : "Action plan" },
      { r: lang === "fr" ? "Audit interne" : "Internal audit", who: "B. Tall", t: lang === "fr" ? "Clôture" : "Closes" },
    ],
    report: [
      { r: lang === "fr" ? "S&E" : "M&E", who: "submitter", t: lang === "fr" ? "Compile & rédige" : "Compiles & drafts" },
      { r: lang === "fr" ? "Chef de projet" : "Project lead", who: "A. Diallo", t: lang === "fr" ? "Revue contenu" : "Content review" },
      { r: lang === "fr" ? "Comité qualité" : "Quality committee", who: lang === "fr" ? "4 membres" : "4 members", t: lang === "fr" ? "Validation" : "Validation" },
      { r: lang === "fr" ? "Direction" : "Direction", who: "D. Ndiaye", t: lang === "fr" ? "Approbation" : "Approval" },
      { r: lang === "fr" ? "Bailleur" : "Donor", who: lang === "fr" ? "AFD" : "AFD", t: lang === "fr" ? "Diffusion" : "Distribution" },
    ],
  };

  // Activity log — for selected item
  const ACTIVITY = [
    { who: "Aïssata Diallo", role: lang === "fr" ? "Chef de projet" : "Project lead", action: lang === "fr" ? "a soumis pour validation" : "submitted for validation", when: lang === "fr" ? "il y a 2 h" : "2 h ago", icon: "send", tone: "accent" },
    { who: "Souleymane Touré", role: "S&E", action: lang === "fr" ? "a vérifié la cohérence avec la baseline" : "checked coherence with baseline", when: lang === "fr" ? "il y a 1 h 28" : "1 h 28 ago", icon: "check", tone: "green", note: lang === "fr" ? "+6.3 pts vs baseline T0 — cohérent avec la campagne de rattrapage février." : "+6.3 pts vs baseline — coherent with February catch-up." },
    { who: "Karim Bensaad", role: lang === "fr" ? "Audit qualité" : "Quality audit", action: lang === "fr" ? "a demandé une pièce justificative" : "requested supporting evidence", when: lang === "fr" ? "il y a 47 min" : "47 min ago", icon: "alert", tone: "amber", note: lang === "fr" ? "Joindre la fiche de stock vaccin des 11 sites — l'écart de +6.3 pts mérite une vérification." : "Attach vaccine stock sheet from 11 sites — +6.3 pts gap warrants verification." },
    { who: "Aïssata Diallo", role: lang === "fr" ? "Chef de projet" : "Project lead", action: lang === "fr" ? "a joint 3 pièces" : "attached 3 evidence", when: lang === "fr" ? "il y a 22 min" : "22 min ago", icon: "upload", tone: "accent", note: "stock-vaccin-fev2026.xlsx · scan-PV-CSCom.pdf · photos-vaccination-Tombouctou.zip" },
    { who: lang === "fr" ? "En attente de vous" : "Waiting on you", role: lang === "fr" ? "Responsable S&E" : "M&E officer", action: lang === "fr" ? "doit valider" : "must validate", when: lang === "fr" ? "depuis 22 min" : "22 min", icon: "clock", tone: "amber", pending: true },
  ];

  // Live KPI cards — derive everything from liveMapped (not fixtures)
  const KPIS = [
    {
      l: lang === "fr" ? "En attente de vous" : "Awaiting you",
      v: String(liveAwaitingCount),
      s: liveAwaiting.length > 0
          ? (liveAwaiting.length + " " + (lang === "fr" ? "assigné(s)" : "assigned"))
          : (liveOverdue.length > 0
              ? (liveOverdue.length + " " + (lang === "fr" ? "en retard" : "overdue"))
              : (lang === "fr" ? "à instruire" : "to act on")),
      tone: liveOverdue.length > 0 ? "amber" : (liveAwaitingCount > 0 ? "accent" : "green"),
    },
    {
      l: lang === "fr" ? "Que j'ai soumis (en cours)" : "Mine in flight",
      v: String(liveMine.filter((it) => ["submitted", "in_review", "sent_back"].includes(it.state)).length),
      s: liveMine.length + " " + (lang === "fr" ? "au total" : "total"),
      tone: "accent",
    },
    {
      l: lang === "fr" ? "Approuvés / 7 j" : "Approved / 7 d",
      v: String(liveApproved7.length),
      s: liveRejected7.length + " " + (lang === "fr" ? "rejetés" : "rejected"),
      tone: liveApproved7.length > 0 ? "green" : undefined,
    },
    {
      l: lang === "fr" ? "Validations ouvertes" : "Open validations",
      v: String(liveOpen.length),
      s: liveMapped.length + " " + (lang === "fr" ? "au total" : "total"),
      tone: liveOverdue.length > 0 ? "amber" : "accent",
    },
  ];

  return (
    <div className="page wf-page">
      <div className="page-header">
        <div className="page-eyebrow">{lang === "fr" ? "WORKFLOW" : "WORKFLOW"}</div>
        <div className="page-header-row">
          <div>
            <h1 className="page-title">{lang === "fr" ? "Validation & approbation" : "Validation & approval"} <LiveBadge on={realtime} lang={lang} /></h1>
            <div className="page-sub">{lang === "fr" ? "Chaînes d'approbation multi-rôles · SLA & journal d'audit" : "Multi-role approval chains · SLA & audit log"}</div>
          </div>
          <div className="page-header-actions">
            <button className="btn sm" onClick={() => setTplOpen(true)}
              title={lang === "fr"
                ? "Gérer les modèles d'approbation par type d'objet (SAT, DVT, rapport…)"
                : "Manage approval templates per object type (SAT, DVT, report…)"}>
              <Icon.settings /> {lang === "fr" ? "Configurer flux" : "Configure flow"}
            </button>
            <button className="btn sm" onClick={() => {
              const date = new Date().toISOString().slice(0, 10);
              window.melr.exportCSV(`workflow-${date}.csv`, ITEMS, [
                { key: "id",        label: "ID" },
                { key: "isLive",    label: lang === "fr" ? "Source" : "Source", value: (it) => it.isLive ? "LIVE" : "DEMO" },
                { key: "title",     label: lang === "fr" ? "Titre" : "Title" },
                { key: "type",      label: "Type" },
                { key: "project",   label: lang === "fr" ? "Projet" : "Project" },
                { key: "module",    label: "Module" },
                { key: "step",      label: lang === "fr" ? "Étape" : "Step" },
                { key: "state",     label: lang === "fr" ? "État" : "State" },
                { key: "prio",      label: lang === "fr" ? "Priorité" : "Priority" },
                { key: "submitted", label: lang === "fr" ? "Soumis le" : "Submitted" },
              ]);
            }}><Icon.download /> {lang === "fr" ? "Exporter journal" : "Export log"}</button>
            <button className={"btn sm " + (bulkMode ? "" : "primary")}
              onClick={() => { setBulkMode((v) => !v); setBulkSelected({}); }}
              title={lang === "fr"
                ? (bulkMode ? "Quitter le mode action groupée" : "Sélectionner plusieurs items pour les approuver / renvoyer / rejeter en masse")
                : (bulkMode ? "Exit bulk mode" : "Select multiple items to approve / send back / reject in bulk")}>
              <Icon.zap /> {bulkMode
                ? (lang === "fr" ? "Quitter" : "Exit bulk")
                : (lang === "fr" ? "Action groupée" : "Bulk action")}
            </button>
          </div>
        </div>
      </div>

      <div className="grid cols-4" style={{ marginBottom: 16 }}>
        {KPIS.map((k, i) => (
          <div key={i} className="kpi">
            <div className="kpi-label">{k.l}</div>
            <div className="kpi-value" style={{ color: `var(--${k.tone === "accent" ? "accent" : k.tone})` }}>{k.v}</div>
            <div className="kpi-sub">{k.s}</div>
          </div>
        ))}
      </div>

      <div className="filter-bar">
        <div className="seg">
          {FILTERS.map((s) => (
            <button key={s.k} className={"seg-btn" + (f === s.k ? " active" : "")} onClick={() => setF(s.k)}>
              {s.l} <span className="seg-count">{s.c}</span>
            </button>
          ))}
        </div>
        <div className="row gap-sm" style={{ marginLeft: "auto" }}>
          {liveMapped.length > 0 && (
            <button className="btn sm" onClick={() => setShowDemo((v) => !v)}
              title={lang === "fr"
                ? "Afficher / masquer les items de démonstration"
                : "Show / hide demo items"}>
              <Icon.eye /> {showDemo
                ? (lang === "fr" ? "Cacher démo" : "Hide demo")
                : (lang === "fr" ? "Afficher démo" : "Show demo")}
            </button>
          )}
          <button className="btn sm"><Icon.filter /> {lang === "fr" ? "Filtrer" : "Filter"}</button>
        </div>
      </div>

      {/* Bulk action toolbar — visible when bulk mode active */}
      {bulkMode && (
        <div className="card" style={{
          marginBottom: 12, padding: "8px 14px",
          background: "#fef3c7", borderColor: "#fcd34d",
          display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
        }}>
          <Icon.zap />
          <strong style={{ fontSize: 13 }}>
            {Object.keys(bulkSelected).length} {lang === "fr" ? "item(s) sélectionné(s)" : "item(s) selected"}
          </strong>
          <button className="btn xs" onClick={() => {
            // Select all displayed live + open items
            const eligible = filteredItems.filter((it) => it.isLive && ["submitted", "in_review", "sent_back"].includes(it.state));
            const next = {};
            eligible.forEach((it) => { next[it.id] = true; });
            setBulkSelected(next);
          }}>{lang === "fr" ? "Tout sélectionner" : "Select all"}</button>
          <button className="btn xs" onClick={() => setBulkSelected({})}>
            {lang === "fr" ? "Effacer" : "Clear"}
          </button>
          <span style={{ flex: 1 }} />
          <button className="btn sm success" disabled={bulkBusy || Object.keys(bulkSelected).length === 0}
            onClick={async () => {
              const ids = Object.keys(bulkSelected);
              if (!confirm(lang === "fr"
                ? "Approuver " + ids.length + " item(s) ?"
                : "Approve " + ids.length + " item(s)?")) return;
              setBulkBusy(true);
              try {
                for (const id of ids) {
                  const it = liveMapped.find((x) => x.id === id);
                  if (!it) continue;
                  const nextStep = (it.step || 0) + 1;
                  const isFinal = nextStep >= (it.total_steps || 1);
                  await window.melr.updateValidationItem(id, {
                    current_step: isFinal ? (it.step || 0) : nextStep,
                    state: isFinal ? "approved" : "in_review",
                    current_assignee_id: null,
                  });
                  await window.melr.createValidationAction(id, {
                    action: "approve", step_index: it.step || 0, note: "(bulk)",
                  });
                }
                await refreshQueue();
                setBulkSelected({});
                alert(ids.length + " " + (lang === "fr" ? "approuvé(s)." : "approved."));
              } catch (e) { alert(e.message); }
              finally { setBulkBusy(false); }
            }}>
            <Icon.check /> {lang === "fr" ? "Approuver tout" : "Approve all"}
          </button>
          <button className="btn sm danger" disabled={bulkBusy || Object.keys(bulkSelected).length === 0}
            onClick={async () => {
              const ids = Object.keys(bulkSelected);
              const note = window.prompt(lang === "fr"
                ? "Note (optionnelle) pour le renvoi de " + ids.length + " item(s) :"
                : "Note (optional) for sending back " + ids.length + " item(s):", "");
              if (note === null) return;
              setBulkBusy(true);
              try {
                for (const id of ids) {
                  const it = liveMapped.find((x) => x.id === id);
                  if (!it) continue;
                  await window.melr.updateValidationItem(id, {
                    current_step: 0, state: "sent_back", current_assignee_id: null,
                  });
                  await window.melr.createValidationAction(id, {
                    action: "send_back", step_index: it.step || 0,
                    note: note || "(bulk)",
                  });
                }
                await refreshQueue();
                setBulkSelected({});
                alert(ids.length + " " + (lang === "fr" ? "renvoyé(s)." : "sent back."));
              } catch (e) { alert(e.message); }
              finally { setBulkBusy(false); }
            }}>
            <Icon.x /> {lang === "fr" ? "Renvoyer tout" : "Send back all"}
          </button>
        </div>
      )}

      <div className="wf-split">
        {/* Queue list */}
        <div className="wf-queue card">
          <div className="card-head">
            <div className="card-title">{lang === "fr" ? "File d'attente" : "Queue"} <span className="muted">· {filteredItems.length}</span></div>
            <div className="muted" style={{ fontSize: 11 }}>
              {bulkMode
                ? (lang === "fr" ? "Mode sélection multiple actif" : "Bulk selection mode active")
                : (liveMapped.length === 0
                    ? (lang === "fr" ? "Démo · aucune validation live" : "Demo · no live validations")
                    : (lang === "fr" ? "Trié par priorité" : "Sorted by priority"))}
            </div>
          </div>
          <div className="card-body flush wf-list">
            {filteredItems.length === 0 && (
              <div style={{ padding: 22, textAlign: "center", color: "var(--text-faint)", fontSize: 13 }}>
                {lang === "fr" ? "Aucun item dans ce filtre." : "No item in this filter."}
              </div>
            )}
            {filteredItems.map((it) => {
              const Ic = Icon[TYPE_ICON[it.type]];
              const tone = TYPE_TONE[it.type];
              const overdue = it.sla === 0;
              const bulkEligible = it.isLive && ["submitted", "in_review", "sent_back"].includes(it.state);
              const bulkChecked = !!bulkSelected[it.id];
              return (
                <button key={it.id} className={"wf-item" + (sel === it.id ? " active" : "")}
                  onClick={(e) => {
                    if (bulkMode && bulkEligible) { e.preventDefault(); toggleBulkItem(it.id); }
                    else setSel(it.id);
                  }}>
                  {bulkMode && (
                    <div style={{ marginRight: 6, display: "flex", alignItems: "center" }}
                      onClick={(e) => { e.stopPropagation(); if (bulkEligible) toggleBulkItem(it.id); }}>
                      <input type="checkbox" checked={bulkChecked} disabled={!bulkEligible}
                        onChange={() => {}} style={{ cursor: bulkEligible ? "pointer" : "not-allowed" }} />
                    </div>
                  )}
                  <div className={"wf-item-icon tone-" + tone}><Ic /></div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div className="wf-item-h">
                      <span className="strong wf-item-title">{it.title}</span>
                      {it.isLive
                        ? <span className="pill tiny" style={{ background: "#dcfce7", color: "#166534", border: "1px solid #86efac" }}>LIVE</span>
                        : <span className="pill tiny" style={{ background: "var(--bg-sunken)", color: "var(--text-faint)", border: "1px solid var(--line)" }}>{lang === "fr" ? "DEMO" : "DEMO"}</span>
                      }
                      {it.state === "approved"  && <span className="pill green dot tiny">{lang === "fr" ? "Approuvé" : "Approved"}</span>}
                      {it.state === "sent_back" && <span className="pill amber dot tiny">{lang === "fr" ? "Renvoyé" : "Sent back"}</span>}
                      {it.state === "rejected"  && <span className="pill red dot tiny">{lang === "fr" ? "Rejeté" : "Rejected"}</span>}
                      {overdue && <span className="pill red dot tiny">{lang === "fr" ? "RETARD" : "OVERDUE"}</span>}
                      {it.prio === "high" && !overdue && <span className="pill amber dot tiny">P1</span>}
                      {it.assignee_id && it.assignee_id === currentUserId && (
                        <span className="pill tiny" style={{ background: "#fef3c7", color: "#92400e", border: "1px solid #fcd34d" }}
                          title={lang === "fr" ? "Item assigné à vous" : "Assigned to you"}>
                          {lang === "fr" ? "À VOUS" : "TO YOU"}
                        </span>
                      )}
                    </div>
                    <div className="wf-item-meta">
                      <span className="mono text-faint">{it.id}</span>
                      <span className="dotsep"></span>
                      <span className="muted">{it.project}</span>
                      <span className="dotsep"></span>
                      <span className="muted">{it.module}</span>
                      <span className="dotsep"></span>
                      <span className="row gap-xs"><span className="avatar xxs" style={{ background: avColorW(it.who) }}>{initialsW(it.who)}</span><span className="muted">{it.who}</span></span>
                    </div>
                    <div className="wf-item-foot">
                      <WFChainMini step={it.step} total={CHAIN[it.type].length} />
                      <span className="text-faint" style={{ fontSize: 11 }}>{it.submitted}</span>
                      <span className="row gap-xs text-faint" style={{ fontSize: 11, marginLeft: "auto" }}>
                        <Icon.message className="xxs" /> {it.comments}
                        <Icon.fileText className="xxs" style={{ marginLeft: 6 }} /> {it.evidence}
                      </span>
                    </div>
                  </div>
                </button>
              );
            })}
          </div>
        </div>

        {/* Detail panel */}
        <div className="wf-detail">
          {!selected ? (
            <div className="card" style={{ padding: 40, textAlign: "center", color: "var(--text-faint)" }}>
              {lang === "fr"
                ? "Sélectionnez un item dans la file pour voir le détail."
                : "Pick an item from the queue to see the detail."}
            </div>
          ) : (
          <WFDetail
            item={selected}
            chain={CHAIN[selected.type]}
            activity={ACTIVITY}
            lang={lang}
            currentUserId={currentUserId}
            typeTone={TYPE_TONE[selected.type]}
            typeIcon={TYPE_ICON[selected.type]}
            onAction={async (kind, extra) => {
              if (!selected.isLive) {
                window.alert(lang === "fr"
                  ? "Cet item est de démonstration (pas dans la base de données). Action ignorée."
                  : "This is a demo item (not in the database). Action ignored.");
                return;
              }
              try {
                const currStep = selected.step || 0;
                const total = selected.total_steps || (CHAIN[selected.type] ? CHAIN[selected.type].length : 1);
                if (kind === "approve") {
                  // Advance one step unless this is the final approval.
                  // Clear current_assignee_id on advance so the next-step
                  // user can self-assign or be assigned.
                  const nextStep = currStep + 1;
                  const isFinal = nextStep >= total;
                  await window.melr.updateValidationItem(selected.id, {
                    current_step: isFinal ? currStep : nextStep,
                    state: isFinal ? "approved" : "in_review",
                    current_assignee_id: null,
                  });
                  await window.melr.createValidationAction(selected.id, {
                    action: "approve",
                    step_index: currStep,
                    note: extra && extra.note,
                  });
                } else if (kind === "send_back") {
                  // Reset to first step, owner must re-submit
                  await window.melr.updateValidationItem(selected.id, {
                    current_step: 0,
                    state: "sent_back",
                  });
                  await window.melr.createValidationAction(selected.id, {
                    action: "send_back",
                    step_index: currStep,
                    note: extra && extra.note,
                  });
                } else if (kind === "comment") {
                  await window.melr.createValidationAction(selected.id, {
                    action: "comment",
                    step_index: currStep,
                    note: extra && extra.note,
                  });
                } else if (kind === "delegate") {
                  // Delegation also re-assigns the item to the chosen user
                  await window.melr.assignValidationItem(selected.id, extra && extra.delegated_to);
                  await window.melr.createValidationAction(selected.id, {
                    action: "delegate",
                    step_index: currStep,
                    note: extra && extra.note,
                    attachments: { delegated_to: extra && extra.delegated_to },
                  });
                } else if (kind === "self_assign") {
                  await window.melr.assignValidationItem(selected.id, currentUserId);
                }
                await refreshQueue();
              } catch (e) {
                window.alert((lang === "fr" ? "Erreur : " : "Error: ") + e.message);
              }
            }}
          />
          )}
        </div>
      </div>

      {/* Workflow templates / configuration */}
      <div className="card" style={{ marginTop: 16 }}>
        <div className="card-head">
          <div className="card-title">{lang === "fr" ? "Modèles de flux configurés" : "Configured workflow templates"}</div>
          <button className="btn sm ghost"><Icon.plus /> {lang === "fr" ? "Nouveau modèle" : "New template"}</button>
        </div>
        <div className="card-body flush">
          <table className="tbl">
            <thead>
              <tr>
                <th>{lang === "fr" ? "Type d'objet" : "Object type"}</th>
                <th>{lang === "fr" ? "Étapes" : "Steps"}</th>
                <th>{lang === "fr" ? "Chaîne d'approbation" : "Approval chain"}</th>
                <th className="num">SLA</th>
                <th className="num">{lang === "fr" ? "Auto-rappel" : "Auto-remind"}</th>
                <th>{lang === "fr" ? "Délégation" : "Delegation"}</th>
                <th>{lang === "fr" ? "Statut" : "Status"}</th>
              </tr>
            </thead>
            <tbody>
              {[
                { t: lang === "fr" ? "Mise à jour indicateur" : "Indicator update", steps: 4, chain: CHAIN.indicator, sla: "3 j", remind: "48 h", deleg: lang === "fr" ? "Activée" : "Enabled", active: true },
                { t: lang === "fr" ? "Évaluation ex ante" : "Ex-ante appraisal", steps: 4, chain: CHAIN.exante, sla: "10 j", remind: "72 h", deleg: lang === "fr" ? "Activée" : "Enabled", active: true },
                { t: lang === "fr" ? "Audit DQA" : "DQA audit", steps: 3, chain: CHAIN.dqa, sla: "5 j", remind: "48 h", deleg: lang === "fr" ? "Désactivée" : "Disabled", active: true },
                { t: lang === "fr" ? "Rapport périodique" : "Periodic report", steps: 5, chain: CHAIN.report, sla: "14 j", remind: "72 h", deleg: lang === "fr" ? "Activée" : "Enabled", active: true },
                { t: lang === "fr" ? "Modification baseline" : "Baseline modification", steps: 3, chain: CHAIN.dqa, sla: "7 j", remind: "48 h", deleg: lang === "fr" ? "Activée" : "Enabled", active: false },
              ].map((tpl, i) => (
                <tr key={i}>
                  <td className="strong">{tpl.t}</td>
                  <td className="num mono">{tpl.steps}</td>
                  <td><WFChainHorz chain={tpl.chain} /></td>
                  <td className="num mono">{tpl.sla}</td>
                  <td className="num mono">{tpl.remind}</td>
                  <td className="muted">{tpl.deleg}</td>
                  <td>{tpl.active ? <span className="pill green dot">{lang === "fr" ? "Actif" : "Active"}</span> : <span className="pill dot" style={{ background: "var(--bg-sunken)", color: "var(--text-faint)" }}>{lang === "fr" ? "Brouillon" : "Draft"}</span>}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
      {tplOpen && <WFTemplatesModal lang={lang} onClose={() => setTplOpen(false)} />}
    </div>
  );
}

// ============================================================================
// Configurer flux — CRUD over validation_templates
// ============================================================================
function WFTemplatesModal({ lang, onClose }) {
  const { data: rows, loading, refresh } = window.melr.useValidationTemplates();
  const [editingId, setEditingId] = useStateW(null);
  const [draft, setDraft] = useStateW(null);
  const [busy, setBusy] = useStateW(false);
  const [err, setErr] = useStateW(null);

  // Object types the validation system currently knows about. Sourced from
  // grep across the codebase: screens-audit.jsx (sat_evaluation, dvt_audit),
  // screens-reporting.jsx (report), plus a placeholder for indicator_value
  // for future use. The list maps each technical slug to a user-friendly
  // bilingual label.
  const OBJECT_TYPES = [
    { value: "sat_evaluation",  fr: "SAT — Évaluation système d'audit", en: "SAT — System audit evaluation" },
    { value: "dvt_audit",       fr: "DVT — Vérification de données",    en: "DVT — Data verification" },
    { value: "report",          fr: "Rapport bailleur / projet",         en: "Donor / project report" },
    { value: "indicator_value", fr: "Valeur d'indicateur",               en: "Indicator value" },
    { value: "exante",          fr: "Dossier ex-ante",                   en: "Ex-ante dossier" },
    { value: "baseline",        fr: "Baseline / état initial",           en: "Baseline" },
  ];

  // Default catalogue of step labels — the most common roles in an M&E
  // validation chain. The user picks the ones that apply via checkboxes;
  // the order in the array determines the order in the chain.
  const STEP_CATALOG = [
    { id: "submission",  fr: "Saisie",              en: "Submission" },
    { id: "me_review",   fr: "Vérification S&E",    en: "M&E review" },
    { id: "data_check",  fr: "Contrôle DQA",        en: "DQA check" },
    { id: "project_lead",fr: "Chef de projet",      en: "Project lead" },
    { id: "me_lead",     fr: "Responsable S&E",     en: "M&E lead" },
    { id: "direction",   fr: "Direction",           en: "Direction" },
    { id: "donor",       fr: "Bailleur",            en: "Donor" },
  ];

  const newDraft = () => ({
    object_type: "",
    steps_picked: [],  // array of step ids from STEP_CATALOG (in chain order)
    steps_text: "",    // legacy free-text mode — only used when an existing
                       // template has a custom step label not in STEP_CATALOG
    sla_days: 5,
    reminder_hours: 48,
    active: true,
  });

  const onAdd = () => { setEditingId("new"); setDraft(newDraft()); };

  // Try to match an existing template's step list back onto our catalogue.
  // Strategy: for each saved step, look for a STEP_CATALOG entry whose
  // FR or EN label matches case-insensitively. Anything we can't match
  // we leave as free-text in steps_text so the user can still edit it.
  const reverseStepsToPicked = (steps) => {
    if (!Array.isArray(steps)) return { picked: [], extra: "" };
    const picked = [];
    const extra = [];
    for (const s of steps) {
      const label = typeof s === "string" ? s : (s.label || s.role || "");
      if (!label) continue;
      const lc = label.toLowerCase().trim();
      const match = STEP_CATALOG.find(
        (c) => c.fr.toLowerCase() === lc || c.en.toLowerCase() === lc || c.id === lc
      );
      if (match && !picked.includes(match.id)) picked.push(match.id);
      else if (!match) extra.push(label);
    }
    return { picked, extra: extra.join(", ") };
  };

  const onEdit = (r) => {
    setEditingId(r.id);
    const { picked, extra } = reverseStepsToPicked(r.steps);
    setDraft({
      object_type: r.object_type || "",
      steps_picked: picked,
      steps_text: extra,
      sla_days: r.sla_days || 5,
      reminder_hours: r.reminder_hours || 48,
      active: r.active !== false,
    });
  };

  const onSave = async () => {
    if (!draft || !draft.object_type.trim()) { setErr(lang === "fr" ? "Type d'objet requis." : "Object type required."); return; }
    // Build the steps list: picked checkboxes first (in catalogue order),
    // then any free-text additions appended at the end.
    const pickedLabels = (draft.steps_picked || []).map((id) => {
      const c = STEP_CATALOG.find((x) => x.id === id);
      return c ? (lang === "fr" ? c.fr : c.en) : null;
    }).filter(Boolean);
    const extraLabels = (draft.steps_text || "").split(",").map((s) => s.trim()).filter(Boolean);
    const labels = [...pickedLabels, ...extraLabels];
    if (labels.length === 0) { setErr(lang === "fr" ? "Au moins une étape." : "At least one step."); return; }
    setBusy(true); setErr(null);
    try {
      const payload = {
        object_type: draft.object_type.trim(),
        steps: labels.map((label) => ({ label })),
        sla_days: parseInt(draft.sla_days, 10) || null,
        reminder_hours: parseInt(draft.reminder_hours, 10) || null,
        active: !!draft.active,
      };
      if (editingId === "new") {
        await window.melr.validationTemplatesCrud.create(payload);
      } else {
        await window.melr.validationTemplatesCrud.update(editingId, payload);
      }
      await refresh();
      setEditingId(null); setDraft(null);
    } catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  const toggleStep = (id) => {
    if (!draft) return;
    const cur = draft.steps_picked || [];
    if (cur.includes(id)) {
      setDraft({ ...draft, steps_picked: cur.filter((x) => x !== id) });
    } else {
      // Add in catalogue order rather than click order so the chain
      // displays consistently regardless of which boxes the user ticks first.
      const order = STEP_CATALOG.map((c) => c.id);
      const next = [...cur, id].sort((a, b) => order.indexOf(a) - order.indexOf(b));
      setDraft({ ...draft, steps_picked: next });
    }
  };

  const onDelete = async (id) => {
    if (!confirm(lang === "fr" ? "Supprimer ce modèle ?" : "Delete this template?")) return;
    setBusy(true); setErr(null);
    try {
      await window.melr.validationTemplatesCrud.remove(id);
      await refresh();
    } catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  const stepsAsText = (r) => Array.isArray(r.steps)
    ? r.steps.map((s) => (typeof s === "string" ? s : (s.label || s.role || ""))).filter(Boolean).join(" → ")
    : "—";

  const inp = { width: "100%", padding: "6px 8px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 12.5, background: "var(--bg, white)", color: "var(--text)", boxSizing: "border-box", fontFamily: "inherit" };
  const lbl = { display: "block", fontSize: 10.5, color: "var(--text-faint)", marginBottom: 3, textTransform: "uppercase", letterSpacing: "0.04em", marginTop: 8 };

  return (
    <div onClick={() => !busy && onClose()} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", zIndex: 9999,
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: "var(--bg, white)", color: "var(--text, #111)",
        padding: 0, borderRadius: 10, width: 800, maxWidth: "95vw", maxHeight: "90vh",
        boxShadow: "0 12px 36px rgba(0,0,0,.30)",
        display: "flex", flexDirection: "column",
      }}>
        <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 10 }}>
          <div style={{ fontSize: 16, fontWeight: 600 }}>
            {lang === "fr" ? "Configurer les flux d'approbation" : "Configure approval flows"}
          </div>
          <span className="pill">{(rows || []).length}</span>
          <span style={{ flex: 1 }} />
          <button className="btn sm primary" onClick={onAdd} disabled={busy || editingId !== null}>
            <Icon.plus /> {lang === "fr" ? "Nouveau modèle" : "New template"}
          </button>
          <button className="btn sm" onClick={onClose} disabled={busy}>
            {lang === "fr" ? "Fermer" : "Close"}
          </button>
        </div>

        <div style={{ flex: 1, overflowY: "auto", padding: 0 }}>
          {loading && <div style={{ padding: 20, color: "var(--text-faint)", textAlign: "center" }}>{lang === "fr" ? "Chargement…" : "Loading…"}</div>}

          {/* Editor row */}
          {editingId !== null && draft && (
            <div style={{ padding: 14, background: "var(--bg-sunken, #f9fafb)", borderBottom: "1px solid var(--line)" }}>
              <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>
                {editingId === "new"
                  ? (lang === "fr" ? "Nouveau modèle" : "New template")
                  : (lang === "fr" ? "Modifier le modèle" : "Edit template")}
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 10 }}>
                <div>
                  <label style={lbl}>{lang === "fr" ? "Type d'objet" : "Object type"} *</label>
                  <select style={inp} value={draft.object_type}
                    onChange={(e) => setDraft({ ...draft, object_type: e.target.value })}>
                    <option value="">— {lang === "fr" ? "Choisir…" : "Choose…"} —</option>
                    {OBJECT_TYPES.map((o) => (
                      <option key={o.value} value={o.value}>
                        {lang === "fr" ? o.fr : o.en}
                      </option>
                    ))}
                  </select>
                </div>
                <div>
                  <label style={lbl}>SLA ({lang === "fr" ? "jours" : "days"})</label>
                  <input type="number" min={1} style={inp} value={draft.sla_days}
                    onChange={(e) => setDraft({ ...draft, sla_days: e.target.value })} />
                </div>
                <div>
                  <label style={lbl}>{lang === "fr" ? "Rappel (h)" : "Reminder (h)"}</label>
                  <input type="number" min={1} style={inp} value={draft.reminder_hours}
                    onChange={(e) => setDraft({ ...draft, reminder_hours: e.target.value })} />
                </div>
                <div>
                  <label style={lbl}>{lang === "fr" ? "Statut" : "Status"}</label>
                  <label style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 8 }}>
                    <input type="checkbox" checked={draft.active}
                      onChange={(e) => setDraft({ ...draft, active: e.target.checked })} />
                    <span style={{ fontSize: 12 }}>{lang === "fr" ? "Actif" : "Active"}</span>
                  </label>
                </div>
              </div>

              <label style={lbl}>
                {lang === "fr" ? "Étapes de la chaîne" : "Chain steps"} *
                <span style={{ marginLeft: 6, textTransform: "none", letterSpacing: 0, color: "var(--text-faint)" }}>
                  ({lang === "fr" ? "cochez celles qui s'appliquent" : "tick those that apply"})
                </span>
              </label>
              <div style={{
                display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
                gap: 6, padding: "6px 0",
              }}>
                {STEP_CATALOG.map((c) => {
                  const checked = (draft.steps_picked || []).includes(c.id);
                  return (
                    <label key={c.id} style={{
                      display: "flex", alignItems: "center", gap: 6,
                      padding: "5px 8px", border: "1px solid var(--line)", borderRadius: 6,
                      background: checked ? "var(--brand-tint, #eff6ff)" : "transparent",
                      cursor: "pointer", fontSize: 12.5,
                    }}>
                      <input type="checkbox" checked={checked} onChange={() => toggleStep(c.id)} />
                      <span>{lang === "fr" ? c.fr : c.en}</span>
                    </label>
                  );
                })}
              </div>
              <label style={{ ...lbl, marginTop: 8 }}>
                {lang === "fr" ? "Étapes supplémentaires (optionnel, séparées par des virgules)" : "Extra steps (optional, comma-separated)"}
              </label>
              <input style={inp} value={draft.steps_text}
                placeholder={lang === "fr" ? "ex : Comité technique, Bailleur secondaire…" : "e.g. Technical committee, Secondary donor…"}
                onChange={(e) => setDraft({ ...draft, steps_text: e.target.value })} />

              {err && <div style={{ color: "#b91c1c", fontSize: 12, marginTop: 8 }}>{err}</div>}

              <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 10 }}>
                <button className="btn sm" onClick={() => { setEditingId(null); setDraft(null); setErr(null); }} disabled={busy}>
                  {lang === "fr" ? "Annuler" : "Cancel"}
                </button>
                <button className="btn sm primary" onClick={onSave} disabled={busy}>
                  {busy ? "…" : (lang === "fr" ? "Enregistrer" : "Save")}
                </button>
              </div>
            </div>
          )}

          {/* List */}
          <table className="tbl" style={{ width: "100%" }}>
            <thead>
              <tr>
                <th>{lang === "fr" ? "Type d'objet" : "Object type"}</th>
                <th>{lang === "fr" ? "Chaîne d'étapes" : "Steps chain"}</th>
                <th className="num">SLA</th>
                <th className="num">{lang === "fr" ? "Rappel" : "Remind"}</th>
                <th>{lang === "fr" ? "Statut" : "Status"}</th>
                <th style={{ width: 80 }}></th>
              </tr>
            </thead>
            <tbody>
              {(rows || []).length === 0 && !loading && (
                <tr><td colSpan={6} style={{ padding: 22, textAlign: "center", color: "var(--text-faint)" }}>
                  {lang === "fr"
                    ? "Aucun modèle pour votre organisation. Cliquez « Nouveau modèle » pour commencer."
                    : "No template for your organization. Click 'New template' to start."}
                </td></tr>
              )}
              {(rows || []).map((r) => {
                const ot = OBJECT_TYPES.find((o) => o.value === r.object_type);
                const otLabel = ot ? (lang === "fr" ? ot.fr : ot.en) : null;
                return (
                <tr key={r.id}>
                  <td>
                    <div className="strong" style={{ fontSize: 12.5 }}>{otLabel || r.object_type}</div>
                    {otLabel && <div className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)" }}>{r.object_type}</div>}
                  </td>
                  <td className="muted">{stepsAsText(r)}</td>
                  <td className="num mono">{r.sla_days != null ? r.sla_days + " j" : "—"}</td>
                  <td className="num mono">{r.reminder_hours != null ? r.reminder_hours + " h" : "—"}</td>
                  <td>
                    {r.active
                      ? <span className="pill green dot">{lang === "fr" ? "Actif" : "Active"}</span>
                      : <span className="pill" style={{ background: "var(--bg-sunken)", color: "var(--text-faint)" }}>{lang === "fr" ? "Inactif" : "Inactive"}</span>}
                  </td>
                  <td>
                    <div style={{ display: "flex", gap: 4 }}>
                      <button className="btn sm ghost" onClick={() => onEdit(r)} disabled={busy} title={lang === "fr" ? "Modifier" : "Edit"}>
                        <Icon.edit />
                      </button>
                      <button className="btn sm ghost" onClick={() => onDelete(r.id)} disabled={busy} title={lang === "fr" ? "Supprimer" : "Delete"}>
                        <Icon.trash />
                      </button>
                    </div>
                  </td>
                </tr>
                );
              })}
            </tbody>
          </table>
        </div>

        <div style={{ padding: "10px 18px", borderTop: "1px solid var(--line)", fontSize: 11, color: "var(--text-faint)" }}>
          {lang === "fr"
            ? "Note : les flux configurés ici sont stockés au niveau de votre organisation. Le système de soumission s'y référera (object_type = SAT, DVT, report…)."
            : "Note: flows configured here are stored at organization level. The submission system will reference them by object_type."}
        </div>
      </div>
    </div>
  );
}

function WFChainMini({ step, total }) {
  return (
    <div className="wf-chain-mini">
      {Array.from({ length: total }).map((_, i) => (
        <div key={i} className={"wf-chain-pip" + (i < step ? " done" : i === step ? " cur" : "")}></div>
      ))}
    </div>
  );
}

function WFChainHorz({ chain }) {
  return (
    <div className="wf-chain-horz">
      {chain.map((c, i) => (
        <React.Fragment key={i}>
          <span className="wf-chain-h-chip">{c.r}</span>
          {i < chain.length - 1 && <Icon.chevronRight className="xxs muted" />}
        </React.Fragment>
      ))}
    </div>
  );
}

function WFDetail({ item, chain, activity, lang, typeTone, typeIcon, onAction, currentUserId }) {
  const Ic = Icon[typeIcon];
  const [openModal, setOpenModal] = useStateW(null); // null | 'comment' | 'delegate' | 'approve' | 'send_back'
  // Live timeline of actions on this item (only for live database items)
  const { data: liveActions } = window.melr.useValidationActions(item.isLive ? item.id : null);

  // ---- Inline discussion composer state ----
  const [commentText, setCommentText]       = useStateW("");
  const [commentMentions, setCommentMentions] = useStateW([]);  // [{id, name}]
  const [commentFiles, setCommentFiles]     = useStateW([]);    // [{name, size, type}]
  const [orgUsers, setOrgUsers]             = useStateW([]);
  const [mentionOpen, setMentionOpen]       = useStateW(false);
  const [mentionPos, setMentionPos]         = useStateW(null);  // { left, top, width }
  const [commentBusy, setCommentBusy]       = useStateW(false);
  const fileInputRef    = React.useRef(null);
  const mentionBtnRef   = React.useRef(null);

  // Close the mention dropdown on Escape or when clicking outside
  React.useEffect(() => {
    if (!mentionOpen) return;
    const onKey = (e) => { if (e.key === "Escape") setMentionOpen(false); };
    const onScroll = () => setMentionOpen(false);
    window.addEventListener("keydown", onKey);
    window.addEventListener("scroll", onScroll, true);
    return () => {
      window.removeEventListener("keydown", onKey);
      window.removeEventListener("scroll", onScroll, true);
    };
  }, [mentionOpen]);

  // Lazy-load org users when @mention is first opened
  const ensureOrgUsers = async () => {
    if (orgUsers.length > 0) return orgUsers;
    try {
      const rows = await window.melr.fetchOrgProfiles();
      setOrgUsers(rows || []);
      return rows || [];
    } catch (e) { console.error(e); return []; }
  };
  const onMentionClick = async () => {
    // Compute fixed-position anchor from the button rect so the dropdown
    // floats above any parent overflow:hidden / overflow:auto container.
    const rect = mentionBtnRef.current && mentionBtnRef.current.getBoundingClientRect();
    if (rect) {
      const width = 300;
      const maxH = 280;
      // Default: open downward below the button. If there isn't enough room,
      // open upward above the button instead.
      const spaceBelow = window.innerHeight - rect.bottom;
      const openUp = spaceBelow < maxH + 20 && rect.top > maxH + 20;
      setMentionPos({
        left: Math.max(8, Math.min(rect.left, window.innerWidth - width - 8)),
        top: openUp ? Math.max(8, rect.top - maxH - 4) : rect.bottom + 4,
        width,
        maxHeight: maxH,
      });
    }
    await ensureOrgUsers();
    setMentionOpen((v) => !v);
  };
  const onMentionPick = (user) => {
    const name = user.full_name || user.email || "user";
    setCommentText((t) => (t ? t + " " : "") + "@" + name + " ");
    setCommentMentions((m) => (m.some((x) => x.id === user.id) ? m : [...m, { id: user.id, name }]));
    setMentionOpen(false);
  };
  const onAttachClick = () => { fileInputRef.current && fileInputRef.current.click(); };
  const onFilesPicked = (e) => {
    const files = Array.from(e.target.files || []);
    if (files.length === 0) return;
    // Keep raw File objects so we can upload them on send.
    setCommentFiles((arr) => [...arr, ...files]);
    e.target.value = "";
  };
  const removeFile = (idx) => setCommentFiles((arr) => arr.filter((_, i) => i !== idx));
  const removeMention = (idx) => setCommentMentions((arr) => arr.filter((_, i) => i !== idx));

  const onSendComment = async () => {
    if (!item.isLive) {
      alert(lang === "fr"
        ? "Cet item est de démonstration. Comment ignoré."
        : "Demo item. Comment ignored.");
      return;
    }
    const text = commentText.trim();
    if (!text && commentFiles.length === 0) return;
    setCommentBusy(true);
    try {
      // 1) Upload any files to Storage; collect their paths
      const uploadedFiles = [];
      for (const f of commentFiles) {
        try {
          const meta = await window.melr.uploadValidationAttachment(item.id, f);
          uploadedFiles.push(meta);  // { path, name, size, type }
        } catch (e) {
          console.error("[upload] " + f.name, e);
          // Fall back to metadata-only if storage rejects (e.g. bucket missing)
          uploadedFiles.push({ name: f.name, size: f.size, type: f.type || "", uploadError: e.message });
        }
      }
      // 2) Persist the comment with mentions + uploaded file paths in attachments
      await window.melr.createValidationAction(item.id, {
        action: "comment",
        step_index: item.step || 0,
        note: text,
        attachments: {
          mentions: commentMentions.map((m) => ({ id: m.id, name: m.name })),
          files: uploadedFiles,
        },
      });
      // Notify each @mentioned user (skip if they're the current user)
      if (commentMentions.length > 0) {
        const rows = commentMentions
          .filter((m) => !currentUserId || m.id !== currentUserId)
          .map((m) => ({
            user_id: m.id,
            title: lang === "fr" ? "Vous avez été mentionné" : "You were mentioned",
            body: (lang === "fr" ? "Sur « " : "On '") + (item.title || "—") + (lang === "fr" ? " »" : "'") +
              (text ? " — " + text.slice(0, 200) : ""),
            kind: "mention",
            severity: "info",
            link_url: "/workflow",
          }));
        if (rows.length > 0) await window.melr.createBulkNotifications(rows);
      }
      setCommentText("");
      setCommentMentions([]);
      setCommentFiles([]);
    } catch (e) {
      alert((lang === "fr" ? "Erreur : " : "Error: ") + e.message);
    } finally { setCommentBusy(false); }
  };
  return (
    <div className="card wf-detail-card">
      <div className="wf-detail-head">
        <div className="row gap-sm">
          <div className={"wf-item-icon lg tone-" + typeTone}><Ic /></div>
          <div style={{ flex: 1 }}>
            <div className="row gap-xs" style={{ marginBottom: 4 }}>
              <span className="mono text-faint" style={{ fontSize: 11 }}>{item.id}</span>
              <span className="dotsep"></span>
              <span className="muted" style={{ fontSize: 11 }}>{item.module} · {item.project}</span>
            </div>
            <div className="wf-detail-title">{item.title}</div>
          </div>
          <button className="iconbtn"><Icon.more /></button>
        </div>

        <div className="wf-detail-stats">
          <div><div className="text-faint" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: 0.4 }}>{lang === "fr" ? "Valeur" : "Value"}</div><div className="mono strong">{item.value}</div></div>
          <div>
            <div className="text-faint" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: 0.4 }}>
              {lang === "fr" ? "Assigné à" : "Assigned to"}
            </div>
            <div className="row gap-xs">
              {item.assignee
                ? <><span className="avatar xxs" style={{ background: avColorW(item.assignee.full_name || item.assignee.email) }}>{initialsW(item.assignee.full_name || item.assignee.email)}</span><span className="strong">{item.assignee.full_name || item.assignee.email}</span></>
                : <span className="text-faint" style={{ fontSize: 12 }}>{lang === "fr" ? "(non assigné)" : "(unassigned)"}</span>}
            </div>
          </div>
          <div><div className="text-faint" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: 0.4 }}>{lang === "fr" ? "Soumis par" : "Submitted by"}</div><div className="row gap-xs"><span className="avatar xxs" style={{ background: avColorW(item.who) }}>{initialsW(item.who)}</span><span className="strong">{item.who}</span></div></div>
          <div><div className="text-faint" style={{ fontSize: 10.5, textTransform: "uppercase", letterSpacing: 0.4 }}>SLA</div><div className={"mono strong " + (item.sla === 0 ? "tone-red" : item.sla <= 1 ? "tone-amber" : "tone-green")}>{item.sla === 0 ? (lang === "fr" ? "En retard" : "Overdue") : (item.sla + " j")}</div></div>
        </div>
      </div>

      {/* Approval chain visualization */}
      <div className="wf-chain">
        {chain.map((c, i) => {
          const state = i < item.step ? "done" : i === item.step ? "cur" : "todo";
          return (
            <React.Fragment key={i}>
              <div className={"wf-chain-node " + state}>
                <div className="wf-chain-node-circle">
                  {state === "done" ? <Icon.check /> : state === "cur" ? <span className="mono">{i + 1}</span> : <span className="mono">{i + 1}</span>}
                </div>
                <div className="wf-chain-node-role">{c.r}</div>
                <div className="wf-chain-node-who">{c.who === "submitter" ? item.who : c.who}</div>
                <div className="wf-chain-node-action">{c.t}</div>
              </div>
              {i < chain.length - 1 && <div className={"wf-chain-link " + (i < item.step ? "done" : "")}></div>}
            </React.Fragment>
          );
        })}
      </div>

      {/* Action bar — for "your turn" */}
      <div className="wf-actions">
        <div className="wf-action-context">
          <Icon.shieldCheck className="sm" />
          <span style={{ fontSize: 12 }}>
            <span className="strong">{lang === "fr" ? "Votre action requise" : "Your action required"}</span>
            <span className="muted"> · {lang === "fr" ? "Étape" : "Step"} {item.step + 1} / {chain.length} — {chain[item.step] ? chain[item.step].t : "—"}</span>
          </span>
        </div>
        <div className="row gap-sm">
          {/* Self-assign only when item is live, unassigned, and user is not the submitter */}
          {item.isLive && !item.assignee_id && currentUserId && item.submitted_by !== currentUserId && (
            <button className="btn sm" onClick={() => onAction && onAction("self_assign")}
              title={lang === "fr" ? "M'assigner cet item" : "Assign this item to me"}>
              <Icon.user /> {lang === "fr" ? "Me l'assigner" : "Assign to me"}
            </button>
          )}
          <button className="btn sm" onClick={() => setOpenModal("comment")}><Icon.message /> {lang === "fr" ? "Commenter" : "Comment"}</button>
          <button className="btn sm" onClick={() => setOpenModal("delegate")}><Icon.users /> {lang === "fr" ? "Déléguer" : "Delegate"}</button>
          <button className="btn sm danger" onClick={() => setOpenModal("send_back")}><Icon.x /> {lang === "fr" ? "Renvoyer" : "Send back"}</button>
          <button className="btn sm success" onClick={() => setOpenModal("approve")}><Icon.check /> {lang === "fr" ? "Approuver" : "Approve"}</button>
        </div>
      </div>

      {/* Activity timeline */}
      <div className="wf-detail-body">
        <div className="wf-section-h">
          <span>{lang === "fr" ? "Activité" : "Activity"}</span>
          <span className="text-faint" style={{ fontSize: 11 }}>{activity.length} {lang === "fr" ? "événements" : "events"}</span>
        </div>
        <div className="wf-timeline">
          {/* Live actions from validation_actions (database) */}
          {liveActions && liveActions.map((a) => {
            const labels = {
              approve:   lang === "fr" ? "a approuvé" : "approved",
              send_back: lang === "fr" ? "a renvoyé"  : "sent back",
              reject:    lang === "fr" ? "a rejeté"   : "rejected",
              comment:   lang === "fr" ? "a commenté" : "commented",
              delegate:  lang === "fr" ? "a délégué"  : "delegated",
            };
            const tones = { approve: "green", send_back: "amber", reject: "red", comment: "accent", delegate: "violet" };
            const icons = { approve: "check", send_back: "x", reject: "x", comment: "message", delegate: "users" };
            const AIc = Icon[icons[a.action]] || Icon.info;
            const who = (a.actor && a.actor.full_name) || (a.actor && a.actor.email) || "—";
            return (
              <div key={a.id} className="wf-tl-item">
                <div className={"wf-tl-dot tone-" + (tones[a.action] || "accent")}><AIc /></div>
                <div className="wf-tl-body">
                  <div className="wf-tl-h">
                    <span className="strong">{who}</span>
                    <span className="muted"> · {labels[a.action] || a.action}</span>
                    <span className="text-faint" style={{ marginLeft: "auto", fontSize: 11 }}>
                      {new Date(a.created_at).toLocaleString(lang === "fr" ? "fr-FR" : "en-US")}
                    </span>
                  </div>
                  {a.note && <div className="wf-tl-note">{a.note}</div>}
                  {a.attachments && (a.attachments.files || []).length > 0 && (
                    <div className="row gap-xs" style={{ flexWrap: "wrap", marginTop: 6 }}>
                      {(a.attachments.files || []).map((f, idx) => (
                        <WFAttachmentChip key={idx} file={f} lang={lang} />
                      ))}
                    </div>
                  )}
                  {a.attachments && (a.attachments.mentions || []).length > 0 && (
                    <div className="row gap-xs" style={{ flexWrap: "wrap", marginTop: 4 }}>
                      {(a.attachments.mentions || []).map((m, idx) => (
                        <span key={idx} className="pill accent" style={{ fontSize: 10.5 }}>@{m.name || m.id}</span>
                      ))}
                    </div>
                  )}
                </div>
              </div>
            );
          })}
          {/* Demo activity fixture (always shown below the live trail) */}
          {activity.map((a, i) => {
            const AIc = Icon[a.icon] || Icon.info;
            return (
              <div key={"fix-" + i} className={"wf-tl-item" + (a.pending ? " pending" : "")}>
                <div className={"wf-tl-dot tone-" + a.tone}><AIc /></div>
                <div className="wf-tl-body">
                  <div className="wf-tl-h">
                    <span className="strong">{a.who}</span>
                    <span className="muted"> · {a.role}</span>
                    <span className="muted"> · {a.action}</span>
                    <span className="text-faint" style={{ marginLeft: "auto", fontSize: 11 }}>{a.when}</span>
                  </div>
                  {a.note && <div className="wf-tl-note">{a.note}</div>}
                </div>
              </div>
            );
          })}
        </div>

        <div className="wf-section-h" style={{ marginTop: 14 }}>
          <span>{lang === "fr" ? "Discussion" : "Discussion"}</span>
        </div>
        <div className="wf-compose" style={{ position: "relative" }}>
          <div className="avatar sm" style={{ background: "var(--accent)" }}>AD</div>
          <div style={{ flex: 1 }}>
            <textarea className="wf-textarea" rows="2"
              value={commentText}
              onChange={(e) => setCommentText(e.target.value)}
              placeholder={lang === "fr" ? "Ajouter un commentaire, mentionner @ une personne…" : "Add a comment, @ mention someone…"} />

            {/* Mentions + Files chips */}
            {(commentMentions.length > 0 || commentFiles.length > 0) && (
              <div className="row gap-xs" style={{ flexWrap: "wrap", marginTop: 4 }}>
                {commentMentions.map((m, i) => (
                  <span key={"m-" + i} className="pill accent" style={{ fontSize: 10.5, paddingRight: 2 }}>
                    @{m.name}
                    <button type="button" className="btn xs ghost" onClick={() => removeMention(i)}
                      style={{ padding: 0, marginLeft: 2 }} title={lang === "fr" ? "Retirer" : "Remove"}><Icon.x className="sm" /></button>
                  </span>
                ))}
                {commentFiles.map((f, i) => {
                  const name = f.name || "(file)";
                  return (
                    <span key={"f-" + i} className="pill" style={{ fontSize: 10.5, paddingRight: 2 }}
                      title={name + " · " + (f.size ? Math.round(f.size / 1024) + " KB" : "")}>
                      <Icon.fileText className="sm" /> {name.length > 24 ? name.slice(0, 24) + "…" : name}
                      <button type="button" className="btn xs ghost" onClick={() => removeFile(i)}
                        style={{ padding: 0, marginLeft: 2 }} title={lang === "fr" ? "Retirer" : "Remove"}><Icon.x className="sm" /></button>
                    </span>
                  );
                })}
              </div>
            )}

            <div className="row gap-sm" style={{ marginTop: 6 }}>
              <input ref={fileInputRef} type="file" multiple onChange={onFilesPicked} style={{ display: "none" }} />
              <button className="btn xs ghost" onClick={onAttachClick} disabled={commentBusy}>
                <Icon.upload /> {lang === "fr" ? "Pièce" : "Attach"}
              </button>
              <button ref={mentionBtnRef} className="btn xs ghost" onClick={onMentionClick} disabled={commentBusy}>
                <Icon.user /> @mention
              </button>
              <span style={{ flex: 1 }}></span>
              <button className="btn xs primary" onClick={onSendComment}
                disabled={commentBusy || (!commentText.trim() && commentFiles.length === 0) || !item.isLive}
                title={!item.isLive ? (lang === "fr" ? "Item démo — non envoyable" : "Demo item — cannot send") : ""}>
                <Icon.send /> {commentBusy ? "…" : (lang === "fr" ? "Envoyer" : "Send")}
              </button>
            </div>

            {/* Mention dropdown — fixed-position so parent overflow:hidden
                containers don't clip it. Closes on Escape, outside scroll. */}
            {mentionOpen && mentionPos && (
              <>
                {/* Click-outside catcher behind the dropdown */}
                <div onClick={() => setMentionOpen(false)} style={{
                  position: "fixed", inset: 0, background: "transparent", zIndex: 9000,
                }} />
                <div style={{
                  position: "fixed",
                  left: mentionPos.left, top: mentionPos.top,
                  width: mentionPos.width, maxHeight: mentionPos.maxHeight,
                  background: "var(--bg, white)", border: "1px solid var(--line)",
                  borderRadius: 6, boxShadow: "0 6px 24px rgba(0,0,0,.18)",
                  zIndex: 9001, overflowY: "auto",
                }}>
                  {orgUsers.length === 0 && (
                    <div style={{ padding: 10, fontSize: 11, color: "var(--text-faint)" }}>
                      {lang === "fr" ? "Chargement…" : "Loading…"}
                    </div>
                  )}
                  {orgUsers.map((u) => (
                    <button key={u.id} className="btn ghost"
                      onClick={(e) => { e.stopPropagation(); onMentionPick(u); }}
                      style={{
                        display: "flex", alignItems: "center", gap: 8,
                        width: "100%", textAlign: "left",
                        padding: "6px 10px", borderRadius: 0, fontSize: 12.5,
                        borderBottom: "1px solid var(--line-faint)",
                      }}>
                      <span className="avatar xxs" style={{ background: avColorW(u.full_name || u.email) }}>{initialsW(u.full_name || u.email)}</span>
                      <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{u.full_name || u.email}</span>
                      {u.full_name && u.email && <span className="text-faint" style={{ fontSize: 10.5, marginLeft: "auto" }}>{u.email}</span>}
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>
        </div>
      </div>
      {openModal && (
        <WFActionModal
          kind={openModal}
          lang={lang}
          onClose={() => setOpenModal(null)}
          onConfirm={async (extra) => {
            const kind = openModal;
            setOpenModal(null);
            if (onAction) await onAction(kind, extra);
          }}
        />
      )}
    </div>
  );
}

// ==================== ATTACHMENT CHIP ====================
// Renders an attachment chip; on click, requests a signed URL from
// Storage and opens it in a new tab. Falls back to a static
// chip when the attachment has no `path` (legacy metadata-only rows).
function WFAttachmentChip({ file, lang }) {
  const [busy, setBusy] = useStateW(false);
  const onOpen = async (e) => {
    e.preventDefault();
    if (!file.path) {
      alert(lang === "fr"
        ? "Pièce sans chemin de stockage (métadonnée seulement)."
        : "Attachment has no storage path (metadata only).");
      return;
    }
    setBusy(true);
    try {
      const url = await window.melr.getValidationAttachmentUrl(file.path);
      if (url) window.open(url, "_blank", "noopener");
    } catch (err) {
      alert((lang === "fr" ? "Erreur : " : "Error: ") + err.message);
    } finally { setBusy(false); }
  };
  const label = file.name || "(file)";
  const display = label.length > 28 ? label.slice(0, 28) + "…" : label;
  const sizeKb = file.size ? Math.round(file.size / 1024) + " KB" : "";
  return (
    <button onClick={onOpen} disabled={busy} className="pill"
      title={label + (sizeKb ? " · " + sizeKb : "") + (file.path ? "" : (lang === "fr" ? " (non téléchargeable)" : " (not downloadable)"))}
      style={{
        fontSize: 10.5, cursor: file.path ? "pointer" : "default",
        border: "1px solid var(--line)", background: "var(--bg-elev, white)",
        color: file.path ? "var(--accent)" : "var(--text-faint)",
        display: "inline-flex", alignItems: "center", gap: 4,
        padding: "2px 8px",
      }}>
      <Icon.fileText className="sm" /> {busy ? "…" : display}
      {file.path && <Icon.download className="sm" />}
    </button>
  );
}

// ==================== WORKFLOW ACTION MODAL ====================
// Single modal used for the four actions (comment / delegate /
// approve / send_back). The content adapts to `kind`.
function WFActionModal({ kind, lang, onClose, onConfirm }) {
  const [note, setNote] = useStateW("");
  const [delegatee, setDelegatee] = useStateW("");
  const [orgUsers, setOrgUsers] = useStateW([]);
  const [submitting, setSubmitting] = useStateW(false);
  const [err, setErr] = useStateW(null);

  // Load org users when the delegate modal opens.
  if (kind === "delegate" && orgUsers.length === 0 && err === null) {
    window.melr.fetchOrgProfiles()
      .then((rows) => setOrgUsers(rows))
      .catch((e) => setErr(e.message));
  }

  const titles = {
    comment:   lang === "fr" ? "Ajouter un commentaire" : "Add a comment",
    delegate:  lang === "fr" ? "Déléguer à un autre membre" : "Delegate to another member",
    approve:   lang === "fr" ? "Approuver cette validation" : "Approve this validation",
    send_back: lang === "fr" ? "Renvoyer pour révision" : "Send back for revision",
  };
  const confirmLabels = {
    comment:   lang === "fr" ? "Publier" : "Post",
    delegate:  lang === "fr" ? "Déléguer" : "Delegate",
    approve:   lang === "fr" ? "Confirmer l'approbation" : "Confirm approval",
    send_back: lang === "fr" ? "Renvoyer" : "Send back",
  };
  const confirmColors = {
    comment:   "#2563eb",
    delegate:  "#7c3aed",
    approve:   "#059669",
    send_back: "#d97706",
  };

  const submit = async (e) => {
    e.preventDefault();
    setErr(null); setSubmitting(true);
    try {
      const extra = { note: note.trim() || null };
      if (kind === "delegate") {
        if (!delegatee) throw new Error(lang === "fr" ? "Choisissez un destinataire." : "Pick a recipient.");
        extra.delegated_to = delegatee;
      }
      await onConfirm(extra);
    } catch (e2) {
      setErr(e2.message);
      setSubmitting(false);
    }
  };

  const inp = { padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13 };

  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.45)", zIndex: 9999,
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <form onClick={(e) => e.stopPropagation()} onSubmit={submit} style={{
        background: "var(--bg, white)", color: "var(--text, #111)",
        padding: 22, borderRadius: 10, width: 480, maxWidth: "92vw",
        boxShadow: "0 10px 30px rgba(0,0,0,.25)", display: "grid", gap: 10,
      }}>
        <div style={{ fontSize: 18, fontWeight: 600 }}>{titles[kind]}</div>

        {kind === "delegate" && (
          <label style={{ display: "grid", gap: 4 }}>
            <span style={{ fontSize: 11, opacity: 0.75 }}>
              {lang === "fr" ? "Déléguer à" : "Delegate to"}
            </span>
            <select required value={delegatee} onChange={(e) => setDelegatee(e.target.value)} style={inp}>
              <option value="">— {lang === "fr" ? "choisir un membre" : "pick a member"} —</option>
              {orgUsers.map((u) => (
                <option key={u.id} value={u.id}>
                  {u.full_name || u.email}{u.email && u.full_name ? " · " + u.email : ""}
                </option>
              ))}
            </select>
            {orgUsers.length === 0 && (
              <span style={{ fontSize: 11, opacity: 0.6 }}>
                {lang === "fr" ? "Chargement des membres…" : "Loading members…"}
              </span>
            )}
          </label>
        )}

        <label style={{ display: "grid", gap: 4 }}>
          <span style={{ fontSize: 11, opacity: 0.75 }}>
            {kind === "comment"
              ? (lang === "fr" ? "Votre commentaire" : "Your comment")
              : (lang === "fr" ? "Note (optionnel)" : "Note (optional)")}
          </span>
          <textarea
            required={kind === "comment"}
            value={note} onChange={(e) => setNote(e.target.value)}
            rows={4}
            placeholder={kind === "send_back"
              ? (lang === "fr" ? "Quels points doivent être révisés ?" : "What needs to be revised?")
              : (lang === "fr" ? "Texte du commentaire…" : "Comment text…")}
            style={{ ...inp, resize: "vertical" }}
          />
        </label>

        {err && <div style={{ color: "#b91c1c", fontSize: 12 }}>{err}</div>}

        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 4 }}>
          <button type="button" onClick={onClose} disabled={submitting}
            style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "transparent", cursor: "pointer" }}>
            {lang === "fr" ? "Annuler" : "Cancel"}
          </button>
          <button type="submit" disabled={submitting}
            style={{ padding: "8px 14px", borderRadius: 6, border: 0, background: confirmColors[kind], color: "white", cursor: "pointer", fontWeight: 600 }}>
            {submitting ? "…" : confirmLabels[kind]}
          </button>
        </div>
      </form>
    </div>
  );
}

function initialsW(name) { return name.split(/\s+/).slice(0, 2).map((s) => s[0]).join("").toUpperCase(); }
function avColorW(name) {
  let h = 0; for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0;
  return `oklch(0.78 0.08 ${Math.abs(h) % 360})`;
}

window.Workflow = Workflow;
