// app-pages.jsx — Scripts editor, Champs, Historique, Profil, Settings (wired to API)

// ─────── FileMaker step list (subset of common steps for syntax highlighting + autocomplete) ───────
const FM_STEPS = [
  "Set Error Capture","Set Error Logging","Allow User Abort","Perform Script","Exit Loop If","Exit Script","Halt Script",
  "Set Variable","Pause/Resume Script","Commit Transaction","Open Transaction","Revert Transaction",
  "Go to Layout","Go to Object","Go to Field","Go to Record/Request/Page","Go to Related Record","Go to Portal Row",
  "Enter Browse Mode","Enter Find Mode","Enter Preview Mode",
  "New Record/Request","Delete Record/Request","Duplicate Record/Request","Commit Records/Requests","Open Record/Request","Revert Record/Request",
  "Set Field","Set Field By Name","Insert Calculated Result","Insert Current Date","Insert Current Time","Insert Current User Name","Insert from URL",
  "Show Custom Dialog","Show/Hide Menubar","Refresh Window","New Window","Close Window","Select Window","Adjust Window","Move/Resize Window",
  "Perform Find","Perform Quick Find","Modify Last Find","Extend Found Set","Constrain Found Set","Show All Records","Show Omitted Only","Omit Record","Omit Multiple Records",
  "Sort Records","Unsort Records","Sort Records by Field",
  "Save Records as PDF","Save Records as Excel","Save Records as Snapshot Link","Export Records","Import Records",
  "Open File","Close File","New File","Print","Print Setup",
  "Open URL","Send Mail","Send Event","Send DDE Execute",
];

const FM_CONTROL = ["If","Else If","Else","End If","Loop","End Loop","Exit Loop If"];

const STEPS_RE = new RegExp(
  "\\b(" +
    [...FM_STEPS, ...FM_CONTROL]
      .sort((a, b) => b.length - a.length)
      .map(s => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"))
      .join("|") +
  ")\\b",
  "g"
);

const CONTROL_SET = new Set(FM_CONTROL.map(c => c.toLowerCase()));

const VALID_ACTIONS = new Set([
  ...FM_STEPS, ...FM_CONTROL,
  "Configure AI Account","Insert Embedding","Insert Embedding in Found Set","Perform Semantic Find","Go to Previous Field","Export Field Contents",
  "Freeze Window","Copy","Paste","Perform Find/Replace","Delete All Records","Delete Portal Row",
  "Delete Record/Request","Save Records as JSONL","Save Records as Snapshot Link",
  "Insert PDF","Insert Picture","Find Matching Records","Insert Calculated Result",
  "Show/Hide Text Ruler","Open Data File","Print","Print Setup","Save a Copy as XML",
  "Save a Copy as","Set Multi-User","Set Use System Formats","Enable Touch Keyboard",
  "Allow Formatting Bar","Install Plug-In File","Set Window Title","Refresh Object",
  "Refresh Portal","Perform AppleScript","Perform JavaScript in Web Viewer",
  "Omit Multiple Records","Install OnTimer Script","Clear","Cut",
  "Insert from Last Visited","Insert Text","Set Selection","Undo/Redo",
  "Close Data File","Get Data File Position","Get File Exists","Get File Size",
  "Insert Audio/Video","Insert from Index","Set Next Serial Value","Close Popover",
  "Go to Next Field","Select All","Copy All Records/Requests","Copy Record/Request",
  "Beep","View As","Truncate Table","Check Found Set","Check Record",
  "Correct Word","Edit User Dictionary","Select Dictionaries","Spelling Options",
  "Open Edit Saved Finds","Open Favorites","Open File Options","Open Find/Replace",
  "Open Help","Open Hosts","Open Manage Containers","Open Manage Data Sources",
  "Open Manage Database","Open Manage Layouts","Open Manage Themes",
  "Open Manage Value Lists","Open Settings","Open Script Workspace","Open Sharing",
  "Open Upload To Host","Exit Application","Flush Cache to Disk","Rename File",
  "Dial Phone","Check Selection","Perform Script On Server","Set Error Logging",
  "Set Layout Object Animation","Set Revert Transaction on Error","Scroll Window",
  "Arrange All Windows","Show/Hide Toolbars","Relookup Field Contents",
  "Replace Field Contents","Close Window","Extend Found Set","Constrain Found Set",
  "Sort Records by Field","Close File","Send Event"
].map(s => s.toLowerCase()));

function getLineAction(line) {
  line = line.trim();
  if (!line || line.startsWith('#')) return null;
  const bracketIdx = line.indexOf('[');
  const instruction = (bracketIdx > 0 ? line.substring(0, bracketIdx) : line).trim();
  return instruction;
}

function analyzeLines(lines) {
  const results = [];
  let depth = 0;
  let actionNum = 0;
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmed = line.trim();
    if (depth > 0) {
      for (const ch of trimmed) { if (ch === '[') depth++; if (ch === ']') depth--; }
      results.push({ valid: true, actionNum: null, continuation: true });
      continue;
    }
    const action = getLineAction(line);
    if (action === null) {
      actionNum++;
      results.push({ valid: true, actionNum, continuation: false });
      continue;
    }
    actionNum++;
    const bracketIdx = trimmed.indexOf('[');
    if (bracketIdx > 0) {
      depth = 0;
      for (const ch of trimmed.substring(bracketIdx)) { if (ch === '[') depth++; if (ch === ']') depth--; }
    }
    results.push({ valid: VALID_ACTIONS.has(action.toLowerCase()), actionNum, continuation: false });
  }
  return results;
}

function countActions(code) {
  if (!code) return 0;
  const lines = code.split("\n");
  const analysis = analyzeLines(lines);
  let max = 0;
  for (const a of analysis) { if (a.actionNum && a.actionNum > max) max = a.actionNum; }
  return max;
}

function escapeHtml(s) {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function highlightFm(code) {
  return code.split("\n").map(line => {
    if (!line.trim()) return "";
    if (/^\s*#/.test(line)) return `<span class="tok-comment">${escapeHtml(line)}</span>`;
    let html = escapeHtml(line);
    html = html.replace(/&quot;([^&]*?)&quot;/g, (_, inner) =>
      `<span class="tok-string">&quot;${inner}&quot;</span>`);
    html = html.replace(STEPS_RE, (m) => {
      const cls = CONTROL_SET.has(m.toLowerCase()) ? "tok-keyword" : "tok-step";
      return `<span class="${cls}">${m}</span>`;
    });
    html = html.replace(/\b([A-Za-z_][\w]*)::([A-Za-z_][\w]*)/g,
      `<span class="tok-field">$1::$2</span>`);
    html = html.replace(/\b(\d+(\.\d+)?)\b/g, `<span class="tok-number">$1</span>`);
    html = html.replace(/(\[|\])/g, `<span class="tok-bracket">$1</span>`);
    return html;
  }).join("\n");
}
window.highlightFm = highlightFm;

// ─────── Code Editor component ───────

function CodeEditor({ value, onChange, placeholder = "" }) {
  const taRef = React.useRef(null);
  const mirrorRef = React.useRef(null);

  const lines = (value || "").split("\n");
  const analysis = React.useMemo(() => analyzeLines(lines), [value]);

  const onScroll = () => {
    if (mirrorRef.current && taRef.current) {
      mirrorRef.current.scrollTop = taRef.current.scrollTop;
    }
  };

  const onKeyDown = (e) => {
    if (e.key === "Tab") {
      e.preventDefault();
      const ta = e.target;
      const s = ta.selectionStart, eEnd = ta.selectionEnd;
      const next = ta.value.slice(0, s) + "  " + ta.value.slice(eEnd);
      onChange(next);
      requestAnimationFrame(() => {
        ta.selectionStart = ta.selectionEnd = s + 2;
      });
    }
  };

  return (
    <div className="code-editor">
      <div ref={mirrorRef} className="code-mirror" aria-hidden="true">
        {lines.map((line, i) => {
          const info = analysis[i] || { valid: true, actionNum: null, continuation: false };
          return (
            <div key={i} className={`code-line${info.valid ? "" : " code-line-invalid"}${info.continuation ? " code-line-cont" : ""}`}>
              <span className="code-ln">{info.actionNum || ""}</span>
              <span className="code-text" dangerouslySetInnerHTML={{ __html: highlightFm(line) || "&nbsp;" }}/>
              {!info.valid && <span className="code-error-icon" title={getLineAction(line)}>!</span>}
            </div>
          );
        })}
        <div className="code-line"><span className="code-ln">&nbsp;</span><span className="code-text">&nbsp;</span></div>
      </div>
      <textarea
        ref={taRef}
        className="code-ta scroll"
        value={value}
        onChange={e => onChange(e.target.value)}
        onScroll={onScroll}
        onKeyDown={onKeyDown}
        placeholder={placeholder}
        spellCheck="false"
        autoCapitalize="off"
        autoComplete="off"
      />
    </div>
  );
}

// ─────── Scripts page (main app view) ───────

function ScriptsPage({ companion, onMenuToggle, loadedScript, onScriptLoaded, fmVersion, fmVersions, onFmVersionChange }) {
  const { t } = useI18n();
  const [scriptName, setScriptName] = React.useState(() => localStorage.getItem('draft_scriptName') || t("scripts.placeholder"));
  const [code, setCode] = React.useState(() => localStorage.getItem('draft_code') || "");

  React.useEffect(() => {
    localStorage.setItem('draft_scriptName', scriptName);
  }, [scriptName]);

  React.useEffect(() => {
    localStorage.setItem('draft_code', code);
  }, [code]);

  React.useEffect(() => {
    if (loadedScript) {
      setScriptName(loadedScript.name || t("scripts.placeholder"));
      setCode(loadedScript.code || "");
      if (onScriptLoaded) onScriptLoaded();
    }
  }, [loadedScript]);

  const [speed, setSpeed] = React.useState(1);
  const [generating, setGenerating] = React.useState(false);
  const [status, setStatus] = React.useState(null);
  const [errorPanel, setErrorPanel] = React.useState(null);
  const [resumeLine, setResumeLine] = React.useState(1);

  const speedLabels = ["0.5×", "1.0×", "2.0×", "5.0×"];
  const speedValues = [0.5, 1.0, 2.0, 5.0];
  const speedNames  = t("scripts.speedNames");

  const generate = async () => {
    if (!companion) {
      setStatus({ type: "error", text: t("scripts.error.noCompanion") });
      return;
    }
    if (!code.trim()) {
      setStatus({ type: "error", text: t("scripts.error.noCode") });
      return;
    }

    setGenerating(true);
    setStatus(null);
    setErrorPanel(null);

    try {
      const _dDelay = parseFloat(localStorage.getItem('pref_keystrokeDelay')) || 0.3;
      const _5tabs = localStorage.getItem('pref_fiveTabs') === 'true';
      console.log('[FMA] generate params → dialogDelay:', _dDelay, 'setFieldExtraTab:', _5tabs);
      const res = await apiFetch('/generate', {
        method: 'POST',
        body: JSON.stringify({
          scriptName: scriptName || 'Nouveau Script',
          scriptCode: code,
          speed: speedValues[speed],
          fmVersion,
          dialogDelay: _dDelay,
          setFieldExtraTab: _5tabs
        })
      });

      const data = await res.json();

      if (data.success && data.applescript) {
        try {
          const compRes = await fetch('http://localhost:27184/execute-raw', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ applescript: data.applescript })
          });

          const result = await compRes.json();

          if (result.errorSource === 'FileMaker') {
            const errLine = result.errorLine || null;
            setErrorPanel({ line: errLine, message: result.message || "Erreur FileMaker détectée", lineContent: result.errorLineContent || null });
            setResumeLine(errLine || 1);
            setStatus({ type: "error", text: result.message || "Erreur détectée par FileMaker" });
          } else if (result.success) {
            const steps = countActions(code);
            setStatus({ type: "success", text: t("scripts.success", { name: scriptName, steps }) });
          } else {
            setStatus({ type: "error", text: result.error || result.message || t("scripts.error.generation") });
          }
        } catch (compErr) {
          setStatus({ type: "error", text: `Companion injoignable : ${compErr.message}` });
        }
      } else {
        setStatus({ type: "error", text: data.message || t("scripts.error.generation") });
      }
    } catch (err) {
      setStatus({ type: "error", text: err.message || t("scripts.error.connection") });
    }

    setGenerating(false);
  };

  const resume = async () => {
    if (!companion || !code.trim()) return;
    setErrorPanel(null);
    setGenerating(true);
    setStatus(null);

    try {
      const res = await apiFetch('/resume', {
        method: 'POST',
        body: JSON.stringify({
          scriptCode: code,
          fromLine: resumeLine,
          speed: speedValues[speed],
          fmVersion,
          dialogDelay: parseFloat(localStorage.getItem('pref_keystrokeDelay')) || 0.3,
          setFieldExtraTab: localStorage.getItem('pref_fiveTabs') === 'true'
        })
      });

      const data = await res.json();

      if (data.success && data.applescript) {
        try {
          const compRes = await fetch('http://localhost:27184/execute-raw', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ applescript: data.applescript })
          });

          const result = await compRes.json();

          if (result.errorSource === 'FileMaker') {
            const errLine = result.errorLine || null;
            setErrorPanel({ line: errLine, message: result.message || "Erreur FileMaker détectée", lineContent: result.errorLineContent || null });
            setResumeLine(errLine || 1);
            setStatus({ type: "error", text: result.message || "Erreur détectée par FileMaker" });
          } else if (result.success) {
            setStatus({ type: "success", text: t("scripts.resumeSuccess") });
          } else {
            setStatus({ type: "error", text: result.error || result.message || t("scripts.error.resume") });
          }
        } catch (compErr) {
          setStatus({ type: "error", text: `Companion injoignable : ${compErr.message}` });
        }
      } else {
        setStatus({ type: "error", text: data.message || t("scripts.error.resume") });
      }
    } catch (err) {
      setStatus({ type: "error", text: err.message });
    }

    setGenerating(false);
  };

  return (
    <>
      <Topbar
        breadcrumb={[t("sidebar.atelier"), t("scripts.breadcrumb")]}
        onMenuToggle={onMenuToggle}
      />

      <CompanionBar connected={companion} onInstall={() => alert("Lance la commande d'installation dans le Terminal.")}/>

      <div className="app-content scroll" style={{ padding: 24 }}>
        <div className="col gap-4" style={{ height: "100%" }}>

          {/* Header row */}
          <div className="row gap-3 scripts-header">
            <div className="grow">
              <label className="label">{t("scripts.name")}</label>
              <div className="field">
                <Icon name="scripts" size={15} style={{ color: "var(--fg-faint)", marginRight: 8 }}/>
                <input value={scriptName} onChange={e => setScriptName(e.target.value)} placeholder={t("scripts.placeholder")}/>
                <span className="badge">{countActions(code)} steps</span>
              </div>
            </div>
            {fmVersions && fmVersions.length > 1 && (
              <div>
                <label className="label">{t("scripts.fmVersion")}</label>
                <div className="field" style={{ padding: "0 10px" }}>
                  <Icon name="cpu" size={15} style={{ color: "var(--fg-faint)" }}/>
                  <select value={fmVersion} onChange={e => onFmVersionChange(e.target.value)}
                    style={{ flex: 1, background: "transparent", border: 0, fontSize: 13, color: "var(--fg)", cursor: "pointer" }}>
                    {fmVersions.map(v => <option key={v.appName} value={v.appName}>{v.label}</option>)}
                  </select>
                </div>
              </div>
            )}
            <div style={{ width: 280 }}>
              <label className="label">{t("scripts.speed")}</label>
              <div className="field" style={{ padding: "0 14px", gap: 12 }}>
                <Icon name="zap" size={15} style={{ color: "var(--fg-faint)" }}/>
                <input
                  type="range" min="0" max="3" step="1" value={speed}
                  onChange={e => setSpeed(parseInt(e.target.value))}
                  style={{ flex: 1, accentColor: "var(--accent)" }}
                />
                <span className="mono" style={{ fontSize: 12, fontWeight: 600, width: 60, textAlign: "right" }}>
                  {speedLabels[speed]} <span style={{ color: "var(--fg-faint)", fontWeight: 400 }}>{speedNames[speed]}</span>
                </span>
              </div>
            </div>
          </div>

          {/* Editor */}
          <div className="col grow" style={{ minHeight: 320 }}>
            <label className="label" style={{ marginBottom: 8 }}>{t("scripts.codeLabel")}</label>
            <CodeEditor value={code} onChange={setCode} placeholder="Set Field [ Patients::Nom ; &quot;Dupont&quot; ]"/>
          </div>

          {/* Actions */}
          <div className="row gap-2 scripts-actions" style={{ justifyContent: "space-between" }}>
            <div className="row gap-2">
              <Button variant="ghost" icon="trash" onClick={() => { setCode(""); setStatus(null); }}>{t("scripts.clear")}</Button>
              <Button variant="ghost" icon="copy" onClick={() => { navigator.clipboard.writeText(code).catch(() => {}); }}>{t("scripts.copy")}</Button>
            </div>
            <Button variant="primary" size="lg" icon="zap" onClick={generate} disabled={generating}>
              {generating ? t("scripts.creating") : t("scripts.create")}
            </Button>
          </div>

          {/* Status */}
          {status && (
            <div className="row gap-3" style={{
              padding: "12px 16px",
              borderRadius: "var(--radius)",
              background: status.type === "success" ? "color-mix(in oklch, var(--success) 12%, var(--bg-elevated))" : "color-mix(in oklch, var(--danger) 12%, var(--bg-elevated))",
              border: `1px solid ${status.type === "success" ? "var(--success)" : "var(--danger)"}`,
              color: status.type === "success" ? "var(--success)" : "var(--danger)",
              fontSize: 13, fontWeight: 500,
            }}>
              <Icon name={status.type === "success" ? "checkCircle" : "alertTri"} size={16}/>
              <span>{status.text}</span>
              <span style={{ flex: 1 }}/>
              <IconButton icon="x" size="sm" onClick={() => setStatus(null)}/>
            </div>
          )}

          {/* Error resume panel */}
          {errorPanel && (
            <div className="card" style={{
              padding: 16,
              borderColor: "var(--danger)",
              background: "color-mix(in oklch, var(--danger) 8%, var(--bg-elevated))"
            }}>
              <div className="row gap-2" style={{ alignItems: "flex-start", marginBottom: 8 }}>
                <Icon name="alertTri" size={18} style={{ color: "var(--danger)", flexShrink: 0, marginTop: 2 }}/>
                <div className="col gap-1 grow">
                  <div style={{ fontWeight: 600, color: "var(--danger)" }}>
                    {errorPanel.line
                      ? t("scripts.error.atLine", { line: errorPanel.line })
                      : t("scripts.error.detected")}
                  </div>
                  <div style={{ fontSize: 13, color: "var(--fg-muted)" }}>{errorPanel.message}</div>
                  {errorPanel.lineContent && (
                    <code style={{ fontSize: 12, color: "var(--fg)", background: "var(--code-bg)", padding: "4px 8px", borderRadius: 4, marginTop: 4, display: "inline-block" }}>
                      {errorPanel.lineContent}
                    </code>
                  )}
                </div>
                <IconButton icon="x" size="sm" onClick={() => setErrorPanel(null)}/>
              </div>
              <p style={{ fontSize: 13, color: "var(--fg-muted)", margin: "8px 0 12px" }}>
                {t("scripts.error.resumeInstructions")}
              </p>
              <div className="row gap-2">
                <span style={{ fontSize: 13, color: "var(--fg-muted)" }}>{t("scripts.resumeAt")}</span>
                <div className="field mono" style={{ width: 64, height: 32, padding: "0 10px" }}>
                  <input type="number" value={resumeLine} onChange={e => setResumeLine(parseInt(e.target.value) || 1)} style={{ textAlign: "center", fontWeight: 600 }}/>
                </div>
                <Button variant="primary" size="sm" icon="play" onClick={resume} disabled={generating}>
                  {generating ? t("scripts.resuming") : t("scripts.resume")}
                </Button>
              </div>
            </div>
          )}

        </div>
      </div>
    </>
  );
}

// ─────── Champs page ───────

function FieldsPage({ companion }) {
  const { t } = useI18n();
  const [tableName, setTableName] = React.useState("Patients");
  const [fields, setFields] = React.useState(
`pk_Id ; Nombre ; Clé primaire ; Indexé
Nom ; Texte ; Nom du patient ; Normal
Prenom ; Texte ; Prénom du patient ; Normal
DateNaissance ; Date ; Date de naissance ; Normal
Email ; Texte ; Adresse email ; Indexé
Photo ; Conteneur ; Photo du patient`
  );

  const rows = fields.split("\n").filter(l => l.trim()).map(l => l.split(";").map(s => s.trim()));

  return (
    <>
      <Topbar breadcrumb={[t("sidebar.atelier"), "Champs"]} actions={
        <>
          <Badge icon="cpu">FileMaker 2024 (v21)</Badge>
          <IconButton icon="folder"/>
          <IconButton icon="help"/>
        </>
      }/>
      <CompanionBar connected={companion} onInstall={() => {}}/>

      <div className="app-content scroll" style={{ padding: 24 }}>
        <div className="col gap-4">

          <div className="row gap-3">
            <div className="grow">
              <label className="label">Nom de la table</label>
              <div className="field">
                <Icon name="database" size={15} style={{ color: "var(--fg-faint)", marginRight: 8 }}/>
                <input value={tableName} onChange={e => setTableName(e.target.value)}/>
                <span className="badge">{rows.length} champs</span>
              </div>
            </div>
          </div>

          <div className="panel" style={{ padding: 14 }}>
            <div className="row gap-2" style={{ marginBottom: 4 }}>
              <Icon name="info" size={14} style={{ color: "var(--accent)" }}/>
              <span style={{ fontSize: 12.5, fontWeight: 600 }}>Format</span>
            </div>
            <code className="mono" style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>
              NomChamp ; Type ; Commentaire ; Options
            </code>
            <div style={{ fontSize: 11.5, color: "var(--fg-faint)", marginTop: 6 }}>
              Types : <span className="mono">Texte</span> · <span className="mono">Nombre</span> · <span className="mono">Date</span> · <span className="mono">Heure</span> · <span className="mono">Horodatage</span> · <span className="mono">Conteneur</span>
            </div>
          </div>

          <div className="col gap-2">
            <div className="row" style={{ justifyContent: "space-between" }}>
              <label className="label" style={{ margin: 0 }}>Définition des champs</label>
              <div className="row gap-2" style={{ fontSize: 11, color: "var(--fg-faint)" }}>
                <span>Aperçu live à droite</span>
              </div>
            </div>
            <div className="fields-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
              <textarea
                className="field mono scroll"
                value={fields}
                onChange={e => setFields(e.target.value)}
                style={{ minHeight: 280, padding: 12, fontSize: 13, lineHeight: 1.65, alignItems: "flex-start" }}
              />
              <div className="card" style={{ padding: 0, overflow: "hidden" }}>
                <div className="row" style={{ padding: "8px 14px", borderBottom: "1px solid var(--border)", background: "var(--surface)", fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--fg-faint)", textTransform: "uppercase", letterSpacing: "0.06em" }}>
                  <span style={{ flex: 1 }}>Nom</span>
                  <span style={{ width: 90 }}>Type</span>
                  <span style={{ width: 80, textAlign: "right" }}>Options</span>
                </div>
                {rows.map((r, i) => (
                  <div key={i} className="row" style={{ padding: "8px 14px", borderBottom: i < rows.length - 1 ? "1px solid var(--border)" : "none", fontSize: 12.5 }}>
                    <span className="mono grow" style={{ color: "var(--fg)" }}>{r[0]}</span>
                    <span className="tag" style={{ width: 80 }}>{r[1] || "—"}</span>
                    <span style={{ width: 90, textAlign: "right", color: "var(--fg-faint)", fontSize: 11 }}>{r[3] || ""}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>

          <div className="row gap-2" style={{ justifyContent: "space-between" }}>
            <div className="row gap-2">
              <Button variant="ghost" icon="trash" onClick={() => setFields("")}>{t("scripts.clear")}</Button>
              <Button variant="ghost" icon="upload">Importer CSV</Button>
            </div>
            <Button variant="primary" size="lg" icon="zap" disabled={!companion}>
              Créer les champs
            </Button>
          </div>
        </div>
      </div>
    </>
  );
}

// ─────── Historique page ───────

function HistoryPage({ onMenuToggle, onLoadScript }) {
  const { t, lang } = useI18n();
  const [scripts, setScripts] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [search, setSearch] = React.useState("");

  React.useEffect(() => {
    fetchHistory();
  }, []);

  const fetchHistory = async () => {
    try {
      const res = await apiFetch('/api/scripts/history');
      const data = await res.json();
      setScripts(data.scripts || []);
    } catch (err) {
      console.error('History fetch error:', err);
    }
    setLoading(false);
  };

  const deleteScript = async (id) => {
    try {
      await apiFetch(`/api/scripts/${id}`, { method: 'DELETE' });
      setScripts(prev => prev.filter(s => s.id !== id));
    } catch (err) {
      console.error('Delete error:', err);
    }
  };

  const formatDate = (dateStr) => {
    const date = new Date(dateStr);
    const now = new Date();
    const diff = now - date;
    const mins = Math.floor(diff / 60000);
    const hours = Math.floor(diff / 3600000);
    const days = Math.floor(diff / 86400000);

    if (mins < 1) return t("history.timeAgo.now");
    if (mins < 60) return t("history.timeAgo.min", { n: mins });
    if (hours < 24) return t("history.timeAgo.hour", { n: hours });
    if (days < 2) return t("history.timeAgo.yesterday");
    return date.toLocaleDateString(lang === 'en' ? 'en-US' : 'fr-FR', { day: '2-digit', month: 'short' });
  };

  const countSteps = (code) => countActions(code);

  const [expanded, setExpanded] = React.useState({});

  const filtered = scripts.filter(s =>
    !search || s.name.toLowerCase().includes(search.toLowerCase())
  );

  const grouped = React.useMemo(() => {
    const map = {};
    for (const s of filtered) {
      const key = s.name || 'Sans nom';
      if (!map[key]) map[key] = [];
      map[key].push(s);
    }
    return Object.entries(map);
  }, [filtered]);

  const toggleGroup = (name) => setExpanded(prev => ({ ...prev, [name]: !prev[name] }));

  const now = new Date();
  const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
  const scriptsThisMonth = scripts.filter(s => new Date(s.created_at) >= monthStart);
  const totalScripts = scriptsThisMonth.length;
  const totalSteps = scripts.reduce((sum, s) => sum + countSteps(s.code), 0);

  return (
    <>
      <Topbar breadcrumb={[t("sidebar.atelier"), t("history.breadcrumb")]} onMenuToggle={onMenuToggle} actions={
        <>
          <div className="field topbar-search" style={{ width: 220, height: 30 }}>
            <Icon name="search" size={14} style={{ color: "var(--fg-faint)", marginRight: 6 }}/>
            <input placeholder={t("history.search")} style={{ fontSize: 13 }} value={search} onChange={e => setSearch(e.target.value)}/>
          </div>
        </>
      }/>

      <div className="app-content scroll" style={{ padding: 24 }}>
        <div className="col gap-4">

          {/* Stat cards */}
          <div className="stats-grid" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
            {[
              { label: t("history.scriptsMonth"), value: String(totalScripts), sub: t("history.included"), icon: "scripts" },
              { label: t("history.stepsGenerated"), value: totalSteps.toLocaleString(lang === 'en' ? 'en-US' : 'fr-FR'), sub: t("history.total"), icon: "code" },
              { label: t("history.timeSaved"), value: `${Math.round(totalSteps * 0.4)}min`, sub: `≈ ${totalSteps * 3} ${t("history.clicks")}`, icon: "clock" },
            ].map(s => (
              <div key={s.label} className="card" style={{ padding: 16 }}>
                <div className="row" style={{ justifyContent: "space-between" }}>
                  <span style={{ fontSize: 11.5, color: "var(--fg-muted)", textTransform: "uppercase", letterSpacing: "0.06em" }}>{s.label}</span>
                  <Icon name={s.icon} size={14} style={{ color: "var(--fg-faint)" }}/>
                </div>
                <div className="display" style={{ fontSize: 26, fontWeight: 600, marginTop: 6, letterSpacing: "-0.02em" }}>{s.value}</div>
                <div style={{ fontSize: 11.5, color: "var(--fg-faint)", marginTop: 2 }}>{s.sub}</div>
              </div>
            ))}
          </div>

          {/* List grouped by name */}
          <div className="card" style={{ padding: 0, overflow: "hidden" }}>
            {loading ? (
              <div style={{ padding: 24, textAlign: "center", color: "var(--fg-muted)", fontSize: 13 }}>{t("history.loading")}</div>
            ) : grouped.length === 0 ? (
              <div style={{ padding: 24, textAlign: "center", color: "var(--fg-muted)", fontSize: 13 }}>
                {search ? t("history.noResults") : t("history.empty")}
              </div>
            ) : grouped.map(([name, items]) => (
              <div key={name}>
                <div className="hist-row" style={{ cursor: "pointer" }} onClick={() => items.length > 1 ? toggleGroup(name) : onLoadScript && onLoadScript(items[0])}>
                  <div className="col" style={{ minWidth: 0, gap: 2 }}>
                    <div className="row gap-2">
                      {items.length > 1 && <Icon name={expanded[name] ? "chevronDown" : "chevronRight"} size={12} style={{ color: "var(--fg-faint)" }}/>}
                      <Icon name="scripts" size={14} style={{ color: "var(--fg-faint)" }}/>
                      <span style={{ fontWeight: 500, fontSize: 13.5 }}>{name}</span>
                      {items.length > 1 && <span style={{ fontSize: 11, color: "var(--fg-muted)", background: "var(--bg-subtle)", borderRadius: 8, padding: "1px 7px" }}>{items.length}×</span>}
                    </div>
                  </div>
                  <span style={{ fontSize: 12, color: "var(--fg-muted)", fontFamily: "var(--font-mono)" }}>{countSteps(items[0].code)} steps</span>
                  <span className="hist-date" style={{ fontSize: 12, color: "var(--fg-muted)", width: 100, textAlign: "right" }}>{formatDate(items[0].created_at)}</span>
                  <div className="row gap-1" onClick={e => e.stopPropagation()}>
                    <IconButton icon="play" size="sm" title={t("history.loadEditor")} onClick={() => onLoadScript && onLoadScript(items[0])}/>
                    <IconButton icon="copy" size="sm" title={t("history.copyCode")} onClick={() => {
                      navigator.clipboard.writeText(items[0].code || '').catch(() => {});
                    }}/>
                    <IconButton icon="trash" size="sm" title={t("history.delete")} onClick={() => deleteScript(items[0].id)}/>
                  </div>
                </div>
                {items.length > 1 && expanded[name] && items.slice(1).map(h => (
                  <div key={h.id} className="hist-row" style={{ paddingLeft: 40, background: "var(--bg-subtle)" }} onClick={() => onLoadScript && onLoadScript(h)}>
                    <div className="col" style={{ minWidth: 0, gap: 2 }}>
                      <div className="row gap-2">
                        <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>{formatDate(h.created_at)}</span>
                      </div>
                    </div>
                    <span style={{ fontSize: 12, color: "var(--fg-muted)", fontFamily: "var(--font-mono)" }}>{countSteps(h.code)} steps</span>
                    <span className="hist-date" style={{ fontSize: 12, color: "var(--fg-muted)", width: 100, textAlign: "right" }}>{formatDate(h.created_at)}</span>
                    <div className="row gap-1" onClick={e => e.stopPropagation()}>
                      <IconButton icon="play" size="sm" title={t("history.loadEditor")} onClick={() => onLoadScript && onLoadScript(h)}/>
                      <IconButton icon="copy" size="sm" title={t("history.copyCode")} onClick={() => {
                        navigator.clipboard.writeText(h.code || '').catch(() => {});
                      }}/>
                      <IconButton icon="trash" size="sm" title={t("history.delete")} onClick={() => deleteScript(h.id)}/>
                    </div>
                  </div>
                ))}
              </div>
            ))}
          </div>

        </div>
      </div>
    </>
  );
}

// ─────── Profil page ───────

function ProfilePage({ navigate, user, onMenuToggle, onUserUpdate }) {
  const { t, lang } = useI18n();
  const email = user?.email || '';
  const fullName = user?.user_metadata?.full_name || user?.user_metadata?.name || '';
  const [first, setFirst] = React.useState(fullName.split(' ')[0] || email.split('@')[0]);
  const [last, setLast] = React.useState(fullName.split(' ').slice(1).join(' ') || '');
  const [username, setUsername] = React.useState(user?.user_metadata?.username || '');
  const [saving, setSaving] = React.useState(false);
  const [saved, setSaved] = React.useState(false);
  const createdAt = user?.created_at
    ? new Date(user.created_at).toLocaleDateString(lang === 'en' ? 'en-US' : 'fr-FR', { month: 'long', year: 'numeric' })
    : '';

  const isDirty = `${first} ${last}`.trim() !== fullName || username !== (user?.user_metadata?.username || '');

  const save = async () => {
    if (!window.sb) return;
    setSaving(true);
    try {
      const newName = `${first} ${last}`.trim();
      const { data, error } = await window.sb.auth.updateUser({ data: { full_name: newName, username: username.trim() } });
      if (error) throw error;
      await apiFetch('/api/profile', {
        method: 'PUT',
        body: JSON.stringify({ username: username.trim(), full_name: newName })
      });
      if (onUserUpdate && data.user) onUserUpdate(data.user);
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
    } catch (e) {
      alert('Erreur : ' + (e.message || 'impossible de sauvegarder'));
    }
    setSaving(false);
  };

  return (
    <>
      <Topbar breadcrumb={[t("sidebar.account"), t("profile.breadcrumb")]} onMenuToggle={onMenuToggle}/>

      <div className="app-content scroll" style={{ padding: 32 }}>
        <div className="col gap-6" style={{ maxWidth: 760, margin: "0 auto", width: "100%" }}>

          <div className="col gap-2">
            <h1 style={{ fontSize: 22 }}>{fullName || first}</h1>
            <span style={{ fontSize: 13, color: "var(--fg-muted)" }}>{email}</span>
            <div className="row gap-2" style={{ marginTop: 4 }}>
              <Badge variant="accent" icon="star">{t("profile.freePlan")}</Badge>
              {createdAt && <Badge>{t("profile.memberSince", { date: createdAt })}</Badge>}
            </div>
          </div>

          <hr className="hr"/>

          <div className="col gap-4">
            <div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
              <h2 style={{ fontSize: 16 }}>{t("profile.info")}</h2>
              {saved && <Badge variant="accent" icon="check">{t("profile.saved")}</Badge>}
            </div>
            <Field label={t("profile.username")} icon="user" value={username} onChange={e => setUsername(e.target.value)} placeholder={t("profile.usernamePlaceholder")}/>
            <div className="row gap-3" style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
              <Field label={t("profile.firstName")} value={first} onChange={e => setFirst(e.target.value)}/>
              <Field label={t("profile.lastName")} value={last} onChange={e => setLast(e.target.value)}/>
            </div>
            <Field label={t("profile.email")} icon="mail" value={email} disabled/>
            {isDirty && (
              <Button variant="primary" size="sm" icon="check" onClick={save} disabled={saving} style={{ alignSelf: "flex-start" }}>
                {saving ? t("profile.saving") : t("profile.save")}
              </Button>
            )}
          </div>

          <hr className="hr"/>

          <div className="col gap-4">
            <div className="row" style={{ justifyContent: "space-between" }}>
              <h2 style={{ fontSize: 16 }}>{t("profile.subscription")}</h2>
              <Button variant="ghost" size="sm">{t("profile.invoices")}</Button>
            </div>
            <div className="card" style={{ padding: 20 }}>
              <div className="row sub-card-inner" style={{ justifyContent: "space-between", alignItems: "flex-start" }}>
                <div className="col gap-1">
                  <span style={{ fontSize: 11.5, color: "var(--fg-faint)", textTransform: "uppercase", letterSpacing: "0.06em" }}>{t("profile.currentPlan")}</span>
                  <div className="row gap-2"><span className="display" style={{ fontSize: 22 }}>{t("profile.free")}</span><Badge variant="accent">{t("profile.scriptsMonth")}</Badge></div>
                  <span style={{ fontSize: 12.5, color: "var(--fg-muted)" }}>{t("profile.freeDesc")}</span>
                </div>
              </div>
            </div>
          </div>

          <hr className="hr"/>

          <div className="col gap-2" style={{ alignItems: "flex-start" }}>
            <Button variant="danger" size="sm">{t("profile.deleteAccount")}</Button>
            <span style={{ fontSize: 11.5, color: "var(--fg-faint)" }}>{t("profile.deleteWarning")}</span>
          </div>

        </div>
      </div>
    </>
  );
}

// ─────── Settings page ───────

function SettingsPage({ companion, onMenuToggle, fmVersions, setFmVersions, activeFmVersion, setActiveFmVersion }) {
  const { t } = useI18n();
  const [showAddForm, setShowAddForm] = React.useState(false);
  const [newLabel, setNewLabel] = React.useState("");
  const [newAppName, setNewAppName] = React.useState("");
  const [showHelp, setShowHelp] = React.useState(false);

  const [fullKbd, setFullKbd] = React.useState(() => {
    const v = localStorage.getItem('pref_fullKbd'); return v === null ? true : v === 'true';
  });
  const [fiveTabs, setFiveTabs] = React.useState(() => localStorage.getItem('pref_fiveTabs') === 'true');
  const [keystrokeDelay, setKeystrokeDelay] = React.useState(() => parseFloat(localStorage.getItem('pref_keystrokeDelay')) || 0.3);
  const [lang, setLang] = React.useState(() => localStorage.getItem('pref_lang') || 'fr');
  const [settingsSaved, setSettingsSaved] = React.useState(false);
  const mountedRef = React.useRef(false);

  React.useEffect(() => {
    if (!mountedRef.current) { mountedRef.current = true; return; }
    localStorage.setItem('pref_fullKbd', fullKbd);
    localStorage.setItem('pref_fiveTabs', fiveTabs);
    localStorage.setItem('pref_keystrokeDelay', keystrokeDelay);
    localStorage.setItem('pref_lang', lang);
    setSettingsSaved(true);
    const timer = setTimeout(() => setSettingsSaved(false), 1500);
    return () => clearTimeout(timer);
  }, [fullKbd, fiveTabs, keystrokeDelay]);

  React.useEffect(() => {
    if (!mountedRef.current) return;
    setAppLang(lang);
  }, [lang]);

  const addVersion = () => {
    if (!newLabel.trim() || !newAppName.trim()) return;
    setFmVersions(prev => [...prev, { label: newLabel.trim(), appName: newAppName.trim() }]);
    if (fmVersions.length === 0) setActiveFmVersion(newAppName.trim());
    setNewLabel(""); setNewAppName(""); setShowAddForm(false);
  };

  const removeVersion = (idx) => {
    const removed = fmVersions[idx];
    const next = fmVersions.filter((_, i) => i !== idx);
    setFmVersions(next);
    if (removed.appName === activeFmVersion && next.length > 0) setActiveFmVersion(next[0].appName);
  };

  return (
    <>
      <Topbar breadcrumb={[t("sidebar.account"), t("settings.breadcrumb")]} onMenuToggle={onMenuToggle}
        actions={settingsSaved ? <Badge variant="accent" icon="check">{t("settings.saved")}</Badge> : null}/>

      <div className="app-content scroll" style={{ padding: 32 }}>
        <div className="col gap-6" style={{ maxWidth: 760, margin: "0 auto", width: "100%" }}>

          <div className="col gap-3">
            <div className="row gap-2" style={{ alignItems: "center" }}>
              <h2 style={{ fontSize: 16, margin: 0 }}>{t("settings.fmVersions")}</h2>
              <button onClick={() => setShowHelp(v => !v)}
                style={{ background: "none", border: 0, cursor: "pointer", color: "var(--fg-faint)" }}>
                <Icon name="help" size={16}/>
              </button>
            </div>
            <p style={{ fontSize: 13, color: "var(--fg-muted)" }}>{t("settings.fmVersionsDesc")}</p>

            {showHelp && (
              <div className="panel" style={{ padding: 14, background: "var(--accent-soft)", border: "1px solid var(--accent-ring)" }}>
                <div className="col gap-2" style={{ fontSize: 12.5, color: "var(--fg)", lineHeight: 1.55 }}>
                  <div style={{ fontWeight: 600 }}>{t("settings.helpTitle")}</div>
                  <p style={{ margin: 0 }} dangerouslySetInnerHTML={{ __html: t("settings.helpText1") }}/>
                  <p style={{ margin: 0 }}>{t("settings.helpText2")}</p>
                  <ul style={{ margin: 0, paddingLeft: 18 }}>
                    <li><code className="mono" style={{ background: "var(--bg-elevated)", padding: "1px 5px", borderRadius: 3 }}>{t("settings.helpExample1")}</code></li>
                    <li><code className="mono" style={{ background: "var(--bg-elevated)", padding: "1px 5px", borderRadius: 3 }}>{t("settings.helpExample2")}</code></li>
                  </ul>
                  <p style={{ margin: 0 }} dangerouslySetInnerHTML={{ __html: t("settings.helpText3") }}/>
                </div>
              </div>
            )}

            <div className="col gap-2">
              {fmVersions.map((v, i) => (
                <label key={i} className="card row gap-3" style={{
                  padding: "10px 14px", cursor: "pointer",
                  borderColor: v.appName === activeFmVersion ? "var(--accent)" : "var(--border)",
                  background: v.appName === activeFmVersion ? "var(--accent-soft)" : "var(--bg-elevated)",
                }}>
                  <input type="radio" name="fmv" checked={v.appName === activeFmVersion}
                    onChange={() => setActiveFmVersion(v.appName)} style={{ accentColor: "var(--accent)" }}/>
                  <div className="col grow">
                    <span style={{ fontSize: 13.5, fontWeight: 500 }}>{v.label}</span>
                    <span className="mono" style={{ fontSize: 11.5, color: "var(--fg-faint)" }}>{v.appName}</span>
                  </div>
                  {fmVersions.length > 1 && (
                    <IconButton icon="trash" size="sm" onClick={e => { e.preventDefault(); removeVersion(i); }}/>
                  )}
                </label>
              ))}
            </div>

            {showAddForm ? (
              <div className="card col gap-3" style={{ padding: 14 }}>
                <span style={{ fontSize: 13, fontWeight: 600 }}>{t("settings.newVersion")}</span>
                <Field label={t("settings.labelField")} value={newLabel} onChange={e => setNewLabel(e.target.value)} placeholder="FileMaker 2024"/>
                <Field label={t("settings.appNameField")} value={newAppName} onChange={e => setNewAppName(e.target.value)} placeholder="FileMaker Pro"
                  hint={t("settings.appNameHint")}/>
                <div className="row gap-2">
                  <Button variant="ghost" size="sm" onClick={() => { setShowAddForm(false); setNewLabel(""); setNewAppName(""); }}>{t("settings.cancel")}</Button>
                  <Button variant="primary" size="sm" icon="check" onClick={addVersion} disabled={!newLabel.trim() || !newAppName.trim()}>{t("settings.add")}</Button>
                </div>
              </div>
            ) : (
              <Button variant="ghost" size="sm" icon="plus" onClick={() => setShowAddForm(true)} style={{ alignSelf: "flex-start" }}>{t("settings.addVersion")}</Button>
            )}
          </div>

          <hr className="hr"/>

          <div className="col gap-3">
            <h2 style={{ fontSize: 16 }}>{t("settings.execPrefs")}</h2>
            <label className="card row gap-3" style={{ padding: "12px 14px", cursor: "pointer" }}>
              <input type="checkbox" checked={fullKbd} onChange={e => setFullKbd(e.target.checked)} style={{ accentColor: "var(--accent)" }}/>
              <div className="col grow">
                <span style={{ fontSize: 13.5, fontWeight: 500 }}>{t("settings.fullKbd")}</span>
                <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>{t("settings.fullKbdDesc")}</span>
              </div>
            </label>
            <label className="card row gap-3" style={{ padding: "12px 14px", cursor: "pointer" }}>
              <input type="checkbox" checked={fiveTabs} onChange={e => setFiveTabs(e.target.checked)} style={{ accentColor: "var(--accent)" }}/>
              <div className="col grow">
                <span style={{ fontSize: 13.5, fontWeight: 500 }}>{t("settings.fiveTabs")}</span>
                <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>{t("settings.fiveTabsDesc")}</span>
              </div>
            </label>
            <div className="card" style={{ padding: 14 }}>
              <div className="row" style={{ justifyContent: "space-between", marginBottom: 6 }}>
                <span style={{ fontSize: 13.5, fontWeight: 500 }}>{t("settings.keystrokeDelay")}</span>
                <span className="mono" style={{ fontSize: 12.5 }}>{keystrokeDelay}s</span>
              </div>
              <input type="range" min="0.1" max="2.0" step="0.1" value={keystrokeDelay}
                onChange={e => setKeystrokeDelay(parseFloat(e.target.value))}
                style={{ width: "100%", accentColor: "var(--accent)" }}/>
              <p style={{ fontSize: 11.5, color: "var(--fg-faint)", marginTop: 4 }}>{t("settings.keystrokeHint")}</p>
            </div>
          </div>

          <hr className="hr"/>

          <div className="col gap-3">
            <h2 style={{ fontSize: 16 }}>{t("settings.language")}</h2>
            <div className="row gap-2 lang-row">
              {[{l:"Français",f:"🇫🇷",v:"fr"},{l:"English",f:"🇬🇧",v:"en"}].map(o => (
                <label key={o.v} className="card row gap-2" style={{ padding: "10px 14px", cursor: "pointer", flex: 1, borderColor: lang === o.v ? "var(--accent)" : "var(--border)" }}>
                  <input type="radio" name="lang" checked={lang === o.v} onChange={() => setLang(o.v)} style={{ accentColor: "var(--accent)" }}/>
                  <span style={{ fontSize: 18 }}>{o.f}</span>
                  <span style={{ fontSize: 13.5 }}>{o.l}</span>
                </label>
              ))}
            </div>
          </div>

        </div>
      </div>
    </>
  );
}

// ─────── Module card (shared between Community & Favorites) ───────

function ModuleCard({ mod, onLike, onFavorite, onOpen }) {
  const { t } = useI18n();
  return (
    <div className="card" style={{ padding: 0, overflow: "hidden", cursor: "pointer" }} onClick={() => onOpen(mod)}>
      <div className="col gap-2" style={{ padding: 16 }}>
        <div className="row gap-2" style={{ alignItems: "flex-start" }}>
          <Icon name="package" size={18} style={{ color: "var(--accent)", flexShrink: 0, marginTop: 2 }}/>
          <div className="col grow" style={{ gap: 2, minWidth: 0 }}>
            <span style={{ fontWeight: 600, fontSize: 14 }}>{mod.title}</span>
            <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>{t("community.by", { author: mod.author })}</span>
          </div>
        </div>
        {mod.description && (
          <p style={{ fontSize: 12.5, color: "var(--fg-muted)", lineHeight: 1.5, margin: 0,
            overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }}>
            {mod.description}
          </p>
        )}
        {mod.tags && mod.tags.length > 0 && (
          <div className="row gap-1" style={{ flexWrap: "wrap" }}>
            {mod.tags.map(tag => <Badge key={tag}>{tag}</Badge>)}
          </div>
        )}
      </div>
      <div className="row" style={{ borderTop: "1px solid var(--border)", padding: "8px 16px", gap: 12 }}>
        <button className="row gap-1" onClick={e => { e.stopPropagation(); onLike(mod); }}
          style={{ background: "none", border: 0, cursor: "pointer", fontSize: 12, color: mod.liked ? "var(--danger)" : "var(--fg-muted)", fontWeight: 500 }}>
          <Icon name={mod.liked ? "heartFill" : "heart"} size={14} style={{ color: mod.liked ? "var(--danger)" : "var(--fg-faint)" }}/>
          {mod.likes_count || 0}
        </button>
        <button className="row gap-1" onClick={e => { e.stopPropagation(); onFavorite(mod); }}
          style={{ background: "none", border: 0, cursor: "pointer", fontSize: 12, color: mod.favorited ? "var(--accent)" : "var(--fg-muted)", fontWeight: 500 }}>
          <Icon name={mod.favorited ? "star" : "star"} size={14} style={{ color: mod.favorited ? "var(--accent)" : "var(--fg-faint)" }}/>
          {mod.favorited ? t("community.favorite") : t("community.addFav")}
        </button>
        <span style={{ flex: 1 }}/>
        <span style={{ fontSize: 11, color: "var(--fg-faint)" }}>
          {mod.scripts_count || "—"} script{(mod.scripts_count || 0) > 1 ? "s" : ""}
        </span>
      </div>
    </div>
  );
}

// ─────── Module detail modal ───────

function ModuleDetail({ mod, onClose, onLoadScript }) {
  const { t } = useI18n();
  const [detail, setDetail] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    (async () => {
      try {
        const res = await apiFetch(`/api/modules/${mod.id}`);
        const data = await res.json();
        setDetail(data.module);
      } catch { /* silent */ }
      setLoading(false);
    })();
  }, [mod.id]);

  return (
    <div style={{ position: "fixed", inset: 0, zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center" }}>
      <div style={{ position: "absolute", inset: 0, background: "oklch(0 0 0 / 0.5)" }} onClick={onClose}/>
      <div className="card col gap-4" style={{ position: "relative", width: "90%", maxWidth: 640, maxHeight: "80vh", overflow: "auto", padding: 24 }}>
        <div className="row" style={{ justifyContent: "space-between", alignItems: "flex-start" }}>
          <div className="col gap-1">
            <h2 style={{ fontSize: 18, margin: 0 }}>{mod.title}</h2>
            <span style={{ fontSize: 12.5, color: "var(--fg-muted)" }}>{t("community.by", { author: mod.author })}</span>
          </div>
          <IconButton icon="x" onClick={onClose}/>
        </div>

        {mod.description && <p style={{ fontSize: 13, color: "var(--fg-muted)", lineHeight: 1.55, margin: 0 }}>{mod.description}</p>}

        {mod.tags && mod.tags.length > 0 && (
          <div className="row gap-1" style={{ flexWrap: "wrap" }}>
            {mod.tags.map(tag => <Badge key={tag} icon="tag">{tag}</Badge>)}
          </div>
        )}

        <hr className="hr"/>

        <div className="col gap-2">
          <span style={{ fontSize: 13, fontWeight: 600 }}>{t("form.scripts")} ({loading ? "…" : (detail?.scripts || []).length})</span>
          {loading ? (
            <div style={{ padding: 16, textAlign: "center", color: "var(--fg-muted)", fontSize: 13 }}>{t("community.loading")}</div>
          ) : (detail?.scripts || []).map((s, i) => (
            <div key={s.id} className="card row gap-3" style={{ padding: "10px 14px", alignItems: "center" }}>
              <span className="mono" style={{ fontSize: 11, color: "var(--fg-faint)", width: 20, textAlign: "right" }}>{i + 1}</span>
              <div className="col grow" style={{ gap: 2, minWidth: 0 }}>
                <span style={{ fontSize: 13, fontWeight: 500 }}>{s.name}</span>
                <span className="mono" style={{ fontSize: 11, color: "var(--fg-faint)" }}>
                  {s.countActions(code)} steps
                </span>
              </div>
              <Button variant="ghost" size="sm" icon="play" onClick={() => onLoadScript(s)}>{t("community.load")}</Button>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─────── Module create/edit form ───────

const MODULE_TAGS = [
  'Facturation', 'Patients', 'Import/Export', 'Inventaire', 'CRM',
  'Reporting', 'Emails', 'PDF', 'Automatisation', 'Utilitaire', 'Autre'
];

function ModuleForm({ existing, onClose, onSaved }) {
  const { t } = useI18n();
  const [title, setTitle] = React.useState(existing?.title || "");
  const [description, setDescription] = React.useState(existing?.description || "");
  const [tags, setTags] = React.useState(existing?.tags || []);
  const [published, setPublished] = React.useState(existing?.published || false);
  const [scripts, setScripts] = React.useState(existing?.scripts || [{ name: "", code: "" }]);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [showHistoryPicker, setShowHistoryPicker] = React.useState(false);
  const [history, setHistory] = React.useState([]);
  const [historyLoading, setHistoryLoading] = React.useState(false);

  const openHistoryPicker = async () => {
    setShowHistoryPicker(true);
    if (history.length > 0) return;
    setHistoryLoading(true);
    try {
      const res = await apiFetch('/api/scripts/history');
      const data = await res.json();
      setHistory(data.scripts || []);
    } catch { /* silent */ }
    setHistoryLoading(false);
  };

  const importFromHistory = (h) => {
    setScripts(prev => [...prev, { name: h.name, code: h.code || "" }]);
    setShowHistoryPicker(false);
  };

  const toggleTag = (t) => setTags(prev => prev.includes(t) ? prev.filter(x => x !== t) : [...prev, t]);

  const updateScript = (idx, field, val) => {
    setScripts(prev => prev.map((s, i) => i === idx ? { ...s, [field]: val } : s));
  };

  const addScript = () => setScripts(prev => [...prev, { name: "", code: "" }]);

  const removeScript = (idx) => {
    if (scripts.length <= 1) return;
    setScripts(prev => prev.filter((_, i) => i !== idx));
  };

  const moveScript = (idx, dir) => {
    const next = [...scripts];
    const target = idx + dir;
    if (target < 0 || target >= next.length) return;
    [next[idx], next[target]] = [next[target], next[idx]];
    setScripts(next);
  };

  const save = async () => {
    if (!title.trim()) { setError(t("form.titleRequired")); return; }
    if (!scripts.some(s => s.name.trim() || s.code.trim())) { setError(t("form.scriptRequired")); return; }
    setSaving(true);
    setError(null);

    try {
      const body = { title, description, tags, published, scripts: scripts.filter(s => s.name.trim() || s.code.trim()) };
      const url = existing ? `/api/modules/${existing.id}` : '/api/modules';
      const method = existing ? 'PUT' : 'POST';
      const res = await apiFetch(url, { method, body: JSON.stringify(body) });
      const data = await res.json();
      if (data.error) throw new Error(data.error);
      onSaved();
    } catch (err) {
      setError(err.message);
    }
    setSaving(false);
  };

  return (
    <div style={{ position: "fixed", inset: 0, zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center" }}>
      <div style={{ position: "absolute", inset: 0, background: "oklch(0 0 0 / 0.5)" }} onClick={onClose}/>
      <div className="card col gap-4" style={{ position: "relative", width: "90%", maxWidth: 680, maxHeight: "85vh", overflow: "auto", padding: 24 }}>
        <div className="row" style={{ justifyContent: "space-between" }}>
          <h2 style={{ fontSize: 18, margin: 0 }}>{existing ? t("form.editModule") : t("form.newModule")}</h2>
          <IconButton icon="x" onClick={onClose}/>
        </div>

        <Field label={t("form.title")} value={title} onChange={e => setTitle(e.target.value)} placeholder={t("form.titlePlaceholder")}/>
        <div>
          <label className="label">{t("form.description")}</label>
          <textarea className="field scroll" value={description} onChange={e => setDescription(e.target.value)}
            placeholder={t("form.descPlaceholder")}
            style={{ minHeight: 80, padding: 12, fontSize: 13, lineHeight: 1.55, alignItems: "flex-start", width: "100%", resize: "vertical" }}/>
        </div>

        <div>
          <label className="label">{t("form.tags")}</label>
          <div className="row gap-1" style={{ flexWrap: "wrap" }}>
            {MODULE_TAGS.map(tag => (
              <button key={tag} onClick={() => toggleTag(tag)}
                className={`badge ${tags.includes(tag) ? "badge-accent" : ""}`}
                style={{ cursor: "pointer", border: tags.includes(tag) ? "1px solid var(--accent)" : "1px solid var(--border)" }}>
                {tag}
              </button>
            ))}
            {tags.filter(tag => !MODULE_TAGS.includes(tag)).map(tag => (
              <button key={tag} onClick={() => toggleTag(tag)}
                className="badge badge-accent"
                style={{ cursor: "pointer", border: "1px solid var(--accent)" }}>
                {tag} ×
              </button>
            ))}
            <form onSubmit={e => {
              e.preventDefault();
              const input = e.target.elements.customTag;
              const val = input.value.trim();
              if (val && !tags.includes(val)) { setTags(prev => [...prev, val]); }
              input.value = "";
            }} className="row gap-1">
              <input name="customTag" placeholder="+ Tag…"
                style={{ width: 80, fontSize: 12, padding: "3px 8px", border: "1px solid var(--border)", borderRadius: "var(--radius-sm)", background: "transparent", color: "var(--fg)" }}/>
            </form>
          </div>
        </div>

        <hr className="hr"/>

        <div className="col gap-2">
          <div className="row" style={{ justifyContent: "space-between", flexWrap: "wrap", gap: 8 }}>
            <span style={{ fontSize: 13, fontWeight: 600 }}>{t("form.scripts")} ({scripts.length})</span>
            <div className="row gap-2">
              <Button variant="ghost" size="sm" icon="history" onClick={openHistoryPicker}>{t("form.fromHistory")}</Button>
              <Button variant="ghost" size="sm" icon="plus" onClick={addScript}>{t("form.emptyScript")}</Button>
            </div>
          </div>
          {scripts.map((s, i) => (
            <div key={i} className="card col gap-2" style={{ padding: 12 }}>
              <div className="row gap-2">
                <span className="mono" style={{ fontSize: 11, color: "var(--fg-faint)", width: 20, textAlign: "right", flexShrink: 0, marginTop: 8 }}>{i + 1}</span>
                <Field label="" value={s.name} onChange={e => updateScript(i, "name", e.target.value)} placeholder={t("form.scriptName")} style={{ flex: 1 }}/>
                <div className="row gap-1" style={{ marginTop: 2 }}>
                  <IconButton icon="chevronUp" size="sm" onClick={() => moveScript(i, -1)} disabled={i === 0}
                    style={{ opacity: i === 0 ? 0.3 : 1 }}/>
                  <IconButton icon="chevronDown" size="sm" onClick={() => moveScript(i, 1)} disabled={i === scripts.length - 1}
                    style={{ opacity: i === scripts.length - 1 ? 0.3 : 1 }}/>
                  <IconButton icon="trash" size="sm" onClick={() => removeScript(i)} style={{ opacity: scripts.length <= 1 ? 0.3 : 1 }}/>
                </div>
              </div>
              <textarea className="field mono scroll" value={s.code} onChange={e => updateScript(i, "code", e.target.value)}
                placeholder="Set Field [ ... ]"
                style={{ minHeight: 80, padding: 10, fontSize: 12, lineHeight: 1.6, alignItems: "flex-start", width: "100%", resize: "vertical" }}/>
            </div>
          ))}

          {showHistoryPicker && (
            <div className="card col gap-1" style={{ padding: 0, overflow: "hidden", maxHeight: 240, overflowY: "auto" }}>
              <div className="row" style={{ padding: "8px 12px", borderBottom: "1px solid var(--border)", background: "var(--surface)", position: "sticky", top: 0 }}>
                <span style={{ fontSize: 12, fontWeight: 600, flex: 1 }}>{t("form.pickHistory")}</span>
                <IconButton icon="x" size="sm" onClick={() => setShowHistoryPicker(false)}/>
              </div>
              {historyLoading ? (
                <div style={{ padding: 16, textAlign: "center", color: "var(--fg-muted)", fontSize: 12 }}>{t("community.loading")}</div>
              ) : history.length === 0 ? (
                <div style={{ padding: 16, textAlign: "center", color: "var(--fg-muted)", fontSize: 12 }}>{t("form.historyEmpty")}</div>
              ) : history.map(h => (
                <div key={h.id} className="row gap-2" onClick={() => importFromHistory(h)}
                  style={{ padding: "8px 12px", cursor: "pointer", borderBottom: "1px solid var(--border)", fontSize: 13 }}
                  onMouseEnter={e => e.currentTarget.style.background = "var(--surface-hover)"}
                  onMouseLeave={e => e.currentTarget.style.background = ""}>
                  <Icon name="scripts" size={14} style={{ color: "var(--fg-faint)", flexShrink: 0 }}/>
                  <span style={{ fontWeight: 500, flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{h.name}</span>
                  <span className="mono" style={{ fontSize: 11, color: "var(--fg-faint)" }}>
                    {countActions(h.code)} steps
                  </span>
                  <Icon name="plus" size={14} style={{ color: "var(--accent)" }}/>
                </div>
              ))}
            </div>
          )}
        </div>

        <hr className="hr"/>

        <label className="row gap-2" style={{ cursor: "pointer" }}>
          <input type="checkbox" checked={published} onChange={e => setPublished(e.target.checked)} style={{ accentColor: "var(--accent)" }}/>
          <span style={{ fontSize: 13 }}>{t("form.publish")}</span>
        </label>

        {error && <div style={{ fontSize: 12.5, color: "var(--danger)" }}>{error}</div>}

        <div className="row gap-2" style={{ justifyContent: "flex-end" }}>
          <Button variant="ghost" onClick={onClose}>{t("form.cancel")}</Button>
          <Button variant="primary" icon="check" onClick={save} disabled={saving}>
            {saving ? t("form.saving") : (existing ? t("form.update") : t("form.save"))}
          </Button>
        </div>
      </div>
    </div>
  );
}

// ─────── Community page ───────

function CommunityPage({ onMenuToggle, onLoadScript }) {
  const { t } = useI18n();
  const [modules, setModules] = React.useState([]);
  const [tags, setTags] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [search, setSearch] = React.useState("");
  const [activeTag, setActiveTag] = React.useState(null);
  const [selectedMod, setSelectedMod] = React.useState(null);
  const [showForm, setShowForm] = React.useState(false);
  const [editMod, setEditMod] = React.useState(null);
  const [tab, setTab] = React.useState("explore"); // explore | mine

  const fetchModules = async () => {
    setLoading(true);
    try {
      const params = new URLSearchParams();
      if (search) params.set("search", search);
      if (activeTag) params.set("tag", activeTag);
      const url = tab === "mine" ? "/api/modules/mine" : `/api/modules?${params}`;
      const res = await apiFetch(url);
      const data = await res.json();
      setModules(data.modules || []);
      if (data.tags) setTags(data.tags);
    } catch { /* silent */ }
    setLoading(false);
  };

  React.useEffect(() => { fetchModules(); }, [tab, activeTag]);

  const doSearch = () => fetchModules();

  const toggleLike = async (mod) => {
    try {
      const res = await apiFetch(`/api/modules/${mod.id}/like`, { method: "POST" });
      const data = await res.json();
      setModules(prev => prev.map(m => m.id === mod.id ? {
        ...m, liked: data.liked, likes_count: m.likes_count + (data.liked ? 1 : -1)
      } : m));
    } catch { /* silent */ }
  };

  const toggleFavorite = async (mod) => {
    try {
      const res = await apiFetch(`/api/modules/${mod.id}/favorite`, { method: "POST" });
      const data = await res.json();
      setModules(prev => prev.map(m => m.id === mod.id ? { ...m, favorited: data.favorited } : m));
    } catch { /* silent */ }
  };

  const openEdit = async (mod) => {
    try {
      const res = await apiFetch(`/api/modules/${mod.id}`);
      const data = await res.json();
      setEditMod(data.module);
      setShowForm(true);
    } catch { /* silent */ }
  };

  return (
    <>
      <Topbar breadcrumb={[t("community.breadcrumb"), tab === "mine" ? t("community.myModules") : t("community.explore")]} onMenuToggle={onMenuToggle}/>

      <div className="app-content scroll" style={{ padding: 24 }}>
        <div className="col gap-4">

          {/* Tabs + actions */}
          <div className="row" style={{ justifyContent: "space-between", flexWrap: "wrap", gap: 12 }}>
            <div className="row gap-1">
              <button className={`tab ${tab === "explore" ? "active" : ""}`} onClick={() => setTab("explore")}>{t("community.explore")}</button>
              <button className={`tab ${tab === "mine" ? "active" : ""}`} onClick={() => setTab("mine")}>{t("community.myModules")}</button>
            </div>
            <div className="row gap-2">
              {tab === "explore" && (
                <div className="field" style={{ width: 220, height: 30 }}>
                  <Icon name="search" size={14} style={{ color: "var(--fg-faint)", marginRight: 6 }}/>
                  <input placeholder={t("history.search")} style={{ fontSize: 13 }} value={search}
                    onChange={e => setSearch(e.target.value)}
                    onKeyDown={e => e.key === "Enter" && doSearch()}/>
                </div>
              )}
              <Button variant="primary" size="sm" icon="plus" onClick={() => { setEditMod(null); setShowForm(true); }}>
                {t("community.newModule")}
              </Button>
            </div>
          </div>

          {/* Tags filter */}
          {tab === "explore" && tags.length > 0 && (
            <div className="row gap-1" style={{ flexWrap: "wrap" }}>
              <button className={`badge ${!activeTag ? "badge-accent" : ""}`}
                style={{ cursor: "pointer", border: !activeTag ? "1px solid var(--accent)" : "1px solid var(--border)" }}
                onClick={() => setActiveTag(null)}>{t("community.all")}</button>
              {tags.map(tag => (
                <button key={tag} className={`badge ${activeTag === tag ? "badge-accent" : ""}`}
                  style={{ cursor: "pointer", border: activeTag === tag ? "1px solid var(--accent)" : "1px solid var(--border)" }}
                  onClick={() => setActiveTag(activeTag === tag ? null : tag)}>{tag}</button>
              ))}
            </div>
          )}

          {/* Grid */}
          {loading ? (
            <div style={{ padding: 32, textAlign: "center", color: "var(--fg-muted)", fontSize: 13 }}>{t("community.loading")}</div>
          ) : modules.length === 0 ? (
            <div style={{ padding: 32, textAlign: "center", color: "var(--fg-muted)", fontSize: 13 }}>
              {tab === "mine" ? t("community.noOwn") : t("community.noModules")}
            </div>
          ) : (
            <div className="modules-grid" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 16 }}>
              {modules.map(m => (
                <div key={m.id} style={{ position: "relative" }}>
                  <ModuleCard mod={m} onLike={toggleLike} onFavorite={toggleFavorite} onOpen={setSelectedMod}/>
                  {tab === "mine" && (
                    <div className="row gap-1" style={{ position: "absolute", top: 8, right: 8 }}>
                      <IconButton icon="settings" size="sm" onClick={e => { e.stopPropagation(); openEdit(m); }} title={t("community.edit")}/>
                      <Badge variant={m.published ? "success" : "default"}>{m.published ? t("community.published") : t("community.draft")}</Badge>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {selectedMod && (
        <ModuleDetail mod={selectedMod} onClose={() => setSelectedMod(null)}
          onLoadScript={(s) => { setSelectedMod(null); onLoadScript(s); }}/>
      )}

      {showForm && (
        <ModuleForm existing={editMod} onClose={() => { setShowForm(false); setEditMod(null); }}
          onSaved={() => { setShowForm(false); setEditMod(null); fetchModules(); }}/>
      )}
    </>
  );
}

// ─────── Favorites page ───────

function FavoritesPage({ onMenuToggle, onLoadScript }) {
  const { t } = useI18n();
  const [modules, setModules] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [selectedMod, setSelectedMod] = React.useState(null);

  const fetchFavorites = async () => {
    setLoading(true);
    try {
      const res = await apiFetch("/api/modules/favorites");
      const data = await res.json();
      setModules(data.modules || []);
    } catch { /* silent */ }
    setLoading(false);
  };

  React.useEffect(() => { fetchFavorites(); }, []);

  const toggleLike = async (mod) => {
    try {
      const res = await apiFetch(`/api/modules/${mod.id}/like`, { method: "POST" });
      const data = await res.json();
      setModules(prev => prev.map(m => m.id === mod.id ? {
        ...m, liked: data.liked, likes_count: m.likes_count + (data.liked ? 1 : -1)
      } : m));
    } catch { /* silent */ }
  };

  const toggleFavorite = async (mod) => {
    try {
      const res = await apiFetch(`/api/modules/${mod.id}/favorite`, { method: "POST" });
      const data = await res.json();
      if (!data.favorited) {
        setModules(prev => prev.filter(m => m.id !== mod.id));
      }
    } catch { /* silent */ }
  };

  return (
    <>
      <Topbar breadcrumb={[t("community.breadcrumb"), t("favorites.breadcrumb")]} onMenuToggle={onMenuToggle}/>

      <div className="app-content scroll" style={{ padding: 24 }}>
        <div className="col gap-4">
          {loading ? (
            <div style={{ padding: 32, textAlign: "center", color: "var(--fg-muted)", fontSize: 13 }}>{t("community.loading")}</div>
          ) : modules.length === 0 ? (
            <div className="col gap-2" style={{ padding: 32, textAlign: "center", alignItems: "center" }}>
              <Icon name="star" size={32} style={{ color: "var(--fg-faint)" }}/>
              <span style={{ color: "var(--fg-muted)", fontSize: 13 }}>{t("favorites.empty")}</span>
              <span style={{ color: "var(--fg-faint)", fontSize: 12 }}>{t("favorites.emptyHint")}</span>
            </div>
          ) : (
            <div className="modules-grid" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 16 }}>
              {modules.map(m => (
                <ModuleCard key={m.id} mod={m} onLike={toggleLike} onFavorite={toggleFavorite} onOpen={setSelectedMod}/>
              ))}
            </div>
          )}
        </div>
      </div>

      {selectedMod && (
        <ModuleDetail mod={selectedMod} onClose={() => setSelectedMod(null)}
          onLoadScript={(s) => { setSelectedMod(null); onLoadScript(s); }}/>
      )}
    </>
  );
}

Object.assign(window, { ScriptsPage, FieldsPage, HistoryPage, ProfilePage, SettingsPage, CommunityPage, FavoritesPage });
