// ============================================================
// customizing-sub-2.jsx — Sotto-pagine Customizing (parte 2)
// Categories · CapexClasses · Sites · SLA · Notifications · AI · Templates · Versions
// ============================================================

// -------------- CATEGORIES --------------
function CustCategories() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.1: merge categorie create in sessione (extras vince via dedup per id)
  const cats = React.useMemo(() => {
    const ext = extras?.categoriesExt || [];
    const extIds = new Set(ext.map(c => c.id));
    const seedFiltered = (seedCustom.CATEGORIES_EXT || []).filter(c => !extIds.has(c.id));
    return [...ext, ...seedFiltered];
  }, [extras, seedCustom]);
  const families = seedCustom.CATEGORY_FAMILIES || [];
  const workflows = seedCustom.APPROVAL_WORKFLOWS || [];
  const checklists = seedCustom.CHECKLIST_RULES || [];
  const [sel, setSel] = React.useState(null);
  const [showNew, setShowNew] = React.useState(false);
  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12 }}>
        <Chip>{cats.length} categorie</Chip>
        <span className="spacer"/>
        <ConfigWriteBtn onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuova categoria</ConfigWriteBtn>
      </div>
      <table className="tbl dense">
        <thead><tr>
          <th style={{width:110}}>Code</th>
          <th>Nome</th>
          <th style={{width:120}}>Famiglia</th>
          <th style={{width:130}}>Workflow default</th>
          <th style={{width:130}}>Checklist default</th>
          <th style={{width:80,textAlign:'right'}}>Lead time</th>
          <th style={{width:60,textAlign:'center'}}>HSE</th>
          <th style={{width:70,textAlign:'center'}}>Stato</th>
        </tr></thead>
        <tbody>
          {cats.map(c => (
            <tr key={c.id} className="clickable" onClick={()=>setSel(c)} style={{opacity: c.active === false ? 0.55 : 1}}>
              <td className="mono" style={{fontSize:10.5}}>{c.code}</td>
              <td style={{fontWeight:500}}>{c.name}</td>
              <td style={{fontSize:11.5, color:'var(--text-2)'}}>{c.family || <span style={{color:'var(--text-3)'}}>—</span>}</td>
              <td className="mono" style={{fontSize:10.5}}>{c.defaultWorkflow || <span style={{color:'var(--text-3)'}}>—</span>}</td>
              <td className="mono" style={{fontSize:10.5}}>{c.defaultChecklist || <span style={{color:'var(--text-3)'}}>—</span>}</td>
              <td className="num mono">{c.leadTimeDays != null ? `${c.leadTimeDays}gg` : '—'}</td>
              <td style={{textAlign:'center'}}>{c.hseRequired ? <Chip kind="warn" dot>sì</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
              <td style={{textAlign:'center'}}><Chip kind={c.active===false?'':'ok'} dot>{c.active===false?'off':'on'}</Chip></td>
            </tr>
          ))}
        </tbody>
      </table>
      <CategoryDetailModal sel={sel} onClose={()=>setSel(null)} families={families} workflows={workflows} checklists={checklists}/>
      <NewCategoryModal open={showNew} onClose={()=>setShowNew(false)} cats={cats} families={families} workflows={workflows} checklists={checklists}/>
    </>
  );
}

function NewCategoryModal({ open, onClose, cats, families, workflows, checklists }) {
  const { addCategory, pushToast, user } = useStore();
  const initial = () => ({
    code: '', name: '', familyId: '', description: '',
    defaultWorkflow: '', defaultChecklist: '',
    leadTimeDays: 90, hseRequired: false, active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); setSaving(false); } }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  const codeValid = /^[A-Z][A-Z0-9_]{1,31}$/.test(form.code);
  const codeUnique = !cats.some(c => c.code === form.code);
  const valid = form.name.trim() && codeValid && codeUnique;

  const rdaWorkflows = (workflows || []).filter(w => w.entityType === 'rda');
  const rdaChecklists = (checklists || []).filter(c => c.entityType === 'rda');

  const submit = async () => {
    if (!valid || saving) return;
    setServerError(null);
    setSaving(true);
    try {
      const res = await fetch('/api/config/categories', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Actor-Persona-Id': user?.id || '' },
        body: JSON.stringify({
          code: form.code,
          name: form.name.trim(),
          familyId: form.familyId || null,
          description: form.description.trim() || null,
          hseRequired: !!form.hseRequired,
          leadTimeDays: Number(form.leadTimeDays) || 0,
          defaultWorkflow: form.defaultWorkflow || null,
          defaultChecklist: form.defaultChecklist || null,
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(() => null);
      if (!res.ok) {
        const msg = json?.error === 'validation_error'
          ? (json.issues?.map(i => `${i.path?.join('.') || 'campo'}: ${i.message}`).join(' · ') || 'Validazione fallita')
          : (json?.error || `Errore HTTP ${res.status}`);
        setServerError(msg);
        setSaving(false);
        return;
      }
      const created = json.data;
      addCategory(created);
      pushToast({
        title: 'Categoria creata',
        desc: `${created.code} · ${created.name} salvata in DB. Audit log registrato.`,
        tone: 'ok',
      });
      setSaving(false);
      onClose();
    } catch (err) {
      setServerError(err?.message || 'Errore di rete');
      setSaving(false);
    }
  };

  return (
    <Modal open={open} onClose={onClose} title="Nuova categoria merceologica" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={submit}>{saving ? 'Salvataggio…' : 'Crea categoria'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Le categorie merceologiche classificano gli acquisti. Determinano la famiglia di appartenenza, il workflow di approvazione e la checklist documentale di default proposti per le RdA, il lead time di riferimento e l'obbligo di review HSE.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g,''))} placeholder="AUTOMATION" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="es. Automazione linea"/>
            </div>
          </div>
          {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code: maiuscola iniziale + A-Z, 0-9, _</div>}
          {codeValid && !codeUnique && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code già esistente</div>}
          <div className="field" style={{marginTop:8}}><label>Descrizione</label>
            <textarea rows={2} value={form.description} onChange={e=>set('description', e.target.value)} placeholder="(opzionale) quando si usa questa categoria"/>
          </div>
          <div className="field" style={{marginTop:8}}><label>Famiglia merceologica</label>
            <window.Autocomplete value={form.familyId} onChange={v=>set('familyId', v)}
              options={(families || []).map(f => ({ value: f.id, label: f.name, sublabel: f.code || '' }))}
              placeholder="Nessuna · cerca famiglia…" testId="cust-cat-family-ac" />
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Default applicati alle RdA</div>
          <div className="grid grid-2">
            <div className="field"><label>Workflow di default</label>
              <window.Autocomplete value={form.defaultWorkflow} onChange={v=>set('defaultWorkflow', v)}
                options={rdaWorkflows.map(w => ({ value: w.code, label: w.name, sublabel: w.code }))}
                placeholder="Nessuno · cerca workflow…" testId="cust-cat-workflow-ac" />
            </div>
            <div className="field"><label>Checklist di default</label>
              <window.Autocomplete value={form.defaultChecklist} onChange={v=>set('defaultChecklist', v)}
                options={rdaChecklists.map(c => ({ value: c.id, label: c.name, sublabel: c.id }))}
                placeholder="Nessuna · cerca checklist…" testId="cust-cat-checklist-ac" />
            </div>
            <div className="field"><label>Lead time tipico (giorni)</label>
              <input type="number" min={0} max={720} value={form.leadTimeDays} onChange={e=>set('leadTimeDays', Number(e.target.value))}/>
            </div>
            <div className="field" style={{display:'flex', flexDirection:'column', justifyContent:'flex-end', gap:6}}>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={form.hseRequired} onChange={e=>set('hseRequired', e.target.checked)}/>
                Richiede review HSE
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={form.active} onChange={e=>set('active', e.target.checked)}/>
                Categoria attiva
              </label>
            </div>
          </div>
        </div>
      </div>
    </Modal>
  );
}

// FASE 3a.15 → ridisegnato sessione 95: detail modal editable per Category.
// Stesso set di campi e layout di NewCategoryModal (coerenza crea/modifica).
function CategoryDetailModal({ sel, onClose, families, workflows, checklists }) {
  const { addCategory, pushToast, user } = useStore();
  const rdaWorkflows = (workflows || []).filter(w => w.entityType === 'rda');
  const rdaChecklists = (checklists || []).filter(c => c.entityType === 'rda');
  const initial = React.useMemo(() => {
    if (!sel) return null;
    return {
      code: sel.code || '',
      name: sel.name || '',
      familyId: sel.familyId || '',
      description: sel.description || '',
      hseRequired: !!(sel.hseRequired ?? sel.requiresHSE),
      leadTimeDays: typeof sel.leadTimeDays === 'number' ? sel.leadTimeDays : 90,
      defaultWorkflow: sel.defaultWorkflow || '',
      defaultChecklist: sel.defaultChecklist || '',
      active: sel.active !== false,
    };
  }, [sel]);

  // Hook always-called: passa fallback {} quando sel è null.
  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => sel ? `/api/config/categories/${sel.id}` : null,
    actorId: user?.id,
    buildBody: (f) => ({
      code: f.code,
      name: f.name?.trim(),
      familyId: f.familyId || null,
      description: (f.description || '').trim() || null,
      hseRequired: !!f.hseRequired,
      leadTimeDays: Number(f.leadTimeDays) || 0,
      defaultWorkflow: f.defaultWorkflow || null,
      defaultChecklist: f.defaultChecklist || null,
      active: !!f.active,
    }),
    onSaved: (json) => {
      addCategory(json.data);
      pushToast({ title: 'Categoria aggiornata', desc: `${json.data.code} · ${json.data.name} salvata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!sel) return <Modal open={false} onClose={onClose} title="" />;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_]{1,31}$/.test(form.code);
  const valid = form.name?.trim() && codeValid;

  return (
    <Modal open={!!sel} onClose={onClose} title={`${sel.code} · ${sel.name}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{ gap: 14 }}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}
        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code</label>
              <input value={form.code ?? ''} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
              {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code: maiuscola iniziale + A-Z/0-9/_</div>}
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome</label>
              <input value={form.name ?? ''} onChange={e=>set('name', e.target.value)}/>
            </div>
          </div>
          <div className="field" style={{marginTop:8}}><label>Descrizione</label>
            <textarea rows={2} value={form.description ?? ''} onChange={e=>set('description', e.target.value)} placeholder="(opzionale)"/>
          </div>
          <div className="field" style={{marginTop:8}}><label>Famiglia merceologica</label>
            <window.Autocomplete value={form.familyId || ''} onChange={v=>set('familyId', v)}
              options={(families || []).map(f => ({ value: f.id, label: f.name, sublabel: f.code || '' }))}
              placeholder="Nessuna · cerca famiglia…" testId="cust-cat-family-edit-ac" />
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Default applicati alle RdA</div>
          <div className="grid grid-2">
            <div className="field"><label>Workflow di default</label>
              <window.Autocomplete value={form.defaultWorkflow || ''} onChange={v=>set('defaultWorkflow', v)}
                options={rdaWorkflows.map(w => ({ value: w.code, label: w.name, sublabel: w.code }))}
                placeholder="Nessuno · cerca workflow…" testId="cust-cat-workflow-edit-ac" />
            </div>
            <div className="field"><label>Checklist di default</label>
              <window.Autocomplete value={form.defaultChecklist || ''} onChange={v=>set('defaultChecklist', v)}
                options={rdaChecklists.map(c => ({ value: c.id, label: c.name, sublabel: c.id }))}
                placeholder="Nessuna · cerca checklist…" testId="cust-cat-checklist-edit-ac" />
            </div>
            <div className="field"><label>Lead time tipico (giorni)</label>
              <input type="number" min={0} max={720} value={form.leadTimeDays ?? ''} onChange={e=>set('leadTimeDays', Number(e.target.value))}/>
            </div>
            <div className="field" style={{display:'flex', flexDirection:'column', justifyContent:'flex-end', gap:6}}>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={!!form.hseRequired} onChange={e=>set('hseRequired', e.target.checked)}/>
                Richiede review HSE
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
                Categoria attiva
              </label>
            </div>
          </div>
        </div>
      </div>
    </Modal>
  );
}

// -------------- CAPEX CLASSES --------------
function CustCapexClasses() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.2: merge extras.capexClassesExt + seedCustom.CAPEX_CLASSES con dedup per id
  const classes = React.useMemo(() => {
    const seedList = seedCustom.CAPEX_CLASSES || [];
    const extList = extras?.capexClassesExt || [];
    const seenIds = new Set();
    const out = [];
    for (const c of [...extList, ...seedList]) {
      if (!c?.id || seenIds.has(c.id)) continue;
      seenIds.add(c.id);
      out.push(c);
    }
    return out;
  }, [seedCustom.CAPEX_CLASSES, extras?.capexClassesExt]);
  const [showNew, setShowNew] = React.useState(false);
  // FASE 3a.2.x (sessione 95) — detail modal editable + righe cliccabili.
  const [selClass, setSelClass] = React.useState(null);
  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12 }}>
        <div style={{ fontSize: 12, color: 'var(--text-2)' }}>Le classi di spesa definiscono natura del cespite, conto contabile e regole di ammortamento applicate a ogni CAPEX. Clicca una riga per vedere il dettaglio e modificare.</div>
        <span className="spacer"/>
        <ConfigWriteBtn onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuova classe</ConfigWriteBtn>
      </div>
      <table className="tbl dense">
        <thead><tr>
          <th style={{width:130}}>Code</th>
          <th>Nome</th>
          <th style={{width:95}}>Natura</th>
          <th style={{width:120}}>Conto contabile</th>
          <th style={{width:150}}>Ammortamento</th>
          <th style={{width:55,textAlign:'right'}}>Vita</th>
          <th style={{width:70,textAlign:'center'}}>IVA ded.</th>
          <th style={{width:70,textAlign:'center'}}>Stato</th>
        </tr></thead>
        <tbody>
          {classes.map(c => (
            <tr key={c.id} className="clickable" onClick={()=>setSelClass(c)} style={{opacity: c.active === false ? 0.55 : 1}}>
              <td><span className="row" style={{gap:6}}><span style={{width:9,height:9,borderRadius:3,background:c.color||'#64748b',display:'inline-block',flexShrink:0}}/><Chip kind="ai">{c.code}</Chip></span></td>
              <td style={{fontWeight:500}}>{c.name}</td>
              <td style={{fontSize:11.5}}>{c.nature || '—'}</td>
              <td className="mono" style={{fontSize:11}}>{c.accountCode || <span style={{color:'var(--text-3)'}}>—</span>}</td>
              <td style={{fontSize:11.5}}>{c.depreciationMethod === 'none' ? <span style={{color:'var(--text-3)'}}>nessuno</span> : `${depreciationMethodLabel(c.depreciationMethod).split(' ')[0]} · ${c.depreciationRate ?? 0}%`}</td>
              <td className="mono num">{c.depreciationMethod === 'none' ? '—' : `${c.usefulLifeYears ?? 0}a`}</td>
              <td style={{textAlign:'center'}}>{c.vatDeductible ? <Chip kind="ok">sì</Chip> : <Chip kind="err">no</Chip>}</td>
              <td style={{textAlign:'center'}}><Chip kind={c.active===false?'':'ok'} dot>{c.active===false?'off':'on'}</Chip></td>
            </tr>
          ))}
        </tbody>
      </table>

      <NewCapexClassModal open={showNew} onClose={()=>setShowNew(false)} classes={classes}/>
      <CapexClassDetailModal selClass={selClass} onClose={()=>setSelClass(null)}/>
    </>
  );
}

/**
 * I 9 codici CAPEX class canonical (enum DB capex_class_code).
 * Devono combaciare con CAPEX_CLASS_CODES in apps/web/src/lib/capex-class.ts
 * (FASE 3a.2).
 */
const CAPEX_CLASS_CODES_UC = [
  'GROWTH', 'MAINTENANCE', 'COMPLIANCE', 'EFFICIENCY', 'HSE',
  'IT_OT', 'REPLACEMENT', 'NEW_CAPACITY', 'STRATEGIC',
];

// FASE 3a.2.x (sessione 95) — opzioni condivise tra NewCapexClassModal e
// CapexClassDetailModal. Allineate a CAPEX_NATURES / DEPRECIATION_METHODS in
// apps/web/src/lib/capex-class.ts.
const CAPEX_NATURE_OPTIONS = ['Tangible', 'Intangible', 'Land', 'Software', 'R&D'];
const DEPRECIATION_METHOD_OPTIONS = [
  { val: 'straight_line', label: 'Lineare (straight line)' },
  { val: 'declining_balance', label: 'Decrescente' },
  { val: 'units_of_production', label: 'Per unità prodotte' },
  { val: 'none', label: 'Nessun ammortamento (es. terreni)' },
];
const depreciationMethodLabel = (v) =>
  DEPRECIATION_METHOD_OPTIONS.find((m) => m.val === v)?.label || v || '—';

function NewCapexClassModal({ open, onClose, classes }) {
  const { addCapexClass, pushToast, user, seedCustom } = useStore();
  const projectWorkflows = (seedCustom?.APPROVAL_WORKFLOWS || []).filter((w) => w.entityType === 'project');
  const [form, setForm] = React.useState({
    code: '', name: '', description: '', nature: 'Tangible',
    accountCode: '', depreciationMethod: 'straight_line', depreciationRate: 10,
    usefulLifeYears: 10, vatDeductible: true,
    color: '#2563eb', minAmount: 0,
    requiresBusinessCase: false, requiresPayback: false,
    defaultWorkflow: '',
    notes: '', active: true,
  });
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => {
    if (open) {
      setForm({
        code: '', name: '', description: '', nature: 'Tangible',
        accountCode: '', depreciationMethod: 'straight_line', depreciationRate: 10,
        usefulLifeYears: 10, vatDeductible: true,
        color: '#2563eb', minAmount: 0,
        requiresBusinessCase: false, requiresPayback: false,
        defaultWorkflow: '',
        notes: '', active: true,
      });
      setServerError(null);
    }
  }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  // FASE 3a.2: code è enum stretto (9 valori). Schema DB capex_class_code.
  const codeValid = CAPEX_CLASS_CODES_UC.includes(form.code);
  const codeUnique = !classes.some(c => c.code === form.code);
  const accountValid = !form.accountCode || /^[0-9.]{4,12}$/.test(form.accountCode);
  const lifeRateConsistent = form.depreciationMethod !== 'straight_line' ||
    !form.usefulLifeYears || Math.abs((100/form.usefulLifeYears) - form.depreciationRate) < 2;
  const valid = form.name.trim() && codeValid && codeUnique && accountValid;

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true);
    setServerError(null);
    try {
      const res = await fetch('/api/config/capex-classes', {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}),
        },
        body: JSON.stringify({
          code: form.code, // BE Zod normalizza a lowercase
          name: form.name.trim(),
          description: form.description.trim() || null,
          color: form.color,
          minAmount: Number.isFinite(form.minAmount) && form.minAmount > 0 ? form.minAmount : null,
          requiresBusinessCase: !!form.requiresBusinessCase,
          requiresPayback: !!form.requiresPayback,
          // FASE 8 (s109) — prefab workflow di governance del progetto.
          defaultWorkflow: form.defaultWorkflow || null,
          // FASE 3a.2.x (sessione 95) — attributi contabili/fiscali ora persistiti.
          nature: form.nature,
          accountCode: form.accountCode.trim() || null,
          depreciationMethod: form.depreciationMethod,
          depreciationRate: Number(form.depreciationRate) || 0,
          usefulLifeYears: Number(form.usefulLifeYears) || 1,
          vatDeductible: !!form.vatDeductible,
          notes: form.notes.trim() || null,
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok) {
        const msg = json?.error === 'validation_error'
          ? `Validazione fallita: ${(json.issues || []).map(i => i.message).join(' · ') || 'campi non validi'}`
          : (json?.error || `HTTP ${res.status}`);
        setServerError(msg);
        return;
      }
      const created = json?.data;
      if (created) {
        addCapexClass(created);
        pushToast({ title: `${created.code} · ${created.name}`, desc: 'Classe CAPEX salvata in DB. Audit log registrato.', tone: 'ok' });
      }
      onClose();
    } catch (err) {
      setServerError(String(err?.message || err));
    } finally {
      setSaving(false);
    }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuova classe CAPEX" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea classe'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Le classi CAPEX determinano il trattamento contabile e fiscale di un investimento: conto di mastro, metodo e durata di ammortamento, deducibilità IVA e obblighi di business case / payback. Le RdA e i progetti referenziano questa classe in fase di creazione. Vedi <code>docs/capex-classes.md</code>.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g,''))} placeholder="NEW_CAPACITY" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="es. New capacity"/>
            </div>
          </div>
          <div className="field" style={{marginTop:8}}><label>Descrizione</label>
            <textarea rows={2} value={form.description} onChange={e=>set('description', e.target.value)} placeholder="Quando si usa questa classe, esempi tipici"/>
          </div>
          {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code deve essere uno dei 9 canonici: {CAPEX_CLASS_CODES_UC.join(', ')}</div>}
          {codeValid && !codeUnique && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code già esistente</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Natura e contabilità</div>
          <div className="grid grid-3">
            <div className="field"><label>Natura</label>
              <select value={form.nature} onChange={e=>set('nature', e.target.value)}>
                {CAPEX_NATURE_OPTIONS.map(n => <option key={n} value={n}>{n}</option>)}
              </select>
            </div>
            <div className="field"><label>Conto contabile</label>
              <input value={form.accountCode} onChange={e=>set('accountCode', e.target.value)} placeholder="15.20.10" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Colore (tag)</label>
              <input type="color" value={form.color} onChange={e=>set('color', e.target.value)} style={{padding:2, height:30}}/>
            </div>
          </div>
          {form.accountCode && !accountValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Conto: solo cifre e punti, 4-12 caratteri</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Ammortamento</div>
          <div className="grid grid-3">
            <div className="field"><label>Metodo</label>
              <select value={form.depreciationMethod} onChange={e=>set('depreciationMethod', e.target.value)}>
                {DEPRECIATION_METHOD_OPTIONS.map(m => <option key={m.val} value={m.val}>{m.label}</option>)}
              </select>
            </div>
            <div className="field"><label>Aliquota %</label>
              <input type="number" min={0} max={100} step={0.5} value={form.depreciationRate} onChange={e=>set('depreciationRate', Number(e.target.value))} disabled={form.depreciationMethod==='none'}/>
            </div>
            <div className="field"><label>Vita utile (anni)</label>
              <input type="number" min={1} max={50} value={form.usefulLifeYears} onChange={e=>set('usefulLifeYears', Number(e.target.value))} disabled={form.depreciationMethod==='none'}/>
            </div>
          </div>
          {!lifeRateConsistent && (
            <div style={{fontSize:10.5, color:'var(--warn)', marginTop:4}}>
              <Icon name="alert-triangle" size={10}/> Aliquota {form.depreciationRate}% e vita utile {form.usefulLifeYears}a non coerenti con ammortamento lineare (atteso ≈ {(100/form.usefulLifeYears).toFixed(1)}%).
            </div>
          )}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Obblighi e soglie</div>
          <div className="grid grid-2">
            <div className="field"><label>Importo minimo per applicare la classe (€)</label>
              <input type="number" min={0} step={5000} value={form.minAmount} onChange={e=>set('minAmount', Number(e.target.value))}/>
            </div>
            <div className="field" style={{display:'flex', flexDirection:'column', justifyContent:'flex-end', gap:6}}>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={form.vatDeductible} onChange={e=>set('vatDeductible', e.target.checked)}/>
                IVA deducibile
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={form.requiresBusinessCase} onChange={e=>set('requiresBusinessCase', e.target.checked)}/>
                Richiede Business Case
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={form.requiresPayback} onChange={e=>set('requiresPayback', e.target.checked)}/>
                Richiede analisi di payback
              </label>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Governance progetto</div>
          <div className="field"><label>Workflow di default</label>
            <select value={form.defaultWorkflow} onChange={e=>set('defaultWorkflow', e.target.value)}>
              <option value="">— Nessuno —</option>
              {projectWorkflows.map(w => <option key={w.code} value={w.code}>{w.code} · {w.name}</option>)}
            </select>
            <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:4}}>
              Avviato come prefab di governance sul progetto di questa classe (dal bottone «Re-applica prefab» nel ProjectDetail). Solo workflow <code>entity_type=project</code>.
            </div>
          </div>
        </div>

        <div className="field"><label>Note</label>
          <textarea rows={2} value={form.notes} onChange={e=>set('notes', e.target.value)} placeholder="Riferimenti a policy fiscali, casi particolari"/>
        </div>

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/capex-classes</code> persiste in DB <strong>tutti i campi</strong> (anagrafica + contabilità + ammortamento + obblighi) con audit log automatico.
        </div>

        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.2.x (sessione 95) — detail modal editable per classe CAPEX.
// PATCH /api/config/capex-classes/[id]. Code read-only (identità della classe).
function CapexClassDetailModal({ selClass, onClose }) {
  const { addCapexClass, pushToast, user, seedCustom } = useStore();
  const projectWorkflows = (seedCustom?.APPROVAL_WORKFLOWS || []).filter((w) => w.entityType === 'project');
  const initial = React.useMemo(() => {
    if (!selClass) return null;
    return {
      name: selClass.name || '',
      description: selClass.description || '',
      color: selClass.color || '#64748b',
      nature: selClass.nature || 'Tangible',
      accountCode: selClass.accountCode || '',
      depreciationMethod: selClass.depreciationMethod || 'straight_line',
      depreciationRate: typeof selClass.depreciationRate === 'number' ? selClass.depreciationRate : 10,
      usefulLifeYears: typeof selClass.usefulLifeYears === 'number' ? selClass.usefulLifeYears : 10,
      vatDeductible: selClass.vatDeductible !== false,
      minAmount: typeof selClass.minAmount === 'number' ? selClass.minAmount : 0,
      requiresBusinessCase: !!selClass.requiresBusinessCase,
      requiresPayback: !!selClass.requiresPayback,
      defaultWorkflow: selClass.defaultWorkflow || '',
      notes: selClass.notes || '',
      active: selClass.active !== false,
    };
  }, [selClass]);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => (selClass ? `/api/config/capex-classes/${selClass.id}` : null),
    actorId: user?.id,
    buildBody: (f) => ({
      name: f.name?.trim(),
      description: (f.description || '').trim() || null,
      color: f.color,
      nature: f.nature,
      accountCode: (f.accountCode || '').trim() || null,
      depreciationMethod: f.depreciationMethod,
      depreciationRate: Number(f.depreciationRate) || 0,
      usefulLifeYears: Number(f.usefulLifeYears) || 1,
      vatDeductible: !!f.vatDeductible,
      minAmount: Number.isFinite(Number(f.minAmount)) && Number(f.minAmount) > 0 ? Number(f.minAmount) : null,
      requiresBusinessCase: !!f.requiresBusinessCase,
      requiresPayback: !!f.requiresPayback,
      defaultWorkflow: f.defaultWorkflow || null,
      notes: (f.notes || '').trim() || null,
      active: !!f.active,
    }),
    onSaved: (json) => {
      addCapexClass(json.data);
      pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: `Classe CAPEX aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selClass) return <Modal open={false} onClose={onClose} title="" />;

  const accountValid = !form.accountCode || /^[0-9.]{4,12}$/.test(form.accountCode);
  const valid = form.name?.trim() && accountValid;
  const noDep = form.depreciationMethod === 'none';

  return (
    <Modal open={!!selClass} onClose={onClose} title={`${selClass.code} · ${selClass.name}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding:'10px 12px', border:'1px solid var(--err, #c0392b)', borderRadius:6, background:'rgba(192,57,43,0.08)', color:'var(--err, #c0392b)', fontSize:12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code</label>
              <input value={selClass.code} disabled style={{fontFamily:'var(--font-mono)', opacity:0.6}}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome</label>
              <input value={form.name ?? ''} onChange={e=>set('name', e.target.value)}/>
            </div>
          </div>
          <div className="field" style={{marginTop:8}}><label>Descrizione</label>
            <textarea rows={2} value={form.description ?? ''} onChange={e=>set('description', e.target.value)}/>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Natura e contabilità</div>
          <div className="grid grid-3">
            <div className="field"><label>Natura</label>
              <select value={form.nature || 'Tangible'} onChange={e=>set('nature', e.target.value)}>
                {CAPEX_NATURE_OPTIONS.map(n => <option key={n} value={n}>{n}</option>)}
              </select>
            </div>
            <div className="field"><label>Conto contabile</label>
              <input value={form.accountCode ?? ''} onChange={e=>set('accountCode', e.target.value)} placeholder="15.20.10" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Colore (tag)</label>
              <input type="color" value={form.color || '#64748b'} onChange={e=>set('color', e.target.value)} style={{padding:2, height:30}}/>
            </div>
          </div>
          {form.accountCode && !accountValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Conto: solo cifre e punti, 4-12 caratteri</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Ammortamento</div>
          <div className="grid grid-3">
            <div className="field"><label>Metodo</label>
              <select value={form.depreciationMethod || 'straight_line'} onChange={e=>set('depreciationMethod', e.target.value)}>
                {DEPRECIATION_METHOD_OPTIONS.map(m => <option key={m.val} value={m.val}>{m.label}</option>)}
              </select>
            </div>
            <div className="field"><label>Aliquota %</label>
              <input type="number" min={0} max={100} step={0.5} value={form.depreciationRate ?? ''} onChange={e=>set('depreciationRate', Number(e.target.value))} disabled={noDep}/>
            </div>
            <div className="field"><label>Vita utile (anni)</label>
              <input type="number" min={1} max={50} value={form.usefulLifeYears ?? ''} onChange={e=>set('usefulLifeYears', Number(e.target.value))} disabled={noDep}/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Obblighi e soglie</div>
          <div className="grid grid-2">
            <div className="field"><label>Importo minimo per applicare la classe (€)</label>
              <input type="number" min={0} step={5000} value={form.minAmount ?? ''} onChange={e=>set('minAmount', Number(e.target.value))}/>
            </div>
            <div className="field" style={{display:'flex', flexDirection:'column', justifyContent:'flex-end', gap:6}}>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={!!form.vatDeductible} onChange={e=>set('vatDeductible', e.target.checked)}/> IVA deducibile
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={!!form.requiresBusinessCase} onChange={e=>set('requiresBusinessCase', e.target.checked)}/> Richiede Business Case
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="checkbox" checked={!!form.requiresPayback} onChange={e=>set('requiresPayback', e.target.checked)}/> Richiede analisi di payback
              </label>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Governance progetto</div>
          <div className="field"><label>Workflow di default</label>
            <select value={form.defaultWorkflow ?? ''} onChange={e=>set('defaultWorkflow', e.target.value)}>
              <option value="">— Nessuno —</option>
              {projectWorkflows.map(w => <option key={w.code} value={w.code}>{w.code} · {w.name}</option>)}
            </select>
            <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:4}}>
              Avviato come prefab di governance sul progetto di questa classe (bottone «Re-applica prefab» nel ProjectDetail). Solo workflow <code>entity_type=project</code>.
            </div>
          </div>
        </div>

        <div className="field"><label>Note</label>
          <textarea rows={2} value={form.notes ?? ''} onChange={e=>set('notes', e.target.value)} placeholder="Riferimenti a policy fiscali, casi particolari"/>
        </div>

        <div className="field"><label className="row" style={{gap:6}}>
          <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/> Classe attiva
        </label></div>
      </div>
    </Modal>
  );
}

// -------------- SITES --------------
function CustSites() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.4: merge extras.sitesExt + seedCustom.SITES_EXT con dedup per id.
  const sites = React.useMemo(() => {
    const seedList = seedCustom.SITES_EXT || [];
    const extList = extras?.sitesExt || [];
    const seenIds = new Set();
    const out = [];
    for (const s of [...extList, ...seedList]) {
      if (!s?.id || seenIds.has(s.id)) continue;
      seenIds.add(s.id);
      out.push(s);
    }
    return out;
  }, [seedCustom.SITES_EXT, extras?.sitesExt]);
  // FASE 3a.6: merge legal entities + BU extras + seed per tabelle e dropdown.
  const dedupById = (extList, seedList) => {
    const seenIds = new Set();
    const out = [];
    for (const it of [...(extList || []), ...(seedList || [])]) {
      if (!it?.id || seenIds.has(it.id)) continue;
      seenIds.add(it.id);
      out.push(it);
    }
    return out;
  };
  const entities = React.useMemo(() => dedupById(extras?.legalEntitiesExt, seedCustom.LEGAL_ENTITIES), [seedCustom.LEGAL_ENTITIES, extras?.legalEntitiesExt]);
  const bus = React.useMemo(() => dedupById(extras?.businessUnitsExt, seedCustom.BUSINESS_UNITS), [seedCustom.BUSINESS_UNITS, extras?.businessUnitsExt]);
  // FASE 3a.5: merge calendari extras + seed per dropdown NewSiteModal (test integrazione).
  const calendars = React.useMemo(() => dedupById(extras?.calendarsExt, seedCustom.CALENDARS), [seedCustom.CALENDARS, extras?.calendarsExt]);
  const [showNew, setShowNew] = React.useState(false);
  const [showNewLE, setShowNewLE] = React.useState(false);
  const [showNewBU, setShowNewBU] = React.useState(false);
  // FASE 3a.15 batch: detail modal editable Sites + LE + BU
  const [sel, setSel] = React.useState(null);
  const [selLE, setSelLE] = React.useState(null);
  const [selBU, setSelBU] = React.useState(null);

  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12, alignItems: 'center' }}>
        <Chip>{sites.length} siti</Chip>
        <Chip>{entities.length} legal entity</Chip>
        <Chip>{bus.length} BU</Chip>
        <span className="spacer"/>
        <ConfigWriteBtn onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuovo sito</ConfigWriteBtn>
      </div>

      <NewSiteModal open={showNew} onClose={()=>setShowNew(false)} sites={sites} bus={bus} calendars={calendars}/>
      <SiteDetailModal sel={sel} onClose={()=>setSel(null)} bus={bus} calendars={calendars}/>

      <div className="eyebrow" style={{marginBottom:6}}>Siti produttivi ({sites.length})</div>
      <table className="tbl dense" style={{marginBottom:20}}>
        <thead><tr>
          <th>Code</th><th>Nome</th><th>Paese</th><th>Legal entity</th>
          <th style={{width:100}}>Centro costo</th><th style={{textAlign:'right'}}>CAPEX YTD</th>
          <th>Certificazioni</th><th style={{textAlign:'center'}}>ATEX</th>
        </tr></thead>
        <tbody>
          {sites.map(s => (
            <tr key={s.id} className="clickable" onClick={()=>setSel(s)}>
              <td className="mono">{s.code}</td>
              <td style={{fontWeight:500}}>{s.name}</td>
              <td style={{fontSize:11.5}}>{s.country}</td>
              <td style={{fontSize:11}}>{s.legalEntity}</td>
              <td className="mono" style={{fontSize:11}}>{s.costCenter}</td>
              <td className="num mono">{fmtEUR(s.capexYtd, true)}</td>
              <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(s.certifications||[]).map(c => <Chip key={c} kind="ok">{c}</Chip>)}</div></td>
              <td style={{textAlign:'center'}}>{s.atex ? <Chip kind="warn" dot>ATEX</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <div className="grid grid-2" style={{ gap: 14 }}>
        <div>
          <div className="row" style={{marginBottom:6, alignItems:'center'}}>
            <div className="eyebrow">Legal entity ({entities.length})</div>
            <span className="spacer"/>
            <ConfigWriteBtn variant="ghost" onClick={()=>setShowNewLE(true)}><Icon name="plus" size={11}/> Nuova legal entity</ConfigWriteBtn>
          </div>
          <table className="tbl dense">
            <thead><tr><th>Code</th><th>Nome</th><th>VAT</th><th>Paese</th><th style={{width:60,textAlign:'center'}}>Stato</th></tr></thead>
            <tbody>{entities.map(e => (
              <tr key={e.id} className="clickable" onClick={()=>setSelLE(e)}>
                <td className="mono">{e.code}</td>
                <td style={{fontWeight:500}}>{e.name}</td>
                <td className="mono" style={{fontSize:11}}>{e.vat}</td>
                <td>{e.country}</td>
                <td style={{textAlign:'center'}}><Chip kind={e.active?'ok':''} dot>{e.active?'on':'off'}</Chip></td>
              </tr>
            ))}</tbody>
          </table>
        </div>
        <div>
          <div className="row" style={{marginBottom:6, alignItems:'center'}}>
            <div className="eyebrow">Business units ({bus.length})</div>
            <span className="spacer"/>
            <ConfigWriteBtn variant="ghost" onClick={()=>setShowNewBU(true)}><Icon name="plus" size={11}/> Nuova BU</ConfigWriteBtn>
          </div>
          <table className="tbl dense">
            <thead><tr><th>Code</th><th>Nome</th><th>Legal entity</th><th>Manager</th><th style={{width:60,textAlign:'center'}}>Stato</th></tr></thead>
            <tbody>{bus.map(b => (
              <tr key={b.id} className="clickable" onClick={()=>setSelBU(b)}>
                <td className="mono">{b.code}</td>
                <td style={{fontWeight:500}}>{b.name}</td>
                <td className="mono" style={{fontSize:10.5}}>{b.legalEntity?.replace('LE_','')?.slice(0,12) || '—'}</td>
                <td style={{fontSize:11}}>{b.director}</td>
                <td style={{textAlign:'center'}}><Chip kind={b.active?'ok':''} dot>{b.active?'on':'off'}</Chip></td>
              </tr>
            ))}</tbody>
          </table>
        </div>
      </div>

      <NewLegalEntityModal open={showNewLE} onClose={()=>setShowNewLE(false)} entities={entities}/>
      <NewBusinessUnitModal open={showNewBU} onClose={()=>setShowNewBU(false)} bus={bus} entities={entities}/>
      <LegalEntityDetailModal selLE={selLE} onClose={()=>setSelLE(null)} entities={entities}/>
      <BusinessUnitDetailModal selBU={selBU} onClose={()=>setSelBU(null)} bus={bus} entities={entities}/>
    </>
  );
}

function NewLegalEntityModal({ open, onClose, entities }) {
  const { addLegalEntity, pushToast, user } = useStore();
  const initial = () => ({
    legalName: '', shortName: '', vatId: '', fiscalCode: '',
    defaultCurrency: 'EUR',
    addrLine1: '', addrCity: '', addrPostal: '', addrCountry: 'IT',
    active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  const legalNameValid = form.legalName.trim().length > 0;
  const shortNameValid = form.shortName.trim().length > 0;
  const shortNameUnique = !entities.some(e => e.shortName === form.shortName);
  const vatValid = form.vatId.trim().length >= 5;
  const vatUnique = !entities.some(e => e.vat === form.vatId);
  const valid = legalNameValid && shortNameValid && shortNameUnique && vatValid && vatUnique;

  const buildAddress = () => {
    if (!form.addrLine1.trim() && !form.addrCity.trim()) return null;
    return {
      line1: form.addrLine1.trim() || '—',
      city: form.addrCity.trim() || '—',
      postal_code: form.addrPostal.trim() || '—',
      country: form.addrCountry.trim().slice(0,2).toUpperCase() || 'IT',
    };
  };

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/legal-entities', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          legalName: form.legalName.trim(),
          shortName: form.shortName.trim(),
          vatId: form.vatId.trim(),
          fiscalCode: form.fiscalCode.trim() || null,
          defaultCurrency: form.defaultCurrency,
          address: buildAddress(),
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) { addLegalEntity(json.data); pushToast({ title: `${json.data.shortName}`, desc: 'Legal entity salvata in DB. Audit registrato.', tone: 'ok' }); }
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  const countries = ['IT','DE','FR','ES','GB','US','CN','JP','PL','RO','TR'];
  const currencies = ['EUR','USD','GBP','CHF','CNY','JPY'];

  return (
    <Modal open={open} onClose={onClose} title="Nuova legal entity" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea legal entity'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Nuova entità giuridica del tenant. Le BU saranno associate a questa entità; le ConfigVersion sono scoped per legal entity. Vedi <code>docs/data-model.md §1.1</code>.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identità</div>
          <div className="grid grid-2">
            <div className="field"><label>Ragione sociale completa <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.legalName} onChange={e=>set('legalName', e.target.value)} placeholder="es. Industrial Demo Italy SpA"/>
            </div>
            <div className="field"><label>Nome breve <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.shortName} onChange={e=>set('shortName', e.target.value)} placeholder="es. Industrial IT"/>
            </div>
          </div>
          {form.shortName && !shortNameUnique && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> shortName già esistente</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Fiscale</div>
          <div className="grid grid-3">
            <div className="field"><label>Partita IVA <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.vatId} onChange={e=>set('vatId', e.target.value)} placeholder="IT01234567890" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Codice fiscale</label>
              <input value={form.fiscalCode} onChange={e=>set('fiscalCode', e.target.value)} placeholder="opzionale" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Valuta default</label>
              <select value={form.defaultCurrency} onChange={e=>set('defaultCurrency', e.target.value)}>
                {currencies.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
          </div>
          {form.vatId && !vatUnique && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Partita IVA già esistente</div>}
          {form.vatId && !vatValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Partita IVA: minimo 5 caratteri</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Sede legale (opzionale)</div>
          <div className="grid grid-3">
            <div className="field" style={{gridColumn:'1 / -1'}}><label>Via e numero</label>
              <input value={form.addrLine1} onChange={e=>set('addrLine1', e.target.value)}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Città</label>
              <input value={form.addrCity} onChange={e=>set('addrCity', e.target.value)}/>
            </div>
            <div className="field"><label>CAP</label>
              <input value={form.addrPostal} onChange={e=>set('addrPostal', e.target.value)}/>
            </div>
            <div className="field"><label>Paese</label>
              <select value={form.addrCountry} onChange={e=>set('addrCountry', e.target.value)}>
                {countries.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
          </div>
        </div>

        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Legal Entity (PATCH /api/config/legal-entities/[id]).
function LegalEntityDetailModal({ selLE, onClose, entities }) {
  const { addLegalEntity, pushToast, user } = useStore();
  const currencies = ['EUR','USD','GBP','CHF','PLN','RON','TRY','CNY','JPY'];
  const countries = ['IT','DE','FR','ES','GB','US','CN','JP','PL','RO','TR'];

  const initial = React.useMemo(() => {
    if (!selLE) return null;
    const country2 = (selLE.address?.country || selLE.country || 'IT').toString().slice(0, 2).toUpperCase();
    return {
      legalName: selLE.legalName || selLE.name || '',
      shortName: selLE.shortName || '',
      vatId: selLE.vatId || selLE.vat || '',
      fiscalCode: selLE.fiscalCode || '',
      defaultCurrency: selLE.defaultCurrency || selLE.currency || 'EUR',
      addrLine1: selLE.address?.line1 || '',
      addrLine2: selLE.address?.line2 || '',
      addrCity: selLE.address?.city || '',
      addrProvince: selLE.address?.province || '',
      addrPostal: selLE.address?.postal_code || '',
      addrCountry: country2,
      active: selLE.active !== false,
    };
  }, [selLE]);

  const buildBody = React.useCallback((f) => {
    const hasAddr = (f.addrLine1 || '').trim() || (f.addrCity || '').trim() || (f.addrPostal || '').trim();
    const address = hasAddr
      ? {
          line1: (f.addrLine1 || '').trim() || '—',
          ...((f.addrLine2 || '').trim() ? { line2: (f.addrLine2 || '').trim() } : {}),
          city: (f.addrCity || '').trim() || '—',
          ...((f.addrProvince || '').trim() ? { province: (f.addrProvince || '').trim() } : {}),
          postal_code: (f.addrPostal || '').trim() || '—',
          country: (f.addrCountry || 'IT').slice(0, 2).toUpperCase(),
        }
      : null;
    return {
      legalName: (f.legalName || '').trim(),
      shortName: (f.shortName || '').trim(),
      vatId: (f.vatId || '').trim(),
      fiscalCode: (f.fiscalCode || '').trim() ? (f.fiscalCode || '').trim() : null,
      defaultCurrency: (f.defaultCurrency || 'EUR').toUpperCase().slice(0, 3),
      address,
      active: !!f.active,
    };
  }, []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selLE ? `/api/config/legal-entities/${selLE.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addLegalEntity(json.data);
      pushToast({ title: `${json.data.shortName || json.data.legalName}`, desc: `Legal entity aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selLE) return <Modal open={false} onClose={onClose} title="" />;

  const legalNameValid = (form.legalName || '').trim().length > 0;
  const shortNameValid = (form.shortName || '').trim().length > 0;
  const shortNameUnique = !(entities || []).some(e => e.id !== selLE.id && (e.shortName === (form.shortName || '').trim()));
  const vatValid = (form.vatId || '').trim().length >= 5;
  const vatUnique = !(entities || []).some(e => e.id !== selLE.id && (e.vat === (form.vatId || '').trim() || e.vatId === (form.vatId || '').trim()));
  const valid = legalNameValid && shortNameValid && shortNameUnique && vatValid && vatUnique;

  return (
    <Modal open={!!selLE} onClose={onClose} title={`${selLE.shortName || selLE.code} · ${selLE.legalName || selLE.name}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-2">
            <div className="field"><label>Short name</label>
              <input value={form.shortName || ''} onChange={e=>set('shortName', e.target.value)}/>
              {!shortNameValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
              {shortNameValid && !shortNameUnique && <div style={{fontSize:10.5, color:'var(--err)'}}>Già usato da altra entità</div>}
            </div>
            <div className="field"><label>Legal name</label>
              <input value={form.legalName || ''} onChange={e=>set('legalName', e.target.value)}/>
              {!legalNameValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativi fiscali</div>
          <div className="grid grid-3">
            <div className="field"><label>VAT ID (P.IVA)</label>
              <input value={form.vatId || ''} onChange={e=>set('vatId', e.target.value)} style={{fontFamily:'var(--font-mono)'}}/>
              {!vatValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Min 5 caratteri</div>}
              {vatValid && !vatUnique && <div style={{fontSize:10.5, color:'var(--err)'}}>Già usato</div>}
            </div>
            <div className="field"><label>Codice fiscale</label>
              <input value={form.fiscalCode || ''} onChange={e=>set('fiscalCode', e.target.value)} placeholder="(opzionale)" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Valuta default</label>
              <select value={form.defaultCurrency || 'EUR'} onChange={e=>set('defaultCurrency', e.target.value)}>
                {currencies.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Sede legale (opzionale)</div>
          <div className="grid grid-3">
            <div className="field" style={{gridColumn:'1 / -1'}}><label>Via e numero</label>
              <input value={form.addrLine1 || ''} onChange={e=>set('addrLine1', e.target.value)}/>
            </div>
            <div className="field" style={{gridColumn:'1 / -1'}}><label>Riga 2 (opzionale)</label>
              <input value={form.addrLine2 || ''} onChange={e=>set('addrLine2', e.target.value)} placeholder="presso, scala, ecc."/>
            </div>
            <div className="field"><label>Città</label>
              <input value={form.addrCity || ''} onChange={e=>set('addrCity', e.target.value)}/>
            </div>
            <div className="field"><label>Provincia</label>
              <input value={form.addrProvince || ''} onChange={e=>set('addrProvince', e.target.value)} placeholder="es. NO"/>
            </div>
            <div className="field"><label>CAP</label>
              <input value={form.addrPostal || ''} onChange={e=>set('addrPostal', e.target.value)}/>
            </div>
            <div className="field"><label>Paese (ISO 2)</label>
              <select value={form.addrCountry || 'IT'} onChange={e=>set('addrCountry', e.target.value)}>
                {countries.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
          </div>
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Legal entity attiva
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/legal-entities/{selLE.id}</code> persisterà la modifica con audit log automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

function NewBusinessUnitModal({ open, onClose, bus, entities }) {
  const { seed, addBusinessUnit, pushToast, user } = useStore();
  const personas = (seed && seed.PERSONAS) || [];
  const initial = () => ({
    code: '', name: '',
    legalEntityId: entities[0]?.id || '',
    managerPersonaId: '',
    description: '', active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open, entities]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  const codeValid = /^[A-Z][A-Z0-9_-]{1,31}$/.test(form.code);
  const codeUnique = !bus.some(b => b.code === form.code);
  const valid = form.name.trim() && codeValid && codeUnique && form.legalEntityId;

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/business-units', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          code: form.code,
          name: form.name.trim(),
          legalEntityId: form.legalEntityId,
          managerPersonaId: form.managerPersonaId || null,
          description: form.description.trim() || null,
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) { addBusinessUnit(json.data); pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: 'Business unit salvata in DB. Audit registrato.', tone: 'ok' }); }
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuova business unit" size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea BU'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Le BU raggruppano i siti per finalità di approvazione, reporting e budget. Devono appartenere a una legal entity esistente.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} placeholder="AERO" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="es. Aerostructures"/>
            </div>
          </div>
          {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code: maiuscola iniziale + A-Z/0-9/_/-</div>}
          {codeValid && !codeUnique && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code già esistente</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Associazioni</div>
          <div className="grid grid-2">
            <div className="field"><label>Legal entity <span style={{color:'var(--err)'}}>*</span></label>
              <window.Autocomplete value={form.legalEntityId} onChange={v=>set('legalEntityId', v)}
                options={entities.map(le => ({ value: le.id, label: (le.shortName || le.name), sublabel: le.code || '' }))}
                placeholder="Cerca legal entity… (spazio per lista)" testId="cust-bu-le-ac" />
            </div>
            <div className="field"><label>Manager (persona)</label>
              <PersonaAutocomplete
                value={form.managerPersonaId}
                onChange={(id) => set('managerPersonaId', id || '')}
                placeholder="Cerca manager…"
                personasFallback={personas}
              />
            </div>
          </div>
        </div>

        <div className="field"><label>Descrizione</label>
          <textarea rows={2} value={form.description} onChange={e=>set('description', e.target.value)} placeholder="A cosa si dedica questa BU"/>
        </div>

        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Business Unit (PATCH /api/config/business-units/[id]).
function BusinessUnitDetailModal({ selBU, onClose, bus, entities }) {
  const { addBusinessUnit, pushToast, user, seed } = useStore();
  const personas = (seed && seed.PERSONAS) || [];

  const initial = React.useMemo(() => {
    if (!selBU) return null;
    return {
      code: selBU.code || '',
      name: selBU.name || '',
      legalEntityId: selBU.legalEntityId || selBU.legalEntity || '',
      managerPersonaId: selBU.managerPersonaId || (selBU.director && selBU.director !== 'u05' ? selBU.director : '') || '',
      description: selBU.description || '',
      active: selBU.active !== false,
    };
  }, [selBU]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    name: (f.name || '').trim(),
    legalEntityId: f.legalEntityId,
    managerPersonaId: f.managerPersonaId ? f.managerPersonaId : null,
    description: (f.description || '').trim() ? (f.description || '').trim() : null,
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selBU ? `/api/config/business-units/${selBU.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addBusinessUnit(json.data);
      pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: `Business unit aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selBU) return <Modal open={false} onClose={onClose} title="" />;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_-]{1,31}$/.test(form.code);
  const codeUnique = !(bus || []).some(b => b.id !== selBU.id && b.code === form.code);
  const nameValid = (form.name || '').trim().length > 0;
  const legalValid = !!form.legalEntityId;
  const valid = codeValid && codeUnique && nameValid && legalValid;

  return (
    <Modal open={!!selBU} onClose={onClose} title={`${selBU.code} · ${selBU.name}`} size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div className="grid grid-2">
          <div className="field"><label>Code</label>
            <input value={form.code || ''} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
            {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code: maiuscola iniziale + A-Z, 0-9, _, - (max 32)</div>}
            {codeValid && form.code && !codeUnique && <div style={{fontSize:10.5, color:'var(--err)'}}>Code già usato da altra BU</div>}
          </div>
          <div className="field"><label>Nome</label>
            <input value={form.name || ''} onChange={e=>set('name', e.target.value)}/>
            {!nameValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
          </div>
        </div>

        <div className="field"><label>Legal entity</label>
          <window.Autocomplete value={form.legalEntityId || ''} onChange={v=>set('legalEntityId', v)}
            options={(entities || []).map(le => ({ value: le.id, label: (le.shortName || le.name), sublabel: le.code || '' }))}
            placeholder="Cerca legal entity… (spazio per lista)" testId="cust-bu-le-edit-ac" />
          {!legalValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Manager (persona)</div>
          <PersonaAutocomplete
            value={form.managerPersonaId}
            onChange={(id) => set('managerPersonaId', id || '')}
            placeholder="Cerca manager…"
            personasFallback={personas}
          />
        </div>

        <div className="field"><label>Descrizione</label>
          <textarea rows={2} value={form.description || ''} onChange={e=>set('description', e.target.value)} placeholder="(opzionale)"/>
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Business unit attiva
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/business-units/{selBU.id}</code> persisterà la modifica con audit log automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

function NewSiteModal({ open, onClose, sites, bus, calendars }) {
  const { addSite, pushToast, user } = useStore();
  const initial = () => ({
    code: '', name: '', country: 'IT', timezone: 'Europe/Rome',
    addrLine1: '', addrCity: '', addrPostal: '',
    buIds: [], calendarId: (calendars && calendars[0]?.id) || '',
    active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open, calendars]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  const codeValid = /^[A-Z][A-Z0-9_-]{1,31}$/.test(form.code);
  const codeUnique = !sites.some(s => s.code === form.code);
  const valid = form.name.trim() && form.country.trim() && codeValid && codeUnique;

  const buildAddress = () => {
    if (!form.addrLine1.trim() && !form.addrCity.trim() && !form.addrPostal.trim()) return null;
    return {
      line1: form.addrLine1.trim() || '—',
      city: form.addrCity.trim() || '—',
      postal_code: form.addrPostal.trim() || '—',
      country: form.country.trim().slice(0, 2).toUpperCase() || 'IT',
    };
  };

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/sites', {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}),
        },
        body: JSON.stringify({
          code: form.code,
          name: form.name.trim(),
          country: form.country.trim(),
          timezone: form.timezone,
          address: buildAddress(),
          buIds: form.buIds,
          calendarId: form.calendarId || null,
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok) {
        const msg = json?.error === 'validation_error'
          ? `Validazione fallita: ${(json.issues || []).map(i => i.message).join(' · ') || 'campi non validi'}`
          : (json?.error || `HTTP ${res.status}`);
        setServerError(msg);
        return;
      }
      const created = json?.data;
      if (created) {
        addSite(created);
        pushToast({ title: `${created.code} · ${created.name}`, desc: 'Sito salvato in DB. Audit log registrato.', tone: 'ok' });
      }
      onClose();
    } catch (err) {
      setServerError(String(err?.message || err));
    } finally {
      setSaving(false);
    }
  }

  const timezones = ['Europe/Rome','Europe/Berlin','Europe/Madrid','Europe/Paris','Europe/London','UTC','America/New_York','Asia/Shanghai'];
  const countries = ['IT','DE','FR','ES','GB','US','CN','JP','PL','RO','TR'];

  return (
    <Modal open={open} onClose={onClose} title="Nuovo sito produttivo" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea sito'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Registra un nuovo plant produttivo. Sarà selezionabile in fase di intake progetto e linkato ai workflow di approvazione locali. Vedi <code>docs/data-model.md §1.3</code>.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} placeholder="CAM" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="es. Cameri (NO)"/>
            </div>
          </div>
          {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code: maiuscola iniziale + A-Z, 0-9, _, - (max 32)</div>}
          {codeValid && !codeUnique && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> Code già esistente</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Geografia & calendario</div>
          <div className="grid grid-3">
            <div className="field"><label>Paese (ISO 2)</label>
              <select value={form.country} onChange={e=>set('country', e.target.value)}>
                {countries.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
            <div className="field"><label>Fuso orario</label>
              <select value={form.timezone} onChange={e=>set('timezone', e.target.value)}>
                {timezones.map(t => <option key={t} value={t}>{t}</option>)}
              </select>
            </div>
            <div className="field"><label>Calendario lavorativo</label>
              <select value={form.calendarId} onChange={e=>set('calendarId', e.target.value)}>
                <option value="">— nessuno —</option>
                {(calendars||[]).map(c => <option key={c.id} value={c.id}>{c.name} · {c.country}</option>)}
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Indirizzo (opzionale)</div>
          <div className="grid grid-3">
            <div className="field" style={{gridColumn:'1 / -1'}}><label>Via e numero</label>
              <input value={form.addrLine1} onChange={e=>set('addrLine1', e.target.value)} placeholder="Via dell'Industria 14"/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Città</label>
              <input value={form.addrCity} onChange={e=>set('addrCity', e.target.value)} placeholder="Cameri"/>
            </div>
            <div className="field"><label>CAP</label>
              <input value={form.addrPostal} onChange={e=>set('addrPostal', e.target.value)} placeholder="28062"/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Business unit associate</div>
          <div style={{display:'flex', flexWrap:'wrap', gap:4}}>
            {form.buIds.map(id => {
              const b = (bus||[]).find(x => x.id === id);
              const label = b ? `${b.code} · ${b.name}` : id;
              return <Chip key={id}>{label} <span style={{cursor:'pointer', marginLeft:4}} onClick={()=>set('buIds', form.buIds.filter(x=>x!==id))}>×</span></Chip>;
            })}
            {(bus||[]).filter(b => !form.buIds.includes(b.id)).map(b => (
              <button key={b.id} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('buIds', [...form.buIds, b.id])}>+ {b.code}</button>
            ))}
          </div>
          {form.buIds.length === 0 && <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:4}}>Nessuna BU associata. Selezionabile dopo creazione.</div>}
        </div>

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/sites</code> persiste in DB con audit log automatico. Salvati: code, name, country, timezone, address (opzionale), buIds, calendarId, active. Il sito sarà disponibile come opzione nei wizard di creazione progetto.
        </div>

        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable per Site (PATCH /api/config/sites/[id]).
// Riusa useEditableEntity hook (forms.jsx). Mappa form→API in buildBody (address composta da line1/city/postal).
function SiteDetailModal({ sel, onClose, bus, calendars }) {
  const { addSite, pushToast, user, seed } = useStore();
  const personas = (seed && seed.PERSONAS) || [];
  const timezones = ['Europe/Rome','Europe/Berlin','Europe/Madrid','Europe/Paris','Europe/London','UTC','America/New_York','Asia/Shanghai'];
  const countries = ['IT','DE','FR','ES','GB','US','CN','JP','PL','RO','TR'];

  const initial = React.useMemo(() => {
    if (!sel) return null;
    const country2 = (sel.country || 'IT').toString().slice(0, 2).toUpperCase();
    return {
      code: sel.code || '',
      name: sel.name || '',
      country: country2,
      timezone: sel.timezone || 'Europe/Rome',
      addrLine1: sel.address?.line1 || '',
      addrLine2: sel.address?.line2 || '',
      addrCity: sel.address?.city || '',
      addrProvince: sel.address?.province || '',
      addrPostal: sel.address?.postal_code || '',
      buIds: Array.isArray(sel.buIds) ? [...sel.buIds] : [],
      managerPersonaId: sel.managerPersonaId || '',
      calendarId: sel.calendarId || '',
      active: sel.active !== false,
    };
  }, [sel]);

  const buildBody = React.useCallback((f) => {
    const hasAddr = (f.addrLine1 || '').trim() || (f.addrCity || '').trim() || (f.addrPostal || '').trim();
    const address = hasAddr
      ? {
          line1: (f.addrLine1 || '').trim() || '—',
          ...((f.addrLine2 || '').trim() ? { line2: (f.addrLine2 || '').trim() } : {}),
          city: (f.addrCity || '').trim() || '—',
          ...((f.addrProvince || '').trim() ? { province: (f.addrProvince || '').trim() } : {}),
          postal_code: (f.addrPostal || '').trim() || '—',
          country: (f.country || 'IT').slice(0, 2).toUpperCase(),
        }
      : null;
    return {
      code: f.code,
      name: (f.name || '').trim(),
      country: (f.country || 'IT').slice(0, 2).toUpperCase(),
      timezone: f.timezone,
      address,
      buIds: f.buIds || [],
      managerPersonaId: f.managerPersonaId ? f.managerPersonaId : null,
      calendarId: f.calendarId ? f.calendarId : null,
      active: !!f.active,
    };
  }, []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => sel ? `/api/config/sites/${sel.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addSite(json.data);
      pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: `Sito aggiornato. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!sel) return <Modal open={false} onClose={onClose} title="" />;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_-]{1,31}$/.test(form.code);
  const valid = (form.name || '').trim() && codeValid;

  return (
    <Modal open={!!sel} onClose={onClose} title={`${sel.code} · ${sel.name}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code</label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
              {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code: maiuscola iniziale + A-Z, 0-9, _, - (max 32)</div>}
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome</label>
              <input value={form.name} onChange={e=>set('name', e.target.value)}/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Geografia & calendario</div>
          <div className="grid grid-3">
            <div className="field"><label>Paese (ISO 2)</label>
              <select value={form.country} onChange={e=>set('country', e.target.value)}>
                {countries.map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
            <div className="field"><label>Fuso orario</label>
              <select value={form.timezone} onChange={e=>set('timezone', e.target.value)}>
                {timezones.map(t => <option key={t} value={t}>{t}</option>)}
              </select>
            </div>
            <div className="field"><label>Calendario lavorativo</label>
              <select value={form.calendarId} onChange={e=>set('calendarId', e.target.value)}>
                <option value="">— nessuno —</option>
                {(calendars||[]).map(c => <option key={c.id} value={c.id}>{c.name} · {c.country}</option>)}
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Indirizzo (opzionale)</div>
          <div className="grid grid-3">
            <div className="field" style={{gridColumn:'1 / -1'}}><label>Via e numero</label>
              <input value={form.addrLine1 || ''} onChange={e=>set('addrLine1', e.target.value)} placeholder="Via dell'Industria 14"/>
            </div>
            <div className="field" style={{gridColumn:'1 / -1'}}><label>Riga 2 (opzionale)</label>
              <input value={form.addrLine2 || ''} onChange={e=>set('addrLine2', e.target.value)} placeholder="presso, scala, ecc."/>
            </div>
            <div className="field"><label>Città</label>
              <input value={form.addrCity || ''} onChange={e=>set('addrCity', e.target.value)}/>
            </div>
            <div className="field"><label>Provincia</label>
              <input value={form.addrProvince || ''} onChange={e=>set('addrProvince', e.target.value)} placeholder="es. NO"/>
            </div>
            <div className="field"><label>CAP</label>
              <input value={form.addrPostal || ''} onChange={e=>set('addrPostal', e.target.value)}/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Manager (persona)</div>
          <PersonaAutocomplete
            value={form.managerPersonaId}
            onChange={(id) => set('managerPersonaId', id || '')}
            placeholder="Cerca manager…"
            personasFallback={personas}
          />
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Business unit associate ({(form.buIds||[]).length})</div>
          <div style={{display:'flex', flexWrap:'wrap', gap:4}}>
            {(form.buIds||[]).map(id => {
              const b = (bus||[]).find(x => x.id === id);
              const label = b ? `${b.code} · ${b.name}` : id;
              return <Chip key={id}>{label} <span style={{cursor:'pointer', marginLeft:4}} onClick={()=>set('buIds', (form.buIds||[]).filter(x=>x!==id))}>×</span></Chip>;
            })}
            {(bus||[]).filter(b => !(form.buIds||[]).includes(b.id)).map(b => (
              <button key={b.id} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('buIds', [...(form.buIds||[]), b.id])}>+ {b.code}</button>
            ))}
          </div>
          {(form.buIds||[]).length === 0 && <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:4}}>Nessuna BU associata.</div>}
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Sito attivo
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/sites/{sel.id}</code> persisterà la modifica con audit log automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

// -------------- SLA & ESCALATION --------------
function CustSLA() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.15 batch: merge extras + seed con dedup per id (extras vince).
  const slas = React.useMemo(() => {
    const seedList = seedCustom.SLA_POLICIES || [];
    const extList = extras?.slasExt || [];
    const seenIds = new Set();
    const out = [];
    for (const s of [...extList, ...seedList]) {
      if (!s?.id || seenIds.has(s.id)) continue;
      seenIds.add(s.id);
      out.push(s);
    }
    return out;
  }, [seedCustom.SLA_POLICIES, extras?.slasExt]);
  const escalations = React.useMemo(() => {
    const seedList = seedCustom.ESCALATION_RULES || [];
    const extList = extras?.escalationsExt || [];
    const seenIds = new Set();
    const out = [];
    for (const e of [...extList, ...seedList]) {
      if (!e?.id || seenIds.has(e.id)) continue;
      seenIds.add(e.id);
      out.push(e);
    }
    return out;
  }, [seedCustom.ESCALATION_RULES, extras?.escalationsExt]);
  const calendars = React.useMemo(() => {
    const seedList = seedCustom.CALENDARS || [];
    const extList = extras?.calendarsExt || [];
    const seenIds = new Set();
    const out = [];
    for (const c of [...extList, ...seedList]) {
      if (!c?.id || seenIds.has(c.id)) continue;
      seenIds.add(c.id);
      out.push(c);
    }
    return out;
  }, [seedCustom.CALENDARS, extras?.calendarsExt]);
  const [tab, setTab] = React.useState('policies');
  const [sel, setSel] = React.useState(null);
  const [showSim, setShowSim] = React.useState(false);
  const [showNew, setShowNew] = React.useState(false);

  return (
    <>
      <div className="row" style={{ gap: 2, marginBottom: 12, background: 'var(--bg-2)', padding: 2, borderRadius: 6, alignItems:'center' }}>
        <button className={`btn sm ${tab==='policies'?'primary':'ghost'}`} onClick={()=>setTab('policies')}>Policy SLA ({slas.length})</button>
        <button className={`btn sm ${tab==='escalations'?'primary':'ghost'}`} onClick={()=>setTab('escalations')}>Escalation rules ({escalations.length})</button>
        <span className="spacer"/>
        <Btn variant="ghost" size="sm" onClick={()=>setShowSim(true)}><Icon name="eye" size={11}/> Simulatore</Btn>
        <ConfigWriteBtn onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuova {tab==='policies'?'policy':'escalation'}</ConfigWriteBtn>
      </div>
      <SlaSimulatorModal open={showSim} onClose={()=>setShowSim(false)} slas={slas} escalations={escalations}/>
      <NewSlaModal open={showNew && tab==='policies'} onClose={()=>setShowNew(false)}/>
      <NewEscalationModal open={showNew && tab==='escalations'} onClose={()=>setShowNew(false)}/>

      {tab === 'policies' ? (
        <>
          <table className="tbl dense">
            <thead><tr>
              <th style={{width:120}}>Code</th><th>Nome</th>
              <th style={{width:130}}>Entità / Step</th>
              <th style={{width:80,textAlign:'right'}}>Target</th>
              <th style={{width:130}}>Warning → Breach</th>
              <th>On breach</th>
              <th style={{width:110,textAlign:'center'}}>Calendario</th>
              <th style={{width:90,textAlign:'center'}}>Pause</th>
              <th style={{width:70,textAlign:'center'}}>Stato</th>
            </tr></thead>
            <tbody>
              {slas.map(s => (
                <tr key={s.id} className="clickable" onClick={()=>setSel({type:'sla', data:s})}>
                  <td className="mono" style={{fontSize:10.5}}>{s.code}</td>
                  <td style={{fontWeight:500}}>{s.name}</td>
                  <td style={{fontSize:11}}>
                    <Chip>{s.entityType}</Chip>
                    {s.stepType && s.stepType !== '*' ? <span style={{marginLeft:4, color:'var(--text-3)', fontFamily:'var(--font-mono)'}}>/{s.stepType}</span> : null}
                  </td>
                  <td className="num mono">{s.targetDays}g</td>
                  <td style={{fontSize:11}} className="mono">{s.warningAtDays}g → {s.breachAtDays}g</td>
                  <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(s.onBreach||[]).map(a => <Chip key={a} kind="warn">{a}</Chip>)}</div></td>
                  <td style={{textAlign:'center', fontSize:11}}>
                    <Chip>{s.calendar || '—'}</Chip>
                    {s.businessDays ? <span style={{marginLeft:4, fontSize:10, color:'var(--text-3)'}}>lav.</span> : <span style={{marginLeft:4, fontSize:10, color:'var(--text-3)'}}>24/7</span>}
                  </td>
                  <td style={{textAlign:'center'}}>{(s.pauseConditions||[]).length>0 ? <Chip kind="info">{(s.pauseConditions||[]).length}</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
                  <td style={{textAlign:'center'}}><Chip kind={s.active?'ok':''} dot>{s.active?'on':'off'}</Chip></td>
                </tr>
              ))}
            </tbody>
          </table>
          <div style={{fontSize:11, color:'var(--text-3)', marginTop:10, lineHeight:1.5}}>
            Gli SLA sono consumati dal <strong>Workflow engine</strong> per calcolo scadenze per step. Le azioni <code>onBreach</code> sono valutate dalle <em>Escalation rules</em>.
          </div>
        </>
      ) : (
        <>
          <table className="tbl dense">
            <thead><tr>
              <th style={{width:130}}>Code</th>
              <th style={{width:110}}>Trigger</th>
              <th style={{width:110}}>Entità</th>
              <th>Condizione</th>
              <th style={{width:60,textAlign:'center'}}>Livello</th>
              <th style={{width:80,textAlign:'right'}}>Wait (h)</th>
              <th>Notifica a</th>
              <th style={{width:130}}>Escala a</th>
              <th style={{width:110}}>Template</th>
              <th style={{width:60,textAlign:'center'}}>Stato</th>
            </tr></thead>
            <tbody>
              {escalations.map(e => (
                <tr key={e.id} className="clickable" onClick={()=>setSel({type:'esc', data:e})}>
                  <td className="mono" style={{fontSize:10.5}}>{e.code}</td>
                  <td><Chip kind={e.trigger==='sla.breach'?'err':e.trigger==='sla.warn'?'warn':'info'}>{e.trigger}</Chip></td>
                  <td><Chip>{e.entityType}</Chip></td>
                  <td style={{fontSize:11, color:'var(--text-2)', fontFamily:'var(--font-mono)'}}>{e.condition ? `${e.condition.field} ${e.condition.op} ${e.condition.value}` : 'always'}</td>
                  <td className="num mono" style={{textAlign:'center'}}>L{e.level}</td>
                  <td className="num mono">{e.waitHours}</td>
                  <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(e.notifyRoles||[]).map(r => <Chip key={r}>{r}</Chip>)}</div></td>
                  <td>{e.escalateTo ? <Chip kind="err">{e.escalateTo}</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
                  <td style={{fontSize:11, fontFamily:'var(--font-mono)', color:'var(--text-2)'}}>{e.templateId}</td>
                  <td style={{textAlign:'center'}}><Chip kind={e.active?'ok':''} dot>{e.active?'on':'off'}</Chip></td>
                </tr>
              ))}
            </tbody>
          </table>
          <div style={{fontSize:11, color:'var(--text-3)', marginTop:10, lineHeight:1.5}}>
            Le <strong>escalation rules</strong> determinano, al superamento del target SLA, chi viene notificato e a quale ruolo viene riassegnato lo step. Il livello L2/L3 si attiva dopo <code>waitHours</code> dal L1.
          </div>
        </>
      )}

      <SlaDetailModal slaSel={sel?.type === 'sla' ? sel.data : null} onClose={()=>setSel(null)} calendars={calendars}/>
      <EscalationDetailModal escSel={sel?.type === 'esc' ? sel.data : null} onClose={()=>setSel(null)}/>
    </>
  );
}

// FASE 3a.15 batch: detail modal editable SLA Policy (PATCH /api/config/sla-policies/[id]).
// Helper UX: onBreach + pauseConditions sono array stringhe; gestiti come csv editabile.
function SlaDetailModal({ slaSel, onClose, calendars }) {
  const { addSla, pushToast, user } = useStore();
  const serverSla = useFetchedDetail(slaSel, (s) => `/api/config/sla-policies/${s.id}`);
  const baseSla = serverSla || slaSel;

  const initial = React.useMemo(() => {
    if (!baseSla) return null;
    return {
      code: baseSla.code || '',
      name: baseSla.name || '',
      entityType: baseSla.entityType || 'rda',
      stepType: baseSla.stepType || '*',
      targetDays: typeof baseSla.targetDays === 'number' ? baseSla.targetDays : 0,
      warningAtDays: typeof baseSla.warningAtDays === 'number' ? baseSla.warningAtDays : 0,
      breachAtDays: typeof baseSla.breachAtDays === 'number' ? baseSla.breachAtDays : 0,
      onBreach: Array.isArray(baseSla.onBreach) ? [...baseSla.onBreach] : [],
      businessDays: baseSla.businessDays !== false,
      calendarId: baseSla.calendarId || baseSla.calendar || '',
      pauseConditions: Array.isArray(baseSla.pauseConditions) ? [...baseSla.pauseConditions] : [],
      active: baseSla.active !== false,
    };
  }, [baseSla]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    name: (f.name || '').trim(),
    entityType: f.entityType,
    stepType: f.stepType,
    targetDays: Number(f.targetDays),
    warningAtDays: Number(f.warningAtDays),
    breachAtDays: Number(f.breachAtDays),
    onBreach: f.onBreach || [],
    businessDays: !!f.businessDays,
    calendarId: f.calendarId ? f.calendarId : null,
    pauseConditions: f.pauseConditions || [],
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => slaSel ? `/api/config/sla-policies/${slaSel.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addSla(json.data);
      pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: `SLA aggiornato. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!slaSel) return <Modal open={false} onClose={onClose} title=""/>;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_-]{1,63}$/.test(form.code);
  const nameValid = (form.name || '').trim().length > 0;
  const daysValid = Number.isFinite(Number(form.targetDays)) && Number.isFinite(Number(form.warningAtDays)) && Number.isFinite(Number(form.breachAtDays));
  const orderValid = Number(form.warningAtDays) <= Number(form.targetDays) && Number(form.breachAtDays) >= Number(form.targetDays);
  const valid = codeValid && nameValid && daysValid && orderValid;

  const ENTITY_TYPES = ['rda','oda','progetto','vendor','contratto','sal','documento'];
  const ON_BREACH_OPTIONS = ['notify','escalate','reassign','block','autoApprove'];
  const PAUSE_OPTIONS = ['waitingDocs','waitingVendor','waitingApproval','onHold'];

  const toggleInArray = (key, value) => {
    const cur = form[key] || [];
    set(key, cur.includes(value) ? cur.filter(x => x !== value) : [...cur, value]);
  };

  return (
    <Modal open={!!slaSel} onClose={onClose} title={`SLA · ${slaSel.code} · ${slaSel.name}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code</label>
              <input value={form.code || ''} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
              {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code: maiuscola iniziale + A-Z, 0-9, _, -</div>}
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome</label>
              <input value={form.name || ''} onChange={e=>set('name', e.target.value)}/>
              {!nameValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Ambito</div>
          <div className="grid grid-3">
            <div className="field"><label>Entity type</label>
              <select value={form.entityType || 'rda'} onChange={e=>set('entityType', e.target.value)}>
                {ENTITY_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
              </select>
            </div>
            <div className="field"><label>Step type</label>
              <input value={form.stepType || ''} onChange={e=>set('stepType', e.target.value)} placeholder="* o nome step" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Calendario</label>
              <select value={form.calendarId || ''} onChange={e=>set('calendarId', e.target.value)}>
                <option value="">— nessuno —</option>
                {(calendars || []).map(c => <option key={c.id} value={c.id}>{c.name} · {c.country}</option>)}
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Soglie temporali (giorni)</div>
          <div className="grid grid-3">
            <div className="field"><label>Warning</label>
              <input type="number" min={0} max={365} value={form.warningAtDays} onChange={e=>set('warningAtDays', Number(e.target.value))}/>
            </div>
            <div className="field"><label>Target</label>
              <input type="number" min={0} max={365} value={form.targetDays} onChange={e=>set('targetDays', Number(e.target.value))}/>
            </div>
            <div className="field"><label>Breach</label>
              <input type="number" min={0} max={365} value={form.breachAtDays} onChange={e=>set('breachAtDays', Number(e.target.value))}/>
            </div>
          </div>
          {!orderValid && (
            <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}>
              Vincolo: warningAtDays ≤ targetDays ≤ breachAtDays
            </div>
          )}
          <label className="row" style={{gap:6, fontSize:11.5, marginTop:8}}>
            <input type="checkbox" checked={!!form.businessDays} onChange={e=>set('businessDays', e.target.checked)}/>
            Conta solo giorni lavorativi (calendario business)
          </label>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Azioni on breach</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:6, border:'1px solid var(--line)', borderRadius:4}}>
            {ON_BREACH_OPTIONS.map(opt => {
              const on = (form.onBreach || []).includes(opt);
              return (
                <button key={opt} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={()=>toggleInArray('onBreach', opt)} style={{fontSize:10}}>
                  {on ? '✓ ' : '+ '}{opt}
                </button>
              );
            })}
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Condizioni di pausa</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:6, border:'1px solid var(--line)', borderRadius:4, minHeight:30}}>
            {PAUSE_OPTIONS.map(opt => {
              const on = (form.pauseConditions || []).includes(opt);
              return (
                <button key={opt} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={()=>toggleInArray('pauseConditions', opt)} style={{fontSize:10}}>
                  {on ? '✓ ' : '+ '}{opt}
                </button>
              );
            })}
            {(form.pauseConditions || []).length === 0 && <span style={{color:'var(--text-3)', fontSize:10.5, alignSelf:'center'}}>Nessuna — SLA sempre attivo</span>}
          </div>
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Policy attiva
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/sla-policies/{slaSel.id}</code> persisterà la modifica con audit log automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Escalation Rule (PATCH /api/config/escalation-rules/[id]).
function EscalationDetailModal({ escSel, onClose }) {
  const { addEscalation, pushToast, user } = useStore();
  const serverEsc = useFetchedDetail(escSel, (s) => `/api/config/escalation-rules/${s.id}`);
  const baseEsc = serverEsc || escSel;

  // Helper: condition.field/op/value <-> string DSL "field op value"
  const conditionToString = (c) => (c && typeof c === 'object' && c.field && c.op !== undefined)
    ? `${c.field} ${c.op} ${c.value === null || c.value === undefined ? '' : c.value}`.trim()
    : '';
  const parseConditionString = (s) => {
    const t = (s || '').trim();
    if (!t) return null;
    const parts = t.split(/\s+/);
    if (parts.length < 2) return null;
    const [field, op, ...rest] = parts;
    const valueStr = rest.join(' ');
    let value = valueStr;
    if (valueStr === '' ) value = null;
    else if (!Number.isNaN(Number(valueStr))) value = Number(valueStr);
    else if (valueStr === 'true') value = true;
    else if (valueStr === 'false') value = false;
    return { field, op, value };
  };

  const initial = React.useMemo(() => {
    if (!baseEsc) return null;
    return {
      code: baseEsc.code || '',
      trigger: baseEsc.trigger || 'sla.warn',
      entityType: baseEsc.entityType || 'rda',
      level: typeof baseEsc.level === 'number' ? baseEsc.level : 1,
      waitHours: typeof baseEsc.waitHours === 'number' ? baseEsc.waitHours : 0,
      notifyRoles: Array.isArray(baseEsc.notifyRoles) ? [...baseEsc.notifyRoles] : [],
      escalateTo: baseEsc.escalateTo || '',
      templateId: baseEsc.templateId || '',
      conditionStr: conditionToString(baseEsc.condition),
      active: baseEsc.active !== false,
    };
  }, [baseEsc]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    trigger: f.trigger,
    entityType: f.entityType,
    condition: parseConditionString(f.conditionStr),
    level: Number(f.level),
    waitHours: Number(f.waitHours),
    notifyRoles: f.notifyRoles || [],
    escalateTo: (f.escalateTo || '').trim() ? (f.escalateTo || '').trim() : null,
    templateId: (f.templateId || '').trim() ? (f.templateId || '').trim() : null,
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => escSel ? `/api/config/escalation-rules/${escSel.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addEscalation(json.data);
      pushToast({ title: `Escalation ${json.data.code}`, desc: `Aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!escSel) return <Modal open={false} onClose={onClose} title=""/>;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_-]{1,63}$/.test(form.code);
  const valid = codeValid;

  const TRIGGERS = ['sla.warn','sla.breach','event.received','manual'];
  const ENTITY_TYPES = ['rda','oda','progetto','vendor','contratto','documento','sal'];
  const ROLE_OPTIONS = ['PM_CAPEX','BUYER','PROCUREMENT_MGR','CONTROLLER','CFO','CEO','QA_MGR','LEGAL','LEGAL_HEAD','BOARD','owner'];

  const toggleRole = (role) => {
    const cur = form.notifyRoles || [];
    set('notifyRoles', cur.includes(role) ? cur.filter(x => x !== role) : [...cur, role]);
  };

  return (
    <Modal open={!!escSel} onClose={onClose} title={`Escalation · ${escSel.code}`} size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div className="grid grid-2">
          <div className="field"><label>Code</label>
            <input value={form.code || ''} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
            {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code non valido</div>}
          </div>
          <div className="field"><label>Trigger</label>
            <select value={form.trigger || 'sla.warn'} onChange={e=>set('trigger', e.target.value)}>
              {TRIGGERS.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
          </div>
          <div className="field"><label>Entity type</label>
            <select value={form.entityType || 'rda'} onChange={e=>set('entityType', e.target.value)}>
              {ENTITY_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
          </div>
          <div className="field"><label>Livello</label>
            <input type="number" min={1} max={10} value={form.level} onChange={e=>set('level', Number(e.target.value))}/>
          </div>
          <div className="field"><label>Wait hours prima di escalation</label>
            <input type="number" min={0} max={720} value={form.waitHours} onChange={e=>set('waitHours', Number(e.target.value))}/>
          </div>
          <div className="field"><label>Template notifica (opzionale)</label>
            <input value={form.templateId || ''} onChange={e=>set('templateId', e.target.value)} placeholder="ID template"/>
          </div>
        </div>

        <div className="field"><label>Condizione (opzionale)</label>
          <input value={form.conditionStr || ''} onChange={e=>set('conditionStr', e.target.value)} placeholder="es. rda.amount gt 50000" style={{fontFamily:'var(--font-mono)', fontSize:11.5}}/>
          <div style={{fontSize:10, color:'var(--text-3)', marginTop:2}}>
            Sintassi <code>field op value</code>. Lascia vuoto per "always".
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Ruoli da notificare</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:6, border:'1px solid var(--line)', borderRadius:4}}>
            {ROLE_OPTIONS.map(role => {
              const on = (form.notifyRoles || []).includes(role);
              return (
                <button key={role} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={()=>toggleRole(role)} style={{fontSize:10}}>
                  {on ? '✓ ' : '+ '}{role}
                </button>
              );
            })}
          </div>
        </div>

        <div className="field"><label>Ruolo target di escalation (opzionale)</label>
          <input value={form.escalateTo || ''} onChange={e=>set('escalateTo', e.target.value)} placeholder="es. CFO, LEGAL_HEAD, BOARD"/>
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Regola attiva
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/escalation-rules/{escSel.id}</code> persisterà con audit automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

// -------------- SLA · Modali (simulatore / nuova policy / nuova escalation) --------------

// Calcolo SLA client-side coerente con docs/sla-escalation.md
function computeSlaMock({ policy, startedAt, pausedDays = 0, amount = 0, now }) {
  if (!policy) return null;
  const msDay = 24*3600*1000;
  let elapsed = Math.max(0, (now - new Date(startedAt).getTime()) / msDay);
  // approssimazione giorni lavorativi: fattore 5/7 se businessDays
  if (policy.businessDays) elapsed = elapsed * 5/7;
  elapsed = Math.max(0, elapsed - pausedDays);
  elapsed = Math.round(elapsed * 10) / 10;

  let status = 'ok';
  if (elapsed >= policy.breachAtDays) status = 'breach';
  else if (elapsed >= policy.warningAtDays) status = 'warn';

  const daysLeft   = Math.max(0, policy.targetDays - elapsed);
  const daysOver   = Math.max(0, elapsed - policy.breachAtDays);
  const pctElapsed = Math.min(150, (elapsed / policy.targetDays) * 100);
  return { elapsed, status, daysLeft, daysOver, pctElapsed };
}

function SlaSimulatorModal({ open, onClose, slas, escalations }) {
  const [policyId, setPolicyId] = React.useState(slas[0]?.id || '');
  const today = new Date().toISOString().slice(0,10);
  const defaultStart = (() => { const d = new Date(); d.setDate(d.getDate()-3); return d.toISOString().slice(0,10); })();
  const [startedAt, setStartedAt] = React.useState(defaultStart);
  const [simDate, setSimDate] = React.useState(today);
  const [paused, setPaused] = React.useState(0);
  const [amount, setAmount] = React.useState(75000);

  const policy = slas.find(s => s.id === policyId) || slas[0];
  const result = policy ? computeSlaMock({
    policy,
    startedAt,
    pausedDays: Number(paused) || 0,
    amount: Number(amount) || 0,
    now: new Date(simDate).getTime(),
  }) : null;

  // Escalation che scatterebbero
  const firedEscalations = React.useMemo(() => {
    if (!policy || !result || result.status === 'ok') return [];
    const trigger = result.status === 'warn' ? 'sla.warn' : 'sla.breach';
    return escalations.filter(e => {
      if (!e.active) return false;
      if (e.trigger !== trigger) return false;
      if (e.entityType !== policy.entityType) return false;
      if (!e.condition) return true;
      const { field, op, value } = e.condition;
      let cmp;
      if (field === 'rda.amount') cmp = Number(amount) || 0;
      else if (field === 'step.type') cmp = policy.stepType;
      else return true;
      if (op === 'gt')  return cmp >  value;
      if (op === 'gte') return cmp >= value;
      if (op === 'lt')  return cmp <  value;
      if (op === 'lte') return cmp <= value;
      if (op === 'eq')  return cmp === value;
      return true;
    });
  }, [policy, result, escalations, amount]);

  const statusColor = { ok: 'var(--ok)', warn: 'var(--warn)', breach: 'var(--err)' };

  return (
    <Modal open={open} onClose={onClose} title="Simulatore SLA" size="lg"
      footer={<><Btn variant="ghost" size="sm" onClick={onClose}>Chiudi</Btn><Btn variant="primary" size="sm" onClick={onClose}>Applica scenario</Btn></>}>
      <div className="col" style={{ gap: 14 }}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Simula il calcolo SLA e le escalation su uno scenario ipotetico. Scegli una policy, data di apertura step, data di valutazione, eventuali giorni di pausa e (per RdA) l'importo.
        </div>

        <div className="grid grid-3" style={{gap:10}}>
          <div className="field"><label>Policy</label>
            <select value={policyId} onChange={e=>setPolicyId(e.target.value)}>
              {slas.map(s => <option key={s.id} value={s.id}>{s.code} — {s.name}</option>)}
            </select>
          </div>
          <div className="field"><label>Step aperto il</label>
            <input type="date" value={startedAt} onChange={e=>setStartedAt(e.target.value)}/>
          </div>
          <div className="field"><label>Data valutazione</label>
            <input type="date" value={simDate} onChange={e=>setSimDate(e.target.value)}/>
          </div>
          <div className="field"><label>Giorni di pausa accumulati</label>
            <input type="number" min="0" step="0.5" value={paused} onChange={e=>setPaused(e.target.value)}/>
          </div>
          <div className="field"><label>Importo RdA (€)</label>
            <input type="number" min="0" step="1000" value={amount} onChange={e=>setAmount(e.target.value)}/>
          </div>
          <div className="field"><label>Entità</label>
            <input disabled value={policy ? `${policy.entityType}${policy.stepType && policy.stepType !== '*' ? ' · '+policy.stepType : ''}` : ''}/>
          </div>
        </div>

        {policy && result && (
          <div style={{padding:14, background:'var(--bg-2)', borderRadius:8, border:'1px solid var(--line)'}}>
            <div className="row" style={{gap:14, alignItems:'center', marginBottom:10}}>
              <div>
                <div className="eyebrow">Stato SLA</div>
                <div style={{fontSize:20, fontWeight:600, color: statusColor[result.status], marginTop:2}}>
                  {result.status === 'ok' ? 'OK' : result.status === 'warn' ? 'WARNING' : 'BREACH'}
                </div>
              </div>
              <div style={{borderLeft:'1px solid var(--line)', paddingLeft:14}}>
                <div className="eyebrow">Consumato</div>
                <div style={{fontSize:18, fontWeight:500, marginTop:2}}>{result.elapsed.toFixed(1)}g <span style={{fontSize:11, color:'var(--text-3)'}}>/ {policy.targetDays}g target</span></div>
              </div>
              <div style={{borderLeft:'1px solid var(--line)', paddingLeft:14}}>
                <div className="eyebrow">{result.status === 'breach' ? 'Sforamento' : 'Residuo'}</div>
                <div style={{fontSize:18, fontWeight:500, marginTop:2, color: result.status==='breach'?'var(--err)':'inherit'}}>
                  {result.status === 'breach' ? '+' + result.daysOver.toFixed(1) + 'g' : result.daysLeft.toFixed(1) + 'g'}
                </div>
              </div>
              <span className="spacer"/>
              <div>
                <div className="eyebrow">Calendario</div>
                <Chip>{policy.calendar}</Chip>
                <span style={{marginLeft:4, fontSize:10.5, color:'var(--text-3)'}}>{policy.businessDays?'giorni lav.':'24/7'}</span>
              </div>
            </div>

            {/* progress bar */}
            <div style={{position:'relative', height:10, background:'var(--bg-3)', borderRadius:5, overflow:'visible', marginBottom:4}}>
              <div style={{position:'absolute', left:0, top:0, bottom:0, width: Math.min(100, result.pctElapsed) + '%',
                background: statusColor[result.status], borderRadius:5, transition:'width 0.2s'}}/>
              {/* marker warning */}
              <div title="Warning" style={{position:'absolute', top:-3, bottom:-3, left: (policy.warningAtDays/policy.targetDays*100) + '%',
                width:2, background:'var(--warn)'}}/>
              {/* marker breach */}
              <div title="Breach" style={{position:'absolute', top:-3, bottom:-3, left: (policy.breachAtDays/policy.targetDays*100) + '%',
                width:2, background:'var(--err)'}}/>
            </div>
            <div className="row" style={{fontSize:10, color:'var(--text-3)', gap:0, marginTop:6}}>
              <span>0g</span><span className="spacer"/>
              <span style={{color:'var(--warn)'}}>warn · {policy.warningAtDays}g</span>
              <span style={{marginLeft:12, color:'var(--err)'}}>breach · {policy.breachAtDays}g</span>
            </div>
          </div>
        )}

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Escalation che scatterebbero ({firedEscalations.length})</div>
          {firedEscalations.length === 0 ? (
            <div style={{padding:14, background:'var(--bg-2)', borderRadius:6, fontSize:11.5, color:'var(--text-3)'}}>
              {result && result.status === 'ok'
                ? 'Nessuna escalation: SLA ancora in stato OK.'
                : 'Nessuna regola di escalation matcha questo scenario.'}
            </div>
          ) : (
            <table className="tbl dense">
              <thead><tr><th style={{width:140}}>Code</th><th style={{width:100}}>Livello</th><th>Notifica a</th><th style={{width:130}}>Escala a</th><th style={{width:110}}>Template</th></tr></thead>
              <tbody>{firedEscalations.map(e => (
                <tr key={e.id}>
                  <td className="mono" style={{fontSize:10.5}}>{e.code}</td>
                  <td><Chip kind={e.level===1?'warn':'err'}>L{e.level} · +{e.waitHours}h</Chip></td>
                  <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(e.notifyRoles||[]).map(r => <Chip key={r}>{r}</Chip>)}</div></td>
                  <td>{e.escalateTo ? <Chip kind="err">{e.escalateTo}</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
                  <td className="mono" style={{fontSize:11, color:'var(--text-2)'}}>{e.templateId}</td>
                </tr>
              ))}</tbody>
            </table>
          )}
        </div>

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> Simulatore client-side: usa approssimazione 5/7 per i giorni lavorativi. In produzione il motore SLA (<code>service:sla-engine</code>) usa il calendario reale con festività da <code>config.calendars</code>.
        </div>
      </div>
    </Modal>
  );
}

function NewSlaModal({ open, onClose }) {
  const { extras, seedCustom, pushToast, user } = useStore();
  const initial = () => ({
    code: '', name: '', entityType: 'rda', stepType: '*',
    targetDays: 5, warningAtDays: 4, breachAtDays: 5,
    businessDays: true, calendar: 'CAL_IT',
    onBreach: ['escalate','notify_owner'],
    pauseConditions: [],
    active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));
  const valid = form.code.trim() && form.name.trim() && form.targetDays > 0
    && form.warningAtDays > 0 && form.warningAtDays <= form.targetDays
    && form.breachAtDays >= form.warningAtDays;

  // Calendari disponibili (extras + seed dedupati)
  const allCalendars = React.useMemo(() => {
    const seedList = (seedCustom && seedCustom.CALENDARS) || [];
    const extList = extras?.calendarsExt || [];
    const seenIds = new Set();
    const out = [];
    for (const c of [...extList, ...seedList]) {
      if (!c?.id || seenIds.has(c.id)) continue;
      seenIds.add(c.id); out.push(c);
    }
    return out;
  }, [seedCustom, extras?.calendarsExt]);

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/sla-policies', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          code: form.code, name: form.name.trim(),
          entityType: form.entityType, stepType: form.stepType || '*',
          targetDays: form.targetDays, warningAtDays: form.warningAtDays, breachAtDays: form.breachAtDays,
          onBreach: form.onBreach, businessDays: !!form.businessDays,
          calendarId: form.calendar || null,
          pauseConditions: form.pauseConditions, active: !!form.active,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) { pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: 'SLA salvato in DB. Audit registrato.', tone: 'ok' }); }
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuova policy SLA" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="ghost" size="sm" disabled={!valid || saving}><Icon name="eye" size={11}/> Valida e simula</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea policy'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Le policy SLA sono consumate dal motore workflow per calcolare lo stato di ogni step. Vedi <code>docs/sla-escalation.md</code> per l'algoritmo completo.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-2">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase())} placeholder="SLA_RDA_MARKETING" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="SLA RdA marketing"/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Ambito di applicazione</div>
          <div className="grid grid-3">
            <div className="field"><label>Entità</label>
              <select value={form.entityType} onChange={e=>set('entityType', e.target.value)}>
                <option value="rda">rda</option>
                <option value="progetto">progetto</option>
                <option value="vendor">vendor</option>
                <option value="contratto">contratto</option>
                <option value="sal">sal</option>
                <option value="documento">documento</option>
              </select>
            </div>
            <div className="field"><label>Step type</label>
              <input value={form.stepType} onChange={e=>set('stepType', e.target.value)} placeholder="* o codice step"/>
            </div>
            <div className="field"><label>Stato</label>
              <select value={form.active ? 'on' : 'off'} onChange={e=>set('active', e.target.value==='on')}>
                <option value="on">Attiva</option>
                <option value="off">Disattiva</option>
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Soglie (in giorni)</div>
          <div className="grid grid-3">
            <div className="field"><label>Target</label>
              <input type="number" min="1" value={form.targetDays} onChange={e=>set('targetDays', Number(e.target.value))}/>
            </div>
            <div className="field"><label>Warning at</label>
              <input type="number" min="1" value={form.warningAtDays} onChange={e=>set('warningAtDays', Number(e.target.value))}/>
            </div>
            <div className="field"><label>Breach at</label>
              <input type="number" min="1" value={form.breachAtDays} onChange={e=>set('breachAtDays', Number(e.target.value))}/>
            </div>
          </div>
          {!valid && (
            <div style={{marginTop:6, fontSize:10.5, color:'var(--err)'}}>
              <Icon name="alert-triangle" size={10}/> Deve essere: warning ≤ target ≤ breach, code e nome obbligatori.
            </div>
          )}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Calendario</div>
          <div className="grid grid-2">
            <div className="field"><label>Calendario</label>
              <select value={form.calendar} onChange={e=>set('calendar', e.target.value)}>
                <option value="">— nessuno —</option>
                {allCalendars.map(c => <option key={c.id} value={c.id}>{c.id} · {c.name}</option>)}
              </select>
            </div>
            <div className="field"><label>Business days</label>
              <select value={form.businessDays?'true':'false'} onChange={e=>set('businessDays', e.target.value==='true')}>
                <option value="true">Sì (giorni lavorativi)</option>
                <option value="false">No (24/7)</option>
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Azioni on breach</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:8, border:'1px solid var(--line)', borderRadius:4, minHeight:36}}>
            {form.onBreach.map(a => (
              <Chip key={a} kind="warn">
                {a} <span style={{marginLeft:4, cursor:'pointer', color:'var(--text-3)'}} onClick={()=>set('onBreach', form.onBreach.filter(x=>x!==a))}>×</span>
              </Chip>
            ))}
            {['escalate','notify_owner','notify_manager','notify_cfo','remind'].filter(x => !form.onBreach.includes(x)).map(x => (
              <button key={x} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('onBreach', [...form.onBreach, x])}>+ {x}</button>
            ))}
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Condizioni di pausa</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:8, border:'1px solid var(--line)', borderRadius:4, minHeight:36}}>
            {form.pauseConditions.length === 0 && <span style={{color:'var(--text-3)', fontSize:11}}>Nessuna — SLA sempre attivo</span>}
            {form.pauseConditions.map(p => (
              <Chip key={p} kind="info">
                {p} <span style={{marginLeft:4, cursor:'pointer', color:'var(--text-3)'}} onClick={()=>set('pauseConditions', form.pauseConditions.filter(x=>x!==p))}>×</span>
              </Chip>
            ))}
            {['waiting_vendor_response','waiting_legal','waiting_customer','invoice_dispute'].filter(x => !form.pauseConditions.includes(x)).map(x => (
              <button key={x} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('pauseConditions', [...form.pauseConditions, x])}>+ {x}</button>
            ))}
          </div>
        </div>

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/sla-policies</code> persiste in DB con audit log automatico. Vedi <code>docs/sla-escalation.md §11</code>.
        </div>
        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

function NewEscalationModal({ open, onClose }) {
  const { pushToast, user } = useStore();
  const initial = () => ({
    code: '', trigger: 'sla.breach', entityType: 'rda',
    conditionField: '', conditionOp: 'gt', conditionValue: '',
    level: 1, waitHours: 0,
    notifyRoles: [], escalateTo: '',
    templateId: 'TPL_N_SLA_B',
    active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));
  // Note: trigger 'doc.expired' del modal NON è in enum BE (TRIGGERS = 'sla.warn'|'sla.breach'|'event.received'|'manual')
  // Lo mappiamo a 'event.received'.
  const codeValid = /^[A-Z][A-Z0-9_-]{1,63}$/.test(form.code);
  const valid = codeValid && (form.notifyRoles.length > 0 || form.escalateTo.trim());

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      let triggerCanon = form.trigger;
      if (triggerCanon === 'doc.expired') triggerCanon = 'event.received';
      const condition = (form.conditionField && form.conditionValue) ? {
        field: form.conditionField,
        op: form.conditionOp,
        value: Number.isFinite(Number(form.conditionValue)) ? Number(form.conditionValue) : form.conditionValue,
      } : null;

      const res = await fetch('/api/config/escalation-rules', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          code: form.code, trigger: triggerCanon, entityType: form.entityType,
          condition, level: form.level, waitHours: form.waitHours,
          notifyRoles: form.notifyRoles, escalateTo: form.escalateTo.trim() || null,
          templateId: form.templateId || null, active: !!form.active,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) pushToast({ title: `${json.data.code}`, desc: 'Regola salvata in DB. Audit registrato.', tone: 'ok' });
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuova regola di escalation" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea regola'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Le regole di escalation reagiscono a eventi SLA o di dominio (<code>sla.breach</code>, <code>sla.warn</code>, <code>doc.expired</code>). Possono notificare ruoli e/o riassegnare lo step.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-2">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase())} placeholder="ESC_RDA_BREACH_L3" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Template notifica</label>
              <select value={form.templateId} onChange={e=>set('templateId', e.target.value)}>
                <option value="TPL_N_SLA_W">TPL_N_SLA_W · SLA warn</option>
                <option value="TPL_N_SLA_B">TPL_N_SLA_B · SLA breach</option>
                <option value="TPL_N_EXP">TPL_N_EXP · Doc scadenza</option>
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Trigger</div>
          <div className="grid grid-2">
            <div className="field"><label>Evento</label>
              <select value={form.trigger} onChange={e=>set('trigger', e.target.value)}>
                <option value="sla.warn">sla.warn</option>
                <option value="sla.breach">sla.breach</option>
                <option value="doc.expired">doc.expired</option>
                <option value="manual">manual</option>
              </select>
            </div>
            <div className="field"><label>Entità</label>
              <select value={form.entityType} onChange={e=>set('entityType', e.target.value)}>
                <option value="rda">rda</option>
                <option value="progetto">progetto</option>
                <option value="vendor">vendor</option>
                <option value="contratto">contratto</option>
                <option value="documento">documento</option>
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Condizione (opzionale)</div>
          <div className="grid grid-3">
            <div className="field"><label>Field</label>
              <input value={form.conditionField} onChange={e=>set('conditionField', e.target.value)} placeholder="rda.amount" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Op</label>
              <select value={form.conditionOp} onChange={e=>set('conditionOp', e.target.value)}>
                <option value="eq">eq</option><option value="gt">gt</option><option value="gte">gte</option>
                <option value="lt">lt</option><option value="lte">lte</option>
              </select>
            </div>
            <div className="field"><label>Value</label>
              <input value={form.conditionValue} onChange={e=>set('conditionValue', e.target.value)} placeholder="50000"/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Livello & timing</div>
          <div className="grid grid-2">
            <div className="field"><label>Livello (catena)</label>
              <select value={form.level} onChange={e=>set('level', Number(e.target.value))}>
                <option value={1}>L1 · immediato</option>
                <option value={2}>L2 · secondario</option>
                <option value={3}>L3 · finale</option>
              </select>
            </div>
            <div className="field"><label>Wait hours prima di applicare</label>
              <input type="number" min="0" value={form.waitHours} onChange={e=>set('waitHours', Number(e.target.value))}/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Destinatari</div>
          <div className="field" style={{marginBottom:8}}><label>Ruoli da notificare</label>
            <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:8, border:'1px solid var(--line)', borderRadius:4, minHeight:36}}>
              {form.notifyRoles.length === 0 && <span style={{color:'var(--text-3)', fontSize:11}}>Nessun ruolo selezionato</span>}
              {form.notifyRoles.map(r => (
                <Chip key={r}>{r} <span style={{marginLeft:4, cursor:'pointer', color:'var(--text-3)'}} onClick={()=>set('notifyRoles', form.notifyRoles.filter(x=>x!==r))}>×</span></Chip>
              ))}
              {['PM_CAPEX','BUYER','PROCUREMENT_MGR','CONTROLLER','CFO','CEO','QA_MGR','LEGAL','LEGAL_HEAD','BOARD','owner'].filter(x => !form.notifyRoles.includes(x)).map(x => (
                <button key={x} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('notifyRoles', [...form.notifyRoles, x])}>+ {x}</button>
              ))}
            </div>
          </div>
          <div className="field"><label>Escala a (ruolo) — opzionale</label>
            <input value={form.escalateTo} onChange={e=>set('escalateTo', e.target.value)} placeholder="CFO, LEGAL_HEAD, BOARD..."/>
          </div>
        </div>

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/escalation-rules</code> persiste in DB con audit log. Trigger <code>doc.expired</code> mappato a <code>event.received</code>.
        </div>
        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// -------------- CALENDARS --------------
const DOW_LABELS = ['Lun','Mar','Mer','Gio','Ven','Sab','Dom'];
const DOW_TO_JS = [1,2,3,4,5,6,0]; // Lun=1...Dom=0

function weekendLabel(weekendDays = [6, 0]) {
  const map = { 0:'Dom', 1:'Lun', 2:'Mar', 3:'Mer', 4:'Gio', 5:'Ven', 6:'Sab' };
  return weekendDays.map(d => map[d]).join(', ') || '—';
}

function CustCalendars() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.5: merge extras.calendarsExt + seedCustom.CALENDARS con dedup per id.
  const cals = React.useMemo(() => {
    const seedList = seedCustom.CALENDARS || [];
    const extList = extras?.calendarsExt || [];
    const seenIds = new Set();
    const out = [];
    for (const c of [...extList, ...seedList]) {
      if (!c?.id || seenIds.has(c.id)) continue;
      seenIds.add(c.id);
      out.push(c);
    }
    return out;
  }, [seedCustom.CALENDARS, extras?.calendarsExt]);
  const [sel, setSel] = React.useState(null);
  const [showNew, setShowNew] = React.useState(false);

  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12, alignItems:'center' }}>
        <div style={{ fontSize: 12, color: 'var(--text-2)' }}>
          I calendari determinano giorni lavorativi e festività per il calcolo SLA in ogni paese/sito. Fanno da base per <code>sla-engine</code>, workflow approvazioni e notifiche di quiet hours.
        </div>
        <span className="spacer"/>
        <ConfigWriteBtn onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuovo calendario</ConfigWriteBtn>
      </div>
      <table className="tbl dense">
        <thead><tr>
          <th style={{width:110}}>Code</th><th>Nome</th>
          <th style={{width:70,textAlign:'center'}}>Paese</th>
          <th style={{width:70,textAlign:'center'}}>Anno</th>
          <th style={{width:120}}>Orario</th>
          <th style={{width:110}}>Weekend</th>
          <th style={{width:90,textAlign:'right'}}>Festività</th>
          <th style={{width:80,textAlign:'center'}}>Default</th>
          <th style={{width:70,textAlign:'center'}}>Stato</th>
        </tr></thead>
        <tbody>{cals.map(c => (
          <tr key={c.id} className="clickable" onClick={()=>setSel(c)}>
            <td className="mono">{c.code}</td>
            <td style={{fontWeight:500}}>{c.name}</td>
            <td style={{textAlign:'center'}}><Chip>{c.country}</Chip></td>
            <td className="num mono" style={{textAlign:'center'}}>{c.year}</td>
            <td className="mono" style={{fontSize:11}}>{c.workingHours?.start}–{c.workingHours?.end}</td>
            <td style={{fontSize:11, color:'var(--text-2)'}}>{weekendLabel(c.weekendDays)}</td>
            <td className="num mono">{Array.isArray(c.holidays) ? c.holidays.length : (c.holidays || 0)}</td>
            <td style={{textAlign:'center'}}>{c.isDefault ? <Chip kind="ok">default</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
            <td style={{textAlign:'center'}}><Chip kind={c.active?'ok':''} dot>{c.active?'on':'off'}</Chip></td>
          </tr>
        ))}</tbody>
      </table>

      <CalendarDetailModal open={!!sel} onClose={()=>setSel(null)} cal={sel}/>
      <NewCalendarModal open={showNew} onClose={()=>setShowNew(false)} cals={cals}/>
    </>
  );
}

function CalendarMiniGrid({ year, weekendDays = [6,0], holidays = [] }) {
  const holidaySet = React.useMemo(() => new Set(holidays), [holidays]);
  const months = ['Gen','Feb','Mar','Apr','Mag','Giu','Lug','Ago','Set','Ott','Nov','Dic'];
  return (
    <div style={{display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:10}}>
      {months.map((mLabel, mi) => {
        const firstDay = new Date(year, mi, 1);
        const daysInMonth = new Date(year, mi+1, 0).getDate();
        const offset = (firstDay.getDay() + 6) % 7; // to Mon=0
        const cells = [];
        for (let i=0; i<offset; i++) cells.push(null);
        for (let d=1; d<=daysInMonth; d++) {
          const date = new Date(year, mi, d);
          const iso = `${year}-${String(mi+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
          const jsDow = date.getDay();
          const isWeekend = weekendDays.includes(jsDow);
          const isHoliday = holidaySet.has(iso);
          cells.push({ d, iso, isWeekend, isHoliday });
        }
        return (
          <div key={mi} style={{background:'var(--bg-1)', border:'1px solid var(--line)', borderRadius:6, padding:8}}>
            <div style={{fontSize:10.5, fontWeight:600, color:'var(--text-2)', marginBottom:4, textAlign:'center'}}>{mLabel} {year}</div>
            <div style={{display:'grid', gridTemplateColumns:'repeat(7,1fr)', gap:1}}>
              {DOW_LABELS.map(l => <div key={l} style={{fontSize:8.5, color:'var(--text-3)', textAlign:'center', padding:'1px 0'}}>{l}</div>)}
              {cells.map((c, i) => c == null
                ? <div key={i}/>
                : <div key={i} title={c.isHoliday?`Festività ${c.iso}`:c.iso} style={{
                    fontSize:9, textAlign:'center', padding:'2px 0', borderRadius:2,
                    background: c.isHoliday ? 'color-mix(in oklch, var(--err) 25%, var(--bg-2))' : c.isWeekend ? 'var(--bg-2)' : 'transparent',
                    color: c.isHoliday ? 'var(--err)' : c.isWeekend ? 'var(--text-3)' : 'var(--text-1)',
                    fontWeight: c.isHoliday ? 600 : 400,
                  }}>{c.d}</div>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// FASE 3a.15 batch: refactor con useEditableEntity + PATCH /api/config/calendars/[id].
function CalendarDetailModal({ open, onClose, cal }) {
  const { addCalendar, pushToast, user } = useStore();
  const [tab, setTab] = React.useState('overview');
  const [newHoliday, setNewHoliday] = React.useState({ date: '', label: '' });

  React.useEffect(() => {
    if (cal) setTab('overview');
  }, [cal]);

  const initial = React.useMemo(() => {
    if (!cal) return null;
    return {
      code: cal.code || '',
      name: cal.name || '',
      country: cal.country || 'IT',
      year: Number(cal.year) || new Date().getFullYear(),
      // Preserva ordine convenzionale del seed (es. [6,0] = Sab,Dom).
      weekendDays: Array.isArray(cal.weekendDays) ? [...cal.weekendDays] : [6, 0],
      workingHours: {
        start: cal.workingHours?.start || '08:00',
        end: cal.workingHours?.end || '17:00',
      },
      holidays: Array.isArray(cal.holidays) ? [...cal.holidays] : [],
      isDefault: !!cal.isDefault,
      active: cal.active !== false,
    };
  }, [cal]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    name: (f.name || '').trim(),
    country: f.country,
    year: Number(f.year),
    weekendDays: [...(f.weekendDays || [])],
    workingHours: { start: f.workingHours?.start || '08:00', end: f.workingHours?.end || '17:00' },
    holidays: [...(f.holidays || [])],
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => cal ? `/api/config/calendars/${cal.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addCalendar(json.data);
      pushToast({ title: `${json.data.code} · ${json.data.name}`, desc: `Calendario aggiornato. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  // Hooks PRIMA dell'early return (Rules of Hooks).
  const workingDays = React.useMemo(() => {
    if (!form?.year) return 0;
    let count = 0;
    const holidaySet = new Set(form.holidays || []);
    for (let m=0; m<12; m++) {
      const days = new Date(form.year, m+1, 0).getDate();
      for (let d=1; d<=days; d++) {
        const dt = new Date(form.year, m, d);
        const iso = `${form.year}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
        if ((form.weekendDays || []).includes(dt.getDay())) continue;
        if (holidaySet.has(iso)) continue;
        count++;
      }
    }
    return count;
  }, [form?.year, form?.weekendDays, form?.holidays]);

  if (!cal || !form?.code) return <Modal open={false} onClose={onClose} title=""/>;

  const setHours = (k, v) => set('workingHours', { ...(form.workingHours || {}), [k]: v });
  const toggleWeekend = (dow) => {
    const cur = form.weekendDays || [];
    set('weekendDays', cur.includes(dow) ? cur.filter(x => x !== dow) : [...cur, dow].sort((a,b)=>a-b));
  };
  const addHoliday = () => {
    if (!newHoliday.date) return;
    const cur = form.holidays || [];
    if (cur.includes(newHoliday.date)) return;
    set('holidays', [...cur, newHoliday.date].sort());
    setNewHoliday({ date: '', label: '' });
  };
  const removeHoliday = (iso) => set('holidays', (form.holidays || []).filter(h => h !== iso));

  const hoursPerDay = (() => {
    const [sh, sm] = (form.workingHours?.start||'08:00').split(':').map(Number);
    const [eh, em] = (form.workingHours?.end||'17:00').split(':').map(Number);
    return Math.max(0, (eh*60+em) - (sh*60+sm)) / 60 - 1; // assume 1h pranzo
  })();

  const codeValid = !form.code || /^[A-Z][A-Z0-9_]{1,31}$/.test(form.code);
  const nameValid = (form.name || '').trim().length > 0;
  const valid = codeValid && nameValid && form.workingHours?.start < form.workingHours?.end;

  return (
    <Modal open={open} onClose={onClose} title={`${cal.code} · ${cal.name}`} size="xl"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="ghost" size="sm"><Icon name="download" size={11}/> Export .ics</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      {serverError && (
        <div style={{ marginBottom: 10, padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
          <strong>Errore salvataggio:</strong> {serverError}
        </div>
      )}
      <div className="row" style={{ gap: 2, marginBottom: 14, background: 'var(--bg-2)', padding: 2, borderRadius: 6 }}>
        <button className={`btn sm ${tab==='overview'?'primary':'ghost'}`} onClick={()=>setTab('overview')}>Generale</button>
        <button className={`btn sm ${tab==='holidays'?'primary':'ghost'}`} onClick={()=>setTab('holidays')}>Festività ({form.holidays.length})</button>
        <button className={`btn sm ${tab==='preview'?'primary':'ghost'}`} onClick={()=>setTab('preview')}>Anteprima anno</button>
        <button className={`btn sm ${tab==='uses'?'primary':'ghost'}`} onClick={()=>setTab('uses')}>Dove è usato</button>
      </div>

      {tab === 'overview' && (
        <div className="col" style={{gap:14}}>
          <div className="grid grid-4">
            <Stat label="Giorni lavorativi" value={workingDays}/>
            <Stat label="Festività" value={form.holidays.length}/>
            <Stat label="Ore/giorno" value={hoursPerDay.toFixed(1)}/>
            <Stat label="Ore/anno" value={(workingDays*hoursPerDay).toFixed(0)}/>
          </div>
          <div className="grid grid-3">
            <div className="field"><label>Code</label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase())} style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field"><label>Paese</label>
              <select value={form.country} onChange={e=>set('country', e.target.value)}>
                {['IT','FR','DE','ES','PL','UK','US','CH','AT','NL','BE'].map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
            <div className="field"><label>Anno</label>
              <input type="number" min={2024} max={2030} value={form.year} onChange={e=>set('year', Number(e.target.value))}/>
            </div>
          </div>
          <div className="field"><label>Nome</label>
            <input value={form.name} onChange={e=>set('name', e.target.value)}/>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Orario di lavoro</div>
            <div className="grid grid-3">
              <div className="field"><label>Inizio</label>
                <input type="time" value={form.workingHours?.start||''} onChange={e=>setHours('start', e.target.value)}/>
              </div>
              <div className="field"><label>Fine</label>
                <input type="time" value={form.workingHours?.end||''} onChange={e=>setHours('end', e.target.value)}/>
              </div>
              <div className="field" style={{display:'flex', flexDirection:'column', justifyContent:'flex-end'}}>
                <div style={{fontSize:10.5, color:'var(--text-3)'}}>≈ {hoursPerDay.toFixed(1)}h / giorno (pausa 1h inclusa)</div>
              </div>
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Giorni di weekend (non lavorativi)</div>
            <div style={{display:'flex', gap:4}}>
              {DOW_LABELS.map((label, i) => {
                const dow = DOW_TO_JS[i];
                const on = form.weekendDays.includes(dow);
                return (
                  <button key={label} type="button" onClick={()=>toggleWeekend(dow)}
                    className={`btn sm ${on?'primary':'ghost'}`}
                    style={{minWidth:50}}>{label}</button>
                );
              })}
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Flag</div>
            <div className="row" style={{gap:14, fontSize:11.5}}>
              <label className="row" style={{gap:6, opacity:0.6}} title="Il default si calcola server-side (id === 'CAL_IT'). Non modificabile da UI in questa fase.">
                <input type="checkbox" checked={!!form.isDefault} disabled readOnly/> Default tenant
              </label>
              <label className="row" style={{gap:6}}>
                <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/> Attivo
              </label>
            </div>
          </div>
        </div>
      )}

      {tab === 'holidays' && (
        <div className="col" style={{gap:12}}>
          <div style={{fontSize:11.5, color:'var(--text-2)'}}>
            {form.holidays.length} festività definite per {form.year}. Ordinate cronologicamente.
          </div>
          <div className="row" style={{gap:6, alignItems:'flex-end'}}>
            <div className="field" style={{flex:'0 0 160px'}}>
              <label>Data</label>
              <input type="date" value={newHoliday.date}
                min={`${form.year}-01-01`} max={`${form.year}-12-31`}
                onChange={e=>setNewHoliday(h=>({...h, date: e.target.value}))}/>
            </div>
            <div className="field" style={{flex:1}}>
              <label>Descrizione (opzionale)</label>
              <input value={newHoliday.label} onChange={e=>setNewHoliday(h=>({...h, label: e.target.value}))} placeholder="es. Ferragosto"/>
            </div>
            <ConfigWriteBtn onClick={addHoliday} disabled={!newHoliday.date}><Icon name="plus" size={11}/> Aggiungi</ConfigWriteBtn>
          </div>
          {form.holidays.length === 0 ? (
            <div style={{fontSize:11, color:'var(--text-3)', textAlign:'center', padding:20}}>Nessuna festività — tutti i giorni feriali sono lavorativi.</div>
          ) : (
            <table className="tbl dense">
              <thead><tr>
                <th style={{width:130}}>Data</th>
                <th style={{width:90}}>Giorno</th>
                <th>Mese</th>
                <th style={{width:50, textAlign:'center'}}></th>
              </tr></thead>
              <tbody>{form.holidays.map(iso => {
                const d = new Date(iso);
                const dow = DOW_LABELS[(d.getDay()+6)%7];
                const mo = d.toLocaleDateString('it-IT', { month:'long' });
                return (
                  <tr key={iso}>
                    <td className="mono" style={{fontSize:11.5}}>{iso}</td>
                    <td style={{fontSize:11.5, color:'var(--text-2)'}}>{dow}</td>
                    <td style={{fontSize:11.5, textTransform:'capitalize'}}>{mo}</td>
                    <td style={{textAlign:'center'}}>
                      <Btn variant="ghost" size="sm" onClick={()=>removeHoliday(iso)}><Icon name="x" size={11}/></Btn>
                    </td>
                  </tr>
                );
              })}</tbody>
            </table>
          )}
        </div>
      )}

      {tab === 'preview' && (
        <div className="col" style={{gap:10}}>
          <div style={{fontSize:11.5, color:'var(--text-2)'}}>
            Anteprima anno {form.year} — <span style={{color:'var(--err)'}}>festività</span>, <span style={{color:'var(--text-3)'}}>weekend</span>, giorni lavorativi.
          </div>
          <CalendarMiniGrid year={form.year} weekendDays={form.weekendDays} holidays={form.holidays}/>
        </div>
      )}

      {tab === 'uses' && (
        <div className="col" style={{gap:10}}>
          <div style={{fontSize:11.5, color:'var(--text-2)'}}>Oggetti di configurazione che usano questo calendario.</div>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>SLA Policies</div>
            <CalendarUsedByList calendarId={cal.id}/>
          </div>
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4}}>
            <Icon name="info" size={10}/> In produzione: anche Siti, Reminder notifiche, Scheduler report referenziano questo calendario. La disattivazione è bloccata finché esistono riferimenti.
          </div>
        </div>
      )}
    </Modal>
  );
}

function CalendarUsedByList({ calendarId }) {
  const { seedCustom } = useStore();
  const slas = (seedCustom.SLA_POLICIES || []).filter(s => s.calendar === calendarId);
  if (slas.length === 0) return <div style={{fontSize:11, color:'var(--text-3)'}}>Nessuna policy associata.</div>;
  return (
    <table className="tbl dense">
      <thead><tr>
        <th style={{width:120}}>Code</th><th>Nome</th>
        <th style={{width:100}}>Entity</th>
        <th style={{width:70, textAlign:'right'}}>Target</th>
      </tr></thead>
      <tbody>{slas.map(s => (
        <tr key={s.id}>
          <td className="mono">{s.code}</td>
          <td>{s.name}</td>
          <td><Chip>{s.entityType}</Chip></td>
          <td className="num mono">{s.targetDays}g</td>
        </tr>
      ))}</tbody>
    </table>
  );
}

function NewCalendarModal({ open, onClose, cals }) {
  const { addCalendar, pushToast, user } = useStore();
  const initial = () => ({
    code: '', name: '', country: 'IT', year: new Date().getFullYear()+1,
    startHour: '08:00', endHour: '17:00',
    weekendDays: [6, 0],
    preset: 'IT',
    isDefault: false, active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => {
    if (open) { setForm(initial()); setServerError(null); }
  }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));
  const toggleWeekend = (dow) => set('weekendDays',
    form.weekendDays.includes(dow) ? form.weekendDays.filter(x=>x!==dow) : [...form.weekendDays, dow].sort());

  const PRESETS = {
    IT: { country: 'IT', holidays: (y) => [`${y}-01-01`,`${y}-01-06`,`${y}-04-25`,`${y}-05-01`,`${y}-06-02`,`${y}-08-15`,`${y}-11-01`,`${y}-12-08`,`${y}-12-25`,`${y}-12-26`] },
    FR: { country: 'FR', holidays: (y) => [`${y}-01-01`,`${y}-05-01`,`${y}-05-08`,`${y}-07-14`,`${y}-08-15`,`${y}-11-01`,`${y}-11-11`,`${y}-12-25`] },
    DE: { country: 'DE', holidays: (y) => [`${y}-01-01`,`${y}-05-01`,`${y}-10-03`,`${y}-12-25`,`${y}-12-26`] },
    ES: { country: 'ES', holidays: (y) => [`${y}-01-01`,`${y}-01-06`,`${y}-05-01`,`${y}-08-15`,`${y}-10-12`,`${y}-11-01`,`${y}-12-06`,`${y}-12-08`,`${y}-12-25`] },
    PL: { country: 'PL', holidays: (y) => [`${y}-01-01`,`${y}-01-06`,`${y}-05-01`,`${y}-05-03`,`${y}-08-15`,`${y}-11-01`,`${y}-11-11`,`${y}-12-25`,`${y}-12-26`] },
    none: { country: 'IT', holidays: () => [] },
  };
  const selectedPreset = PRESETS[form.preset] || PRESETS.none;
  const previewHolidays = selectedPreset.holidays(form.year);

  const codeValid = /^[A-Z][A-Z0-9_]{1,31}$/.test(form.code);
  const codeUnique = !cals.some(c => c.code === form.code);
  const hoursValid = form.startHour < form.endHour;
  const valid = codeValid && codeUnique && form.name.trim() && hoursValid;

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/calendars', {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}),
        },
        body: JSON.stringify({
          code: form.code,
          name: form.name.trim(),
          country: form.country,
          year: form.year,
          weekendDays: form.weekendDays,
          workingHours: { start: form.startHour, end: form.endHour },
          holidays: previewHolidays,
          active: !!form.active,
          // NB: form.isDefault è UI-only (non in schema DB capex_class). Resta locale.
        }),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok) {
        const msg = json?.error === 'validation_error'
          ? `Validazione fallita: ${(json.issues || []).map(i => i.message).join(' · ') || 'campi non validi'}`
          : (json?.error || `HTTP ${res.status}`);
        setServerError(msg);
        return;
      }
      const created = json?.data;
      if (created) {
        addCalendar(created);
        pushToast({ title: `${created.code} · ${created.name}`, desc: 'Calendario salvato in DB. Audit log registrato.', tone: 'ok' });
      }
      onClose();
    } catch (err) {
      setServerError(String(err?.message || err));
    } finally {
      setSaving(false);
    }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuovo calendario lavorativo" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea calendario'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Un calendario ha un anno di validità. Per l'anno successivo va duplicato e aggiornato. I preset caricano le festività nazionali principali, ma <strong>ricorrenze mobili (Pasqua, Pentecoste)</strong> e festività patronali vanno aggiunte manualmente dopo la creazione.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="grid grid-3">
            <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.code} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g,''))} placeholder="IT_2027" style={{fontFamily:'var(--font-mono)'}}/>
            </div>
            <div className="field" style={{gridColumn:'span 2'}}><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="es. Calendario lavorativo Italia 2027"/>
            </div>
          </div>
          {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}><Icon name="alert-triangle" size={10}/> Code: A-Z, 0-9, _ — inizia con maiuscola</div>}
          {codeValid && !codeUnique && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}><Icon name="alert-triangle" size={10}/> Code già esistente</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Ambito</div>
          <div className="grid grid-3">
            <div className="field"><label>Paese</label>
              <select value={form.country} onChange={e=>set('country', e.target.value)}>
                {['IT','FR','DE','ES','PL','UK','US','CH','AT','NL','BE'].map(c => <option key={c} value={c}>{c}</option>)}
              </select>
            </div>
            <div className="field"><label>Anno</label>
              <input type="number" min={new Date().getFullYear()} max={2030} value={form.year} onChange={e=>set('year', Number(e.target.value))}/>
            </div>
            <div className="field"><label>Preset festività</label>
              <select value={form.preset} onChange={e=>set('preset', e.target.value)}>
                <option value="IT">Italia (10 festività nazionali)</option>
                <option value="FR">Francia (8)</option>
                <option value="DE">Germania (5 federali)</option>
                <option value="ES">Spagna (9)</option>
                <option value="PL">Polonia (9)</option>
                <option value="none">Nessuno (vuoto)</option>
              </select>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Orario di lavoro</div>
          <div className="grid grid-3">
            <div className="field"><label>Inizio</label>
              <input type="time" value={form.startHour} onChange={e=>set('startHour', e.target.value)}/>
            </div>
            <div className="field"><label>Fine</label>
              <input type="time" value={form.endHour} onChange={e=>set('endHour', e.target.value)}/>
            </div>
            <div className="field" style={{display:'flex', flexDirection:'column', justifyContent:'flex-end'}}>
              <div style={{fontSize:10.5, color: hoursValid ? 'var(--text-3)' : 'var(--err)'}}>
                {hoursValid ? 'Intervallo valido' : 'Fine deve essere successivo a inizio'}
              </div>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Giorni weekend</div>
          <div style={{display:'flex', gap:4}}>
            {DOW_LABELS.map((label, i) => {
              const dow = DOW_TO_JS[i];
              const on = form.weekendDays.includes(dow);
              return (
                <button key={label} type="button" onClick={()=>toggleWeekend(dow)}
                  className={`btn sm ${on?'primary':'ghost'}`}
                  style={{minWidth:50}}>{label}</button>
              );
            })}
          </div>
          <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:4}}>
            {form.weekendDays.length === 0 ? 'Nessun weekend: ogni giorno feriale considerato lavorativo' :
             form.weekendDays.length === 2 ? 'Standard 5 giorni lavorativi' :
             'Configurazione non standard (turni continuativi, paesi islamici, ecc.)'}
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Anteprima festività caricate ({previewHolidays.length})</div>
          {previewHolidays.length === 0 ? (
            <div style={{fontSize:11, color:'var(--text-3)'}}>Calendario verrà creato vuoto. Aggiungi festività dopo la creazione.</div>
          ) : (
            <div style={{display:'flex', flexWrap:'wrap', gap:4, maxHeight:80, overflow:'auto'}}>
              {previewHolidays.map(h => <Chip key={h}>{h}</Chip>)}
            </div>
          )}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Flag</div>
          <div className="row" style={{gap:14, fontSize:11.5}}>
            <label className="row" style={{gap:6}}><input type="checkbox" checked={form.isDefault} onChange={e=>set('isDefault', e.target.checked)}/> Imposta come default del tenant</label>
            <label className="row" style={{gap:6}}><input type="checkbox" checked={form.active} onChange={e=>set('active', e.target.checked)}/> Attivo</label>
          </div>
        </div>

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/calendars</code> persiste in DB con audit log. Salvati: code, name, country, year, weekendDays, workingHours, holidays (calcolate dal preset), active. Note: <code>isDefault</code> è UI-only (non in schema capex_class). Dopo publish, SLA policies e siti possono referenziare il nuovo calendario.
        </div>

        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// -------------- DELEGATIONS --------------
function CustDelegations() {
  const { seedCustom, extras, seed } = useStore();
  // FASE 3a.7: merge extras + seed con dedup per id (extras vince).
  const dels = React.useMemo(() => {
    const seedList = seedCustom.DELEGATIONS || [];
    const extList = extras?.delegationsExt || [];
    const seenIds = new Set();
    const out = [];
    for (const d of [...extList, ...seedList]) {
      if (!d?.id || seenIds.has(d.id)) continue;
      seenIds.add(d.id);
      out.push(d);
    }
    return out;
  }, [seedCustom.DELEGATIONS, extras?.delegationsExt]);
  const personas = (seed && seed.PERSONAS) || [];
  const [showNew, setShowNew] = React.useState(false);
  // FASE 3a.15 batch: detail modal editable Delegations.
  const [selDel, setSelDel] = React.useState(null);

  // FASE 16 (sessione 94) — stato reale della delega derivato da active + periodo.
  const today = new Date().toISOString().slice(0, 10);
  const delegationStatus = (d) => {
    if (d.active === false) return { label: 'disattivata', kind: '' };
    if (d.fromDate && today < d.fromDate) return { label: 'programmata', kind: 'ai' };
    if (d.toDate && today > d.toDate) return { label: 'scaduta', kind: '' };
    return { label: 'attiva', kind: 'ok' };
  };

  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12, alignItems:'center' }}>
        <div style={{ fontSize: 12, color: 'var(--text-2)' }}>
          Durante il periodo indicato il sostituto eredita i ruoli del delegante: può approvare gli step di workflow e firmare i documenti al posto suo. Le deleghe attive compaiono anche nel feed "Cosa devi fare" del sostituto.
        </div>
        <span className="spacer"/>
        <Btn variant="primary" size="sm" onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuova delega</Btn>
      </div>

      <NewDelegationModal open={showNew} onClose={()=>setShowNew(false)} personas={personas}/>
      <DelegationDetailModal selDel={selDel} onClose={()=>setSelDel(null)} personas={personas}/>
      <table className="tbl dense">
        <thead><tr>
          <th style={{width:90}}>ID</th>
          <th>Delegante</th>
          <th>Sostituto</th>
          <th style={{width:200}}>Periodo</th>
          <th>Motivo</th>
          <th style={{width:120,textAlign:'center'}}>Stato</th>
        </tr></thead>
        <tbody>{dels.map(d => {
          const st = delegationStatus(d);
          return (
          <tr key={d.id} className="clickable" onClick={()=>setSelDel(d)}>
            <td className="mono" style={{fontSize:10.5}}>{d.id}</td>
            <td style={{fontSize:11.5}}>{d.fromUser}</td>
            <td style={{fontSize:11.5}}>{d.toUser}</td>
            <td className="mono" style={{fontSize:11}}>{d.fromDate} → {d.toDate}</td>
            <td style={{fontSize:11, color:'var(--text-2)'}}>{d.reason}</td>
            <td style={{textAlign:'center'}}><Chip kind={st.kind} dot>{st.label}</Chip></td>
          </tr>
          );
        })}</tbody>
      </table>
    </>
  );
}

function NewDelegationModal({ open, onClose, personas }) {
  const { addDelegation, pushToast, user } = useStore();
  const today = new Date().toISOString().slice(0,10);
  const initial = () => ({
    fromPersonaId: '', toPersonaId: '',
    fromDate: today, toDate: '',
    reason: '', active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  const personasValid = form.fromPersonaId && form.toPersonaId && form.fromPersonaId !== form.toPersonaId;
  const datesValid = form.fromDate && form.toDate && form.fromDate < form.toDate;
  const valid = personasValid && datesValid;

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/delegations', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          fromPersonaId: form.fromPersonaId,
          toPersonaId: form.toPersonaId,
          fromDate: form.fromDate,
          toDate: form.toDate,
          reason: form.reason.trim() || null,
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) { addDelegation(json.data); pushToast({ title: `Delega ${json.data.fromUser} → ${json.data.toUser}`, desc: 'Salvata in DB. Audit registrato.', tone: 'ok' }); }
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuova delega" size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea delega'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Durante l'intervallo indicato il sostituto eredita i ruoli del delegante e può agire al posto suo: approvare gli step di workflow e firmare i documenti. La delega compare nel feed "Cosa devi fare" del sostituto.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Persone</div>
          <div className="grid grid-2">
            <div className="field"><label>Delegante <span style={{color:'var(--err)'}}>*</span></label>
              <PersonaAutocomplete
                value={form.fromPersonaId}
                onChange={(id) => set('fromPersonaId', id || '')}
                placeholder="Cerca delegante…"
                personasFallback={personas}
              />
            </div>
            <div className="field"><label>Sostituto <span style={{color:'var(--err)'}}>*</span></label>
              <PersonaAutocomplete
                value={form.toPersonaId}
                onChange={(id) => set('toPersonaId', id || '')}
                placeholder="Cerca sostituto…"
                personasFallback={personas}
                excludeIds={form.fromPersonaId ? [form.fromPersonaId] : []}
              />
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Periodo</div>
          <div className="grid grid-2">
            <div className="field"><label>Da <span style={{color:'var(--err)'}}>*</span></label>
              <input type="date" value={form.fromDate} onChange={e=>set('fromDate', e.target.value)}/>
            </div>
            <div className="field"><label>A <span style={{color:'var(--err)'}}>*</span></label>
              <input type="date" value={form.toDate} onChange={e=>set('toDate', e.target.value)}/>
            </div>
          </div>
          {form.fromDate && form.toDate && !datesValid && <div style={{fontSize:10.5, color:'var(--err)'}}><Icon name="alert-triangle" size={10}/> A deve essere dopo Da</div>}
        </div>

        <div className="field"><label>Motivo</label>
          <input value={form.reason} onChange={e=>set('reason', e.target.value)} placeholder="es. Ferie estive 2026"/>
        </div>

        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 → ridisegnato FASE 16 (sessione 94): detail modal editable Delegation.
// Read-only su delegante/sostituto (cambio struttura non in scope BE PATCH).
// Usa `useFetchedDetail` per il GET pre-fetch del dato reale dal server.
function DelegationDetailModal({ selDel, onClose, personas }) {
  const { addDelegation, pushToast, user } = useStore();

  // Fetch dato reale dal server al mount via hook condiviso.
  const serverDel = useFetchedDetail(selDel, (s) => `/api/config/delegations/${s.id}`);
  const baseDel = serverDel || selDel;

  const initial = React.useMemo(() => {
    if (!baseDel) return null;
    return {
      fromDate: baseDel.fromDate || '',
      toDate: baseDel.toDate || '',
      reason: baseDel.reason || '',
      active: baseDel.active !== false,
    };
  }, [baseDel]);

  const buildBody = React.useCallback((f) => ({
    fromDate: f.fromDate,
    toDate: f.toDate,
    reason: (f.reason || '').trim() ? (f.reason || '').trim() : null,
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selDel ? `/api/config/delegations/${selDel.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addDelegation(json.data);
      pushToast({ title: `Delega ${json.data.fromUser} → ${json.data.toUser}`, desc: `Aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selDel) return <Modal open={false} onClose={onClose} title="" />;

  const datesValid = form.fromDate && form.toDate && form.fromDate < form.toDate;
  const valid = datesValid;

  const fromName = (personas || []).find(p => p.id === selDel.fromPersonaId)?.name || selDel.fromUser || selDel.fromPersonaId;
  const toName = (personas || []).find(p => p.id === selDel.toPersonaId)?.name || selDel.toUser || selDel.toPersonaId;

  return (
    <Modal open={!!selDel} onClose={onClose} title={`Delega ${fromName} → ${toName}`} size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          ID delega: <code>{selDel.id}</code>. Il sostituto <strong>{toName}</strong> eredita i ruoli di <strong>{fromName}</strong> per il periodo indicato. Delegante e sostituto non sono modificabili: per cambiarli, disattiva questa delega e creane una nuova.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Periodo</div>
          <div className="grid grid-2">
            <div className="field"><label>Da</label>
              <input type="date" value={form.fromDate || ''} onChange={e=>set('fromDate', e.target.value)}/>
            </div>
            <div className="field"><label>A</label>
              <input type="date" value={form.toDate || ''} onChange={e=>set('toDate', e.target.value)}/>
            </div>
          </div>
          {form.fromDate && form.toDate && !datesValid && <div style={{fontSize:10.5, color:'var(--err)'}}>A deve essere dopo Da</div>}
        </div>

        <div className="field"><label>Motivo</label>
          <input value={form.reason || ''} onChange={e=>set('reason', e.target.value)} placeholder="es. Ferie estive"/>
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Delega attiva
          </label>
        </div>
      </div>
    </Modal>
  );
}

// -------------- NOTIFICATIONS --------------
function CustNotifications() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.15 batch: dedup extras+seed per rules+events.
  const events = React.useMemo(() => {
    const seedList = seedCustom.NOTIFICATION_EVENTS || [];
    const extList = extras?.notifEventsExt || [];
    const seenIds = new Set();
    const out = [];
    for (const e of [...extList, ...seedList]) {
      if (!e?.id || seenIds.has(e.id)) continue;
      seenIds.add(e.id);
      out.push(e);
    }
    return out;
  }, [seedCustom.NOTIFICATION_EVENTS, extras?.notifEventsExt]);
  const rules = React.useMemo(() => {
    const seedList = seedCustom.NOTIFICATION_RULES || [];
    const extList = extras?.notifRulesExt || [];
    const seenIds = new Set();
    const out = [];
    for (const r of [...extList, ...seedList]) {
      if (!r?.id || seenIds.has(r.id)) continue;
      seenIds.add(r.id);
      out.push(r);
    }
    return out;
  }, [seedCustom.NOTIFICATION_RULES, extras?.notifRulesExt]);
  const [tab, setTab] = React.useState('rules');
  const [showNewRule, setShowNewRule] = React.useState(false);
  const [showNewEvent, setShowNewEvent] = React.useState(false);
  const [selRule, setSelRule] = React.useState(null);
  const [selEvent, setSelEvent] = React.useState(null);

  return (
    <>
      <div className="row" style={{ gap: 2, marginBottom: 12, background: 'var(--bg-2)', padding: 2, borderRadius: 6, alignItems:'center' }}>
        <button className={`btn sm ${tab==='rules'?'primary':'ghost'}`} onClick={()=>setTab('rules')}>Regole di invio ({rules.length})</button>
        <button className={`btn sm ${tab==='events'?'primary':'ghost'}`} onClick={()=>setTab('events')}>Eventi ({events.length})</button>
        <span className="spacer"/>
        {tab === 'rules'
          ? <ConfigWriteBtn onClick={()=>setShowNewRule(true)}><Icon name="plus" size={11}/> Nuova regola</ConfigWriteBtn>
          : <ConfigWriteBtn onClick={()=>setShowNewEvent(true)}><Icon name="plus" size={11}/> Nuovo evento</ConfigWriteBtn>}
      </div>

      {tab === 'rules' ? (
        <table className="tbl dense">
          <thead><tr>
            <th style={{width:100}}>ID</th>
            <th>Nome</th>
            <th style={{width:170}}>Evento</th>
            <th>Destinatari</th>
            <th style={{width:160}}>Canali</th>
            <th style={{width:130}}>Template</th>
            <th style={{width:110,textAlign:'center'}}>Freq. max</th>
            <th style={{width:100,textAlign:'center'}}>Quiet hours</th>
            <th style={{width:70,textAlign:'center'}}>Stato</th>
          </tr></thead>
          <tbody>{rules.map(r => (
            <tr key={r.id} className="clickable" onClick={()=>setSelRule(r)}>
              <td className="mono" style={{fontSize:10.5}}>{r.id}</td>
              <td style={{fontWeight:500}}>{r.name}</td>
              <td className="mono" style={{fontSize:11}}>{r.event}</td>
              <td style={{fontSize:11.5}}><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(r.recipients||[]).map(x => <Chip key={x}>{x}</Chip>)}</div></td>
              <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(r.channels||[]).map(c => <Chip key={c} kind="info">{c}</Chip>)}</div></td>
              <td className="mono" style={{fontSize:11}}>{r.template || <span style={{color:'var(--text-3)'}}>default</span>}</td>
              <td style={{textAlign:'center', fontSize:11}}>{r.maxFrequency || '—'}</td>
              <td style={{textAlign:'center'}}>{r.quietHours ? <Chip kind="warn">{r.quietHours}</Chip> : <span style={{color:'var(--text-3)'}}>—</span>}</td>
              <td style={{textAlign:'center'}}><Chip kind={r.active?'ok':''} dot>{r.active?'on':'off'}</Chip></td>
            </tr>
          ))}</tbody>
        </table>
      ) : (
        <table className="tbl dense">
          <thead><tr><th style={{width:200}}>Code</th><th>Descrizione</th><th>Payload</th><th style={{width:120}}>Categoria</th></tr></thead>
          <tbody>{events.map(e => (
            <tr key={e.id} className="clickable" onClick={()=>setSelEvent(e)}>
              <td className="mono" style={{fontSize:11}}>{e.code}</td>
              <td>{e.description}</td>
              <td style={{fontSize:11, color:'var(--text-2)', fontFamily:'var(--font-mono)'}}>{(e.payloadFields||[]).join(', ')}</td>
              <td><Chip>{e.category}</Chip></td>
            </tr>
          ))}</tbody>
        </table>
      )}

      <NewNotificationRuleModal open={showNewRule} onClose={()=>setShowNewRule(false)} events={events}/>
      <NewNotificationEventModal open={showNewEvent} onClose={()=>setShowNewEvent(false)}/>
      <NotificationRuleDetailModal selRule={selRule} onClose={()=>setSelRule(null)} events={events}/>
      <NotificationEventDetailModal selEvent={selEvent} onClose={()=>setSelEvent(null)}/>
    </>
  );
}

function NewNotificationRuleModal({ open, onClose, events }) {
  const { pushToast, user } = useStore();
  const initial = () => ({
    name: '', event: events[0]?.code || '',
    recipients: [], channels: ['email','in_app'],
    template: '', maxFrequency: 'realtime', quietHours: '',
    active: true,
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open, events]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));
  const valid = form.name.trim() && form.event && form.recipients.length > 0 && form.channels.length > 0;

  const selEvent = events.find(e => e.code === form.event);

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/notification-rules', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          name: form.name.trim(), eventCode: form.event,
          recipients: form.recipients, channels: form.channels,
          templateId: form.template || null,
          maxFrequency: form.maxFrequency, quietHours: form.quietHours.trim() || null,
          active: !!form.active,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) pushToast({ title: `${json.data.name}`, desc: 'Regola notifica salvata in DB. Audit registrato.', tone: 'ok' });
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuova regola di notifica" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="ghost" size="sm" disabled={!valid || saving}><Icon name="eye" size={11}/> Anteprima</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea regola'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Le regole di notifica collegano un <strong>evento di dominio</strong> a uno o più destinatari e canali. Vedi <code>docs/notifications.md</code>.
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Identificativo</div>
          <div className="field"><label>Nome regola <span style={{color:'var(--err)'}}>*</span></label>
            <input value={form.name} onChange={e=>set('name', e.target.value)} placeholder="es. RdA emergenza → CFO"/>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Trigger</div>
          <div className="field"><label>Evento</label>
            <select value={form.event} onChange={e=>set('event', e.target.value)} style={{fontFamily:'var(--font-mono)'}}>
              {events.map(e => <option key={e.id} value={e.code}>{e.code} — {e.description}</option>)}
            </select>
          </div>
          {selEvent && (
            <div style={{marginTop:6, fontSize:10.5, color:'var(--text-3)', padding:'6px 8px', background:'var(--bg-2)', borderRadius:4}}>
              <strong>Payload disponibile:</strong> <span style={{fontFamily:'var(--font-mono)'}}>{(selEvent.payloadFields||[]).join(', ')}</span>
            </div>
          )}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Destinatari <span style={{color:'var(--err)'}}>*</span></div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:8, border:'1px solid var(--line)', borderRadius:4, minHeight:36}}>
            {form.recipients.length === 0 && <span style={{color:'var(--text-3)', fontSize:11}}>Seleziona almeno un destinatario</span>}
            {form.recipients.map(r => (
              <Chip key={r}>{r} <span style={{marginLeft:4, cursor:'pointer', color:'var(--text-3)'}} onClick={()=>set('recipients', form.recipients.filter(x=>x!==r))}>×</span></Chip>
            ))}
            {['owner','delegate_chain','escalation_role','PM_CAPEX','BUYER','PROCUREMENT_MGR','CONTROLLER','CFO','CEO','QA_MGR','LEGAL','VENDOR','BU_DIRECTOR'].filter(x => !form.recipients.includes(x)).map(x => (
              <button key={x} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('recipients', [...form.recipients, x])}>+ {x}</button>
            ))}
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Canali <span style={{color:'var(--err)'}}>*</span></div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:8, border:'1px solid var(--line)', borderRadius:4, minHeight:36}}>
            {form.channels.map(c => (
              <Chip key={c} kind="info">{c} <span style={{marginLeft:4, cursor:'pointer', color:'var(--text-3)'}} onClick={()=>set('channels', form.channels.filter(x=>x!==c))}>×</span></Chip>
            ))}
            {['email','in_app','teams','push','sms'].filter(x => !form.channels.includes(x)).map(x => (
              <button key={x} className="btn sm ghost" style={{fontSize:10, padding:'2px 6px'}} onClick={()=>set('channels', [...form.channels, x])}>+ {x}</button>
            ))}
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Template & frequenza</div>
          <div className="grid grid-3">
            <div className="field"><label>Template</label>
              <select value={form.template} onChange={e=>set('template', e.target.value)}>
                <option value="">— usa default evento —</option>
                <option value="TPL_N_RDA_SUB">TPL_N_RDA_SUB · RdA sottomessa</option>
                <option value="TPL_N_RDA_APR">TPL_N_RDA_APR · RdA approvata</option>
                <option value="TPL_N_SLA_W">TPL_N_SLA_W · SLA warn</option>
                <option value="TPL_N_SLA_B">TPL_N_SLA_B · SLA breach</option>
                <option value="TPL_N_SLIP">TPL_N_SLIP · Slittamento</option>
                <option value="TPL_N_EXP">TPL_N_EXP · Doc scadenza</option>
                <option value="TPL_N_AI">TPL_N_AI · Anomalia AI</option>
                <option value="TPL_N_CPI">TPL_N_CPI · CPI warn</option>
                <option value="TPL_N_VCERT">TPL_N_VCERT · Cert vendor</option>
              </select>
            </div>
            <div className="field"><label>Frequenza max</label>
              <select value={form.maxFrequency} onChange={e=>set('maxFrequency', e.target.value)}>
                <option value="realtime">realtime</option>
                <option value="hourly_digest">hourly_digest</option>
                <option value="daily_digest">daily_digest</option>
                <option value="weekly_digest">weekly_digest</option>
              </select>
            </div>
            <div className="field"><label>Quiet hours</label>
              <input value={form.quietHours} onChange={e=>set('quietHours', e.target.value)} placeholder="es. 20:00-07:00"/>
            </div>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Stato</div>
          <select value={form.active?'on':'off'} onChange={e=>set('active', e.target.value==='on')} style={{maxWidth:200}}>
            <option value="on">Attiva</option>
            <option value="off">Disattiva (draft)</option>
          </select>
        </div>

        {!valid && (
          <div style={{fontSize:10.5, color:'var(--err)', padding:'6px 8px', background:'var(--bg-2)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> Nome, evento, almeno 1 destinatario e almeno 1 canale sono obbligatori.
          </div>
        )}

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/notification-rules</code> persiste in DB con audit log automatico. Vedi <code>docs/notifications.md §4</code>.
        </div>
        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Notification Rule (PATCH /api/config/notification-rules/[id]).
function NotificationRuleDetailModal({ selRule, onClose, events }) {
  const { addNotificationRule, pushToast, user } = useStore();
  const serverRule = useFetchedDetail(selRule, (s) => `/api/config/notification-rules/${s.id}`);
  const baseRule = serverRule || selRule;

  const initial = React.useMemo(() => {
    if (!baseRule) return null;
    return {
      name: baseRule.name || '',
      eventCode: baseRule.eventCode || baseRule.event || '',
      recipients: Array.isArray(baseRule.recipients) ? [...baseRule.recipients] : [],
      channels: Array.isArray(baseRule.channels) ? [...baseRule.channels] : [],
      templateId: baseRule.templateId || baseRule.template || '',
      maxFrequency: baseRule.maxFrequency || 'realtime',
      quietHours: baseRule.quietHours || '',
      active: baseRule.active !== false,
    };
  }, [baseRule]);

  const buildBody = React.useCallback((f) => ({
    name: (f.name || '').trim(),
    eventCode: f.eventCode,
    recipients: f.recipients || [],
    channels: f.channels || [],
    templateId: (f.templateId || '').trim() ? (f.templateId || '').trim() : null,
    maxFrequency: f.maxFrequency,
    quietHours: (f.quietHours || '').trim() ? (f.quietHours || '').trim() : null,
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selRule ? `/api/config/notification-rules/${selRule.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addNotificationRule(json.data);
      pushToast({ title: `Regola ${json.data.name}`, desc: `Aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selRule) return <Modal open={false} onClose={onClose} title=""/>;

  const nameValid = (form.name || '').trim().length > 0;
  const recipientsValid = (form.recipients || []).length > 0;
  const channelsValid = (form.channels || []).length > 0;
  const eventValid = !!form.eventCode;
  const valid = nameValid && recipientsValid && channelsValid && eventValid;

  const FREQ_OPTIONS = ['realtime','hourly_digest','daily_digest','weekly_digest'];
  const CHANNEL_OPTIONS = ['email','in_app','sms','teams','webhook'];
  const RECIPIENT_OPTIONS = ['PM_CAPEX','BUYER','PROCUREMENT_MGR','CONTROLLER','CFO','CEO','QA_MGR','LEGAL','LEGAL_HEAD','BOARD','owner'];

  const toggleArr = (key, opts) => {
    const cur = form[key] || [];
    set(key, cur.includes(opts) ? cur.filter(x => x !== opts) : [...cur, opts]);
  };

  return (
    <Modal open={!!selRule} onClose={onClose} title={`Regola · ${selRule.name}`} size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div className="grid grid-2">
          <div className="field"><label>Nome</label>
            <input value={form.name || ''} onChange={e=>set('name', e.target.value)}/>
            {!nameValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
          </div>
          <div className="field"><label>Evento</label>
            <select value={form.eventCode || ''} onChange={e=>set('eventCode', e.target.value)}>
              <option value="">— seleziona —</option>
              {(events || []).map(e => <option key={e.id} value={e.code}>{e.code}</option>)}
            </select>
          </div>
          <div className="field"><label>Frequenza max</label>
            <select value={form.maxFrequency || 'realtime'} onChange={e=>set('maxFrequency', e.target.value)}>
              {FREQ_OPTIONS.map(f => <option key={f} value={f}>{f}</option>)}
            </select>
          </div>
          <div className="field"><label>Quiet hours</label>
            <input value={form.quietHours || ''} onChange={e=>set('quietHours', e.target.value)} placeholder="es. 20:00-07:00" style={{fontFamily:'var(--font-mono)'}}/>
          </div>
          <div className="field" style={{gridColumn:'1 / -1'}}><label>Template ID (opzionale)</label>
            <input value={form.templateId || ''} onChange={e=>set('templateId', e.target.value)} style={{fontFamily:'var(--font-mono)'}}/>
          </div>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Destinatari</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:6, border:'1px solid var(--line)', borderRadius:4}}>
            {RECIPIENT_OPTIONS.map(r => {
              const on = (form.recipients || []).includes(r);
              return (
                <button key={r} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={()=>toggleArr('recipients', r)} style={{fontSize:10}}>
                  {on ? '✓ ' : '+ '}{r}
                </button>
              );
            })}
          </div>
          {!recipientsValid && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}>Almeno 1 destinatario</div>}
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Canali</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:6, border:'1px solid var(--line)', borderRadius:4}}>
            {CHANNEL_OPTIONS.map(c => {
              const on = (form.channels || []).includes(c);
              return (
                <button key={c} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={()=>toggleArr('channels', c)} style={{fontSize:10}}>
                  {on ? '✓ ' : '+ '}{c}
                </button>
              );
            })}
          </div>
          {!channelsValid && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}>Almeno 1 canale</div>}
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Regola attiva
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/notification-rules/{selRule.id}</code> persisterà con audit automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Notification Event (PATCH /api/config/notification-events/[id]).
function NotificationEventDetailModal({ selEvent, onClose }) {
  const { addNotificationEvent, pushToast, user } = useStore();
  const serverEvt = useFetchedDetail(selEvent, (s) => `/api/config/notification-events/${s.id}`);
  const baseEvt = serverEvt || selEvent;

  const initial = React.useMemo(() => {
    if (!baseEvt) return null;
    return {
      code: baseEvt.code || '',
      description: baseEvt.description || '',
      category: baseEvt.category || 'Generale',
      defaultSeverity: baseEvt.defaultSeverity || 'info',
      payloadFields: Array.isArray(baseEvt.payloadFields) ? [...baseEvt.payloadFields] : [],
      payloadFieldsStr: Array.isArray(baseEvt.payloadFields) ? baseEvt.payloadFields.join(', ') : '',
      active: baseEvt.active !== false,
    };
  }, [baseEvt]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    description: (f.description || '').trim() ? (f.description || '').trim() : null,
    category: (f.category || 'Generale').trim(),
    defaultSeverity: f.defaultSeverity || 'info',
    payloadFields: (f.payloadFieldsStr || '').split(',').map(x => x.trim()).filter(Boolean),
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selEvent ? `/api/config/notification-events/${selEvent.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addNotificationEvent(json.data);
      pushToast({ title: `Evento ${json.data.code}`, desc: `Aggiornato. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selEvent) return <Modal open={false} onClose={onClose} title=""/>;

  const codeValid = !form.code || /^[a-z][a-z0-9_.]*$/.test(form.code);
  const valid = codeValid && (form.code || '').length > 0;

  const SEVERITIES = ['debug','info','warn','error','critical'];

  return (
    <Modal open={!!selEvent} onClose={onClose} title={`Evento · ${selEvent.code}`} size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div className="grid grid-2">
          <div className="field"><label>Code</label>
            <input value={form.code || ''} onChange={e=>set('code', e.target.value.toLowerCase().replace(/[^a-z0-9_.]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
            {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code: a-z, 0-9, _, .</div>}
          </div>
          <div className="field"><label>Categoria</label>
            <input value={form.category || ''} onChange={e=>set('category', e.target.value)}/>
          </div>
          <div className="field"><label>Default severity</label>
            <select value={form.defaultSeverity || 'info'} onChange={e=>set('defaultSeverity', e.target.value)}>
              {SEVERITIES.map(s => <option key={s} value={s}>{s}</option>)}
            </select>
          </div>
        </div>

        <div className="field"><label>Descrizione</label>
          <textarea rows={2} value={form.description || ''} onChange={e=>set('description', e.target.value)} placeholder="(opzionale)"/>
        </div>

        <div className="field"><label>Payload fields (csv)</label>
          <input value={form.payloadFieldsStr || ''} onChange={e=>set('payloadFieldsStr', e.target.value)} placeholder="es. rdaId, vendorId, amount" style={{fontFamily:'var(--font-mono)', fontSize:11.5}}/>
          <div style={{fontSize:10, color:'var(--text-3)', marginTop:2}}>Lista campi disponibili nel payload. Separati da virgola.</div>
        </div>

        <div>
          <label className="row" style={{gap:6, fontSize:11.5}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Evento attivo
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/notification-events/{selEvent.id}</code> persisterà con audit automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

function NewNotificationEventModal({ open, onClose }) {
  const { pushToast, user } = useStore();
  const initial = () => ({
    code: '', description: '', category: 'RdA',
    payloadFields: [],
  });
  const [form, setForm] = React.useState(initial);
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => { if (open) { setForm(initial()); setServerError(null); } }, [open]);
  const [newField, setNewField] = React.useState('');
  const set = (k, v) => setForm(f => ({...f, [k]: v}));
  const valid = form.code.trim() && form.description.trim() && /^[a-z][a-z0-9_.]*$/.test(form.code);

  async function handleSubmit() {
    if (!valid || saving) return;
    setSaving(true); setServerError(null);
    try {
      const res = await fetch('/api/config/notification-events', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({
          code: form.code, description: form.description.trim(),
          category: form.category, payloadFields: form.payloadFields,
        }),
      });
      const json = await res.json().catch(()=>({}));
      if (!res.ok) {
        setServerError(json?.error === 'validation_error' ? `Validazione fallita: ${(json.issues||[]).map(i=>i.message).join(' · ')}` : (json?.error || `HTTP ${res.status}`));
        return;
      }
      if (json?.data) pushToast({ title: `${json.data.code}`, desc: 'Evento notifica salvato in DB. Audit registrato.', tone: 'ok' });
      onClose();
    } catch (e) { setServerError(String(e?.message||e)); }
    finally { setSaving(false); }
  }

  return (
    <Modal open={open} onClose={onClose} title="Nuovo evento di notifica" size="md"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving} onClick={handleSubmit}>{saving ? 'Salvataggio…' : 'Crea evento'}</Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        <div style={{fontSize:11.5, color:'var(--text-2)', lineHeight:1.5}}>
          Registra un nuovo evento di dominio che il sistema può pubblicare. Una volta creato, può essere consumato da <strong>regole di notifica</strong> e <strong>regole di escalation</strong>.
        </div>

        <div className="grid grid-2">
          <div className="field"><label>Code <span style={{color:'var(--err)'}}>*</span></label>
            <input value={form.code} onChange={e=>set('code', e.target.value.toLowerCase())} placeholder="es. rda.resubmitted" style={{fontFamily:'var(--font-mono)'}}/>
          </div>
          <div className="field"><label>Categoria</label>
            <select value={form.category} onChange={e=>set('category', e.target.value)}>
              <option>RdA</option><option>SLA</option><option>Progetto</option><option>Vendor</option><option>Documenti</option><option>AI</option><option>Sistema</option>
            </select>
          </div>
        </div>
        <div className="field"><label>Descrizione <span style={{color:'var(--err)'}}>*</span></label>
          <input value={form.description} onChange={e=>set('description', e.target.value)} placeholder="Descrizione breve dell'evento"/>
        </div>

        <div>
          <div className="eyebrow" style={{marginBottom:6}}>Payload fields</div>
          <div style={{display:'flex', gap:4, flexWrap:'wrap', padding:8, border:'1px solid var(--line)', borderRadius:4, minHeight:36, marginBottom:6}}>
            {form.payloadFields.length === 0 && <span style={{color:'var(--text-3)', fontSize:11}}>Nessun field definito</span>}
            {form.payloadFields.map(f => (
              <Chip key={f}>{f} <span style={{marginLeft:4, cursor:'pointer', color:'var(--text-3)'}} onClick={()=>set('payloadFields', form.payloadFields.filter(x=>x!==f))}>×</span></Chip>
            ))}
          </div>
          <div className="row" style={{gap:6}}>
            <input value={newField} onChange={e=>setNewField(e.target.value)} placeholder="es. rda.id, owner, amount" style={{fontFamily:'var(--font-mono)', fontSize:11.5, flex:1}}/>
            <Btn variant="ghost" size="sm" onClick={()=>{ if(newField.trim()){ set('payloadFields', [...form.payloadFields, newField.trim()]); setNewField(''); }}}><Icon name="plus" size={10}/> Aggiungi</Btn>
          </div>
        </div>

        {!valid && form.code && (
          <div style={{fontSize:10.5, color:'var(--err)'}}>
            <Icon name="alert-triangle" size={10}/> Code deve essere lowercase con solo [a-z0-9_.], es. <code>rda.my_event</code>. Descrizione obbligatoria.
          </div>
        )}

        <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
          <Icon name="info" size={10}/> <code>POST /api/config/notification-events</code> persiste in DB con audit log. Dopo la publish, l'evento può essere emesso dal codice con <code>events.publish(code, payload)</code>.
        </div>
        {serverError && (
          <div style={{fontSize:11, color:'var(--err)', padding:'8px 10px', background:'rgba(239,68,68,0.06)', border:'1px solid var(--err)', borderRadius:4}}>
            <Icon name="alert-triangle" size={10}/> {serverError}
          </div>
        )}
      </div>
    </Modal>
  );
}

// -------------- AI CONFIG --------------
function CustAI() {
  const { seedCustom } = useStore();
  const agents = seedCustom.AI_AGENTS || [];
  const policy = seedCustom.AI_POLICY || {};
  const [sel, setSel] = React.useState(null);

  return (
    <>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 10, marginBottom: 16 }}>
        <div className="card" style={{padding:12}}>
          <div className="eyebrow">Modello default</div>
          <div style={{fontSize:14, fontWeight:500, marginTop:4}}>{policy.defaultModel}</div>
        </div>
        <div className="card" style={{padding:12}}>
          <div className="eyebrow">Fallback</div>
          <div style={{fontSize:14, marginTop:4}}>{policy.fallbackModel}</div>
        </div>
        <div className="card" style={{padding:12}}>
          <div className="eyebrow">Data residency</div>
          <div style={{fontSize:14, marginTop:4}}><Chip kind="ok">{policy.dataResidency}</Chip></div>
        </div>
        <div className="card" style={{padding:12}}>
          <div className="eyebrow">PII redaction</div>
          <div style={{fontSize:14, marginTop:4}}>{policy.piiRedaction ? <Chip kind="ok">on</Chip> : <Chip kind="err">off</Chip>}</div>
        </div>
      </div>

      <div className="eyebrow" style={{marginBottom:6}}>Agenti AI ({agents.length})</div>
      <table className="tbl dense">
        <thead><tr>
          <th style={{width:120}}>Code</th><th>Nome</th>
          <th style={{width:160}}>Modello</th>
          <th style={{width:90,textAlign:'right'}}>Temp</th>
          <th>Tools</th>
          <th style={{width:110}}>Guardrails</th>
          <th style={{width:100,textAlign:'center'}}>Autonomia</th>
          <th style={{width:70,textAlign:'center'}}>Stato</th>
        </tr></thead>
        <tbody>{agents.map(a => (
          <tr key={a.id} className="clickable" onClick={()=>setSel(a)}>
            <td><Chip kind="ai">{a.code}</Chip></td>
            <td style={{fontWeight:500}}>{a.name}</td>
            <td style={{fontSize:11.5}}>{a.model}</td>
            <td className="num mono">{a.temperature}</td>
            <td style={{fontSize:11}}><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(a.tools||[]).slice(0,3).map(t => <Chip key={t}>{t}</Chip>)}{(a.tools||[]).length>3 && <Chip>+{(a.tools||[]).length-3}</Chip>}</div></td>
            <td style={{fontSize:11}}>{(a.guardrails||[]).length} regole</td>
            <td style={{textAlign:'center'}}><Chip kind={a.autonomy==='auto'?'warn':'info'}>{a.autonomy}</Chip></td>
            <td style={{textAlign:'center'}}><Chip kind={a.active?'ok':''} dot>{a.active?'on':'off'}</Chip></td>
          </tr>
        ))}</tbody>
      </table>

      <Modal open={!!sel} onClose={()=>setSel(null)} title={sel ? `Agente · ${sel.name}` : ''} size="lg" footer={<><Btn variant="ghost" size="sm" onClick={()=>setSel(null)}>Chiudi</Btn><Btn variant="ghost" size="sm"><Icon name="eye" size={11}/> Test prompt</Btn><Btn variant="primary" size="sm">Salva</Btn></>}>
        {sel && (
          <div className="col" style={{ gap: 14 }}>
            <div className="grid grid-3">
              <div className="field"><label>Modello</label><input defaultValue={sel.model}/></div>
              <div className="field"><label>Temperature</label><input type="number" step="0.1" defaultValue={sel.temperature}/></div>
              <div className="field"><label>Autonomia</label><select defaultValue={sel.autonomy}><option>suggest</option><option>assist</option><option>auto</option></select></div>
            </div>
            <div className="field"><label>System prompt</label><textarea rows={10} defaultValue={sel.systemPrompt} style={{fontFamily:'var(--font-mono)', fontSize:11.5}}/></div>
            <div>
              <div className="eyebrow" style={{marginBottom:6}}>Tools abilitati</div>
              <div style={{display:'flex', gap:4, flexWrap:'wrap'}}>{(sel.tools||[]).map(t => <Chip key={t} kind="ai">{t}</Chip>)}</div>
            </div>
            <div>
              <div className="eyebrow" style={{marginBottom:6}}>Guardrails</div>
              <div style={{display:'grid', gap:4}}>
                {(sel.guardrails||[]).map((g,i) => (
                  <div key={i} className="row" style={{gap:8, padding:'6px 10px', background:'var(--bg-2)', borderRadius:6, fontSize:11.5}}>
                    <Icon name="lock" size={11}/><span>{g}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}
      </Modal>
    </>
  );
}

// -------------- TEMPLATES --------------
function CustTemplates() {
  const { seedCustom, extras } = useStore();
  // FASE 3a.15 batch: dedup extras+seed.
  const templates = React.useMemo(() => {
    const seedList = seedCustom.TEMPLATES || [];
    const extList = extras?.templatesExt || [];
    const seenIds = new Set();
    const out = [];
    for (const t of [...extList, ...seedList]) {
      if (!t?.id || seenIds.has(t.id)) continue;
      seenIds.add(t.id);
      out.push(t);
    }
    return out;
  }, [seedCustom.TEMPLATES, extras?.templatesExt]);
  const clauses = React.useMemo(() => {
    const seedList = seedCustom.CLAUSES || [];
    const extList = extras?.clausesExt || [];
    const seenIds = new Set();
    const out = [];
    for (const c of [...extList, ...seedList]) {
      if (!c?.id || seenIds.has(c.id)) continue;
      seenIds.add(c.id);
      out.push(c);
    }
    return out;
  }, [seedCustom.CLAUSES, extras?.clausesExt]);
  const [tab, setTab] = React.useState('templates');
  const [selected, setSelected] = React.useState(null); // template object
  const [selClause, setSelClause] = React.useState(null);
  const [selTplMeta, setSelTplMeta] = React.useState(null); // template per meta-edit
  const [showNewTpl, setShowNewTpl] = React.useState(false);
  const [showNewClause, setShowNewClause] = React.useState(false);
  const { addTemplate, addClause } = useStore();

  return (
    <>
      <div className="row" style={{ gap: 2, marginBottom: 12, background: 'var(--bg-2)', padding: 2, borderRadius: 6, alignItems:'center' }}>
        <button className={`btn sm ${tab==='templates'?'primary':'ghost'}`} onClick={()=>setTab('templates')}>Template ({templates.length})</button>
        <button className={`btn sm ${tab==='clauses'?'primary':'ghost'}`} onClick={()=>setTab('clauses')}>Clausole ({clauses.length})</button>
        <span className="spacer"/>
        <ConfigWriteBtn onClick={() => tab === 'templates' ? setShowNewTpl(true) : setShowNewClause(true)}>
          <Icon name="plus" size={11}/> {tab === 'templates' ? 'Nuovo template' : 'Nuova clausola'}
        </ConfigWriteBtn>
      </div>

      {tab === 'templates' ? (
        <table className="tbl dense">
          <thead><tr>
            <th style={{width:110}}>Code</th><th>Nome</th>
            <th style={{width:100}}>Tipo</th>
            <th>Variabili</th>
            <th style={{width:110}}>Locale</th>
            <th style={{width:70,textAlign:'center'}}>Ver.</th>
            <th style={{width:90,textAlign:'center'}}>AI assist</th>
          </tr></thead>
          <tbody>{templates.map(t => (
            <tr key={t.id} className="clickable" onClick={() => setSelected(t)}>
              <td className="mono" style={{fontSize:11}}>{t.code}</td>
              <td style={{fontWeight:500}}>{t.name}</td>
              <td><Chip>{t.type}</Chip></td>
              <td style={{fontSize:11, color:'var(--text-2)'}}>{(t.variables||[]).slice(0,4).join(', ')}{(t.variables||[]).length>4 && ' +' + ((t.variables||[]).length-4)}</td>
              <td style={{fontSize:11}}>{(t.locales||[]).join(', ')}</td>
              <td className="mono num">v{t.version}</td>
              <td style={{textAlign:'center'}}>{t.aiAssist ? <Chip kind="ai" dot>on</Chip> : <Chip>off</Chip>}</td>
            </tr>
          ))}</tbody>
        </table>
      ) : (
        <table className="tbl dense">
          <thead><tr>
            <th style={{width:110}}>Code</th><th>Titolo</th>
            <th style={{width:110}}>Tipo</th>
            <th>Applicabile a</th>
            <th style={{width:90,textAlign:'center'}}>Req.</th>
            <th style={{width:90,textAlign:'center'}}>Negoziabile</th>
          </tr></thead>
          <tbody>{clauses.map(c => (
            <tr key={c.id} className="clickable" onClick={()=>setSelClause(c)}>
              <td className="mono" style={{fontSize:11}}>{c.code}</td>
              <td style={{fontWeight:500}}>{c.title}</td>
              <td><Chip>{c.type}</Chip></td>
              <td style={{fontSize:11, color:'var(--text-2)'}}>{(c.appliesTo||[]).join(', ')}</td>
              <td style={{textAlign:'center'}}>{c.required ? <Chip kind="err">sì</Chip> : <Chip>opz</Chip>}</td>
              <td style={{textAlign:'center'}}>{c.negotiable ? <Chip kind="ok">sì</Chip> : <Chip kind="err">no</Chip>}</td>
            </tr>
          ))}</tbody>
        </table>
      )}

      {selected && <TemplateDetailModal tpl={selected} onClose={() => setSelected(null)} onEditMeta={() => { setSelTplMeta(selected); setSelected(null); }} />}
      <TemplateMetaModal selTpl={selTplMeta} onClose={()=>setSelTplMeta(null)}/>
      <ClauseDetailModal selClause={selClause} onClose={()=>setSelClause(null)}/>
      {/* P2 sessione 34 — modal create live */}
      <NewTemplateModalLive
        open={showNewTpl}
        onClose={() => setShowNewTpl(false)}
        clauses={clauses}
        onCreated={(row) => addTemplate && addTemplate(row)}
      />
      <NewClauseModalLive
        open={showNewClause}
        onClose={() => setShowNewClause(false)}
        onCreated={(row) => addClause && addClause(row)}
      />
    </>
  );
}

/**
 * Helper user-friendly error messages: mappa codici tecnici a messaggi business.
 * Sprint 3.5: nascondiamo dettagli tecnici provider/model dall'utente CFO/PM.
 */
function mapAiErrorToUser(errorCode, detail) {
  if (errorCode === 'no_ai_key') return 'Nessuna chiave AI configurata. Vai in Impostazioni → Provider AI per impostarne una.';
  if (errorCode === 'primary_provider_no_key') return 'Il provider AI selezionato non ha una chiave configurata. Verifica le Impostazioni.';
  if (errorCode === 'extract_failed') return 'Non è stato possibile leggere il documento. Verifica che il file sia un PDF testuale o riprova più tardi.';
  if (errorCode === 'doc_no_file' || errorCode === 'source_doc_no_file') return 'Il documento selezionato non ha un file allegato.';
  if (errorCode === 'file_gone') return 'Il file non è più disponibile nello storage. Ricaricalo per riprovare.';
  if (errorCode === 'no_variables') return 'Questo template non ha variabili da compilare con AI.';
  if (errorCode === 'template_not_found') return 'Template non trovato.';
  if (errorCode === 'source_doc_not_found') return 'Documento sorgente non trovato.';
  if (errorCode === 'validation_error') return 'Dati non validi. Verifica i campi del form.';
  return detail || 'Operazione AI non riuscita. Riprova più tardi.';
}

/**
 * FASE 2c.4 (Sprint 3 cliente): Template detail modal con AI fill + render preview.
 * Sprint 3.5: dispatcher multi-provider trasparente, error message user-friendly,
 * niente nome modello visibile a utente business.
 */
function TemplateDetailModal({ tpl, onClose, onEditMeta }) {
  const { user, pushToast } = useStore();
  const [variables, setVariables] = React.useState(() => {
    const initial = {};
    (tpl.variables || []).forEach(v => { initial[v] = ''; });
    return initial;
  });
  const [rendering, setRendering] = React.useState(false);
  const [generatingPdf, setGeneratingPdf] = React.useState(false);
  const [aiFillOpen, setAiFillOpen] = React.useState(false);
  const [aiFillResult, setAiFillResult] = React.useState(null); // { values, render, extractionSource, fallback }

  const setVar = (name, val) => setVariables(prev => ({ ...prev, [name]: val }));

  async function handleGeneratePdf() {
    setGeneratingPdf(true);
    try {
      const res = await fetch(`/api/templates/${encodeURIComponent(tpl.id)}/render`, {
        method: 'POST',
        headers: { 'content-type': 'application/json', 'X-Actor-Persona-Id': user?.id || '' },
        body: JSON.stringify({ variables, format: 'pdf' }),
      });
      if (!res.ok) {
        const j = await res.json().catch(() => ({}));
        throw new Error(j?.detail || j?.error || `HTTP ${res.status}`);
      }
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      // Apri in nuova tab + offre download via Content-Disposition
      window.open(url, '_blank');
      setTimeout(() => URL.revokeObjectURL(url), 60000);
      const sizeKB = (blob.size / 1024).toFixed(1);
      pushToast({ title: 'PDF generato', desc: `${sizeKB} KB · pronto per firma`, tone: 'ok' });
    } catch (err) {
      pushToast({ title: 'Generazione PDF non riuscita', desc: err?.message?.slice(0, 240) || 'errore', tone: 'err' });
    } finally {
      setGeneratingPdf(false);
    }
  }

  async function handlePreview() {
    setRendering(true);
    try {
      const res = await fetch(`/api/templates/${encodeURIComponent(tpl.id)}/render`, {
        method: 'POST',
        headers: { 'content-type': 'application/json', 'X-Actor-Persona-Id': user?.id || '' },
        body: JSON.stringify({ variables, format: 'html' }),
      });
      if (!res.ok) {
        const j = await res.json().catch(() => ({}));
        throw new Error(j?.detail || j?.error || `HTTP ${res.status}`);
      }
      const html = await res.text();
      const blob = new Blob([html], { type: 'text/html' });
      const url = URL.createObjectURL(blob);
      window.open(url, '_blank');
      // Revoke dopo qualche secondo
      setTimeout(() => URL.revokeObjectURL(url), 30000);
      pushToast({ title: 'Anteprima aperta', desc: 'Usa Ctrl+P nel browser per salvare come PDF', tone: 'ok' });
    } catch (err) {
      pushToast({ title: 'Errore anteprima', desc: err?.message?.slice(0, 200) || 'errore', tone: 'err' });
    } finally {
      setRendering(false);
    }
  }

  return (
    <Modal
      open
      onClose={onClose}
      title={`${tpl.code} · ${tpl.name}`}
      size="lg"
      footer={
        <>
          <Btn variant="ghost" size="sm" onClick={onClose}>Chiudi</Btn>
          {onEditMeta && (
            <Btn variant="ghost" size="sm" onClick={onEditMeta}>
              <Icon name="settings" size={12}/> Modifica metadati
            </Btn>
          )}
          <Btn variant="ghost" size="sm" onClick={() => setAiFillOpen(true)}>
            <Icon name="sparkle" size={12}/> Compila con AI
          </Btn>
          <Btn variant="ghost" size="sm" onClick={handlePreview} disabled={rendering}>
            {rendering ? 'Rendering…' : <><Icon name="eye" size={12}/> Anteprima HTML</>}
          </Btn>
          <Btn variant="primary" size="sm" onClick={handleGeneratePdf} disabled={generatingPdf}>
            {generatingPdf ? 'Genero PDF…' : <><Icon name="download" size={12}/> Genera PDF</>}
          </Btn>
        </>
      }
    >
      <div className="col" style={{ gap: 14 }}>
        <div className="grid grid-2" style={{ gap: 14 }}>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Variabili template</div>
            {(tpl.variables || []).length === 0 ? (
              <div style={{ fontSize: 12, color: 'var(--text-3)', padding: 10, background: 'var(--bg-2)', borderRadius: 6 }}>
                Questo template non dichiara variabili.
              </div>
            ) : (
              <div className="col" style={{ gap: 8 }}>
                {(tpl.variables || []).map(v => (
                  <div key={v} className="field">
                    <label style={{ fontSize: 10.5 }}><code>{`{{${v}}}`}</code></label>
                    <input
                      value={variables[v] || ''}
                      onChange={e => setVar(v, e.target.value)}
                      placeholder={`Valore per ${v}`}
                    />
                  </div>
                ))}
              </div>
            )}
          </div>
          <div>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Configurazione</div>
            <div style={{ fontSize: 12, lineHeight: 1.6 }}>
              <div><strong>Tipo:</strong> {tpl.type}</div>
              <div><strong>Locales:</strong> {(tpl.locales || []).join(', ')}</div>
              <div><strong>AI assist:</strong> {tpl.aiAssist ? '✓ abilitato' : '— disabilitato'}</div>
              <div><strong>Clausole linkate:</strong> {(tpl.clauses || tpl.clauseIds || []).length}</div>
              <div><strong>Versione:</strong> v{tpl.version}</div>
            </div>
            {aiFillResult && (
              <div style={{ marginTop: 14, padding: 10, background: 'var(--bg-2)', borderRadius: 6, fontSize: 11.5 }}>
                <div className="eyebrow" style={{ marginBottom: 4 }}>Ultima compilazione AI</div>
                <div className="row" style={{ gap: 6, alignItems: 'center' }}>
                  <Chip kind="ai"><Icon name="sparkle" size={10}/> AI Copilot</Chip>
                  {aiFillResult.attempts > 1 && (
                    <Chip kind="info" title={`Riuscita al tentativo ${aiFillResult.attempts}`}>
                      retry × {aiFillResult.attempts}
                    </Chip>
                  )}
                </div>
                <div style={{ marginTop: 4 }}>
                  {Object.keys(aiFillResult.values || {}).length} variabili compilate · {(aiFillResult.totalLatencyMs / 1000).toFixed(1)}s
                </div>
              </div>
            )}
          </div>
        </div>

        <details>
          <summary style={{ cursor: 'pointer', fontSize: 12, color: 'var(--text-2)' }}>Body markdown del template ({(tpl.bodyMarkdown || '').length} char)</summary>
          <pre style={{ marginTop: 8, padding: 10, background: 'var(--bg-2)', borderRadius: 4, fontSize: 11, maxHeight: 240, overflow: 'auto' }}>
            {tpl.bodyMarkdown || '(template senza body)'}
          </pre>
        </details>
      </div>

      {aiFillOpen && (
        <AiFillSourceModal
          tpl={tpl}
          onClose={() => setAiFillOpen(false)}
          onResult={(r) => {
            setVariables(prev => ({ ...prev, ...(r.values || {}) }));
            setAiFillResult({
              values: r.values,
              attempts: r.attempts,
              totalLatencyMs: r.latencyMs?.total ?? 0,
            });
            setAiFillOpen(false);
          }}
        />
      )}
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Template metadata (PATCH /api/config/templates/[id]).
// Separato da TemplateDetailModal (che è "executor" per AI fill + render PDF).
function TemplateMetaModal({ selTpl, onClose }) {
  const { addTemplate, pushToast, user } = useStore();
  const serverTpl = useFetchedDetail(selTpl, (s) => `/api/config/templates/${s.id}`);
  const baseTpl = serverTpl || selTpl;

  const initial = React.useMemo(() => {
    if (!baseTpl) return null;
    return {
      code: baseTpl.code || '',
      name: baseTpl.name || '',
      type: baseTpl.type || 'po',
      variablesStr: Array.isArray(baseTpl.variables) ? baseTpl.variables.join(', ') : '',
      localesStr: Array.isArray(baseTpl.locales) ? baseTpl.locales.join(', ') : 'it-IT',
      aiAssist: !!baseTpl.aiAssist,
      legalEntityId: baseTpl.legalEntityId || (baseTpl.legalEntity && baseTpl.legalEntity !== '*' ? baseTpl.legalEntity : ''),
      bodyMarkdown: baseTpl.bodyMarkdown || '',
      active: baseTpl.active !== false,
    };
  }, [baseTpl]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    name: (f.name || '').trim(),
    type: f.type,
    variables: (f.variablesStr || '').split(',').map(x => x.trim()).filter(Boolean),
    locales: (f.localesStr || 'it-IT').split(',').map(x => x.trim()).filter(Boolean),
    aiAssist: !!f.aiAssist,
    legalEntityId: (f.legalEntityId || '').trim() ? (f.legalEntityId || '').trim() : null,
    bodyMarkdown: (f.bodyMarkdown || '').trim() ? f.bodyMarkdown : null,
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selTpl ? `/api/config/templates/${selTpl.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addTemplate(json.data);
      pushToast({ title: `Template ${json.data.code}`, desc: `Aggiornato. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selTpl) return <Modal open={false} onClose={onClose} title=""/>;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_-]{1,63}$/.test(form.code);
  const nameValid = (form.name || '').trim().length > 0;
  const typeValid = (form.type || '').trim().length > 0;
  const valid = codeValid && nameValid && typeValid;

  const TYPE_OPTIONS = ['po','contract','rda_summary','vendor_qualification','approval_letter','notification','generic'];

  return (
    <Modal open={!!selTpl} onClose={onClose} title={`Metadati template · ${selTpl.code}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div className="grid grid-3">
          <div className="field"><label>Code</label>
            <input value={form.code || ''} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
            {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code non valido</div>}
          </div>
          <div className="field" style={{gridColumn:'span 2'}}><label>Nome</label>
            <input value={form.name || ''} onChange={e=>set('name', e.target.value)}/>
          </div>
          <div className="field"><label>Tipo</label>
            <select value={form.type || 'po'} onChange={e=>set('type', e.target.value)}>
              {TYPE_OPTIONS.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
          </div>
          <div className="field"><label>Locales (csv)</label>
            <input value={form.localesStr || ''} onChange={e=>set('localesStr', e.target.value)} placeholder="it-IT, en-US" style={{fontFamily:'var(--font-mono)'}}/>
          </div>
          <div className="field"><label>Legal Entity (opzionale)</label>
            <input value={form.legalEntityId || ''} onChange={e=>set('legalEntityId', e.target.value)} placeholder="LE_IT (vuoto = global)" style={{fontFamily:'var(--font-mono)'}}/>
          </div>
        </div>

        <div className="field"><label>Variabili template (csv)</label>
          <input value={form.variablesStr || ''} onChange={e=>set('variablesStr', e.target.value)} placeholder="vendor.name, rda.amount, project.code" style={{fontFamily:'var(--font-mono)', fontSize:11.5}}/>
          <div style={{fontSize:10, color:'var(--text-3)', marginTop:2}}>Variabili che il body markdown può referenziare via <code>{'{{var}}'}</code>.</div>
        </div>

        <div className="field"><label>Body markdown (opzionale)</label>
          <textarea rows={6} value={form.bodyMarkdown || ''} onChange={e=>set('bodyMarkdown', e.target.value)} placeholder="# Titolo&#10;Corpo del template..." style={{fontFamily:'var(--font-mono)', fontSize:11}}/>
        </div>

        <div className="row" style={{gap:14, fontSize:11.5}}>
          <label className="row" style={{gap:6}}>
            <input type="checkbox" checked={!!form.aiAssist} onChange={e=>set('aiAssist', e.target.checked)}/>
            AI assist abilitato
          </label>
          <label className="row" style={{gap:6}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Template attivo
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/templates/{selTpl.id}</code> persisterà con audit automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

// FASE 3a.15 batch: detail modal editable Clause (PATCH /api/config/clauses/[id]).
function ClauseDetailModal({ selClause, onClose }) {
  const { addClause, pushToast, user } = useStore();
  const serverClause = useFetchedDetail(selClause, (s) => `/api/config/clauses/${s.id}`);
  const baseClause = serverClause || selClause;

  const initial = React.useMemo(() => {
    if (!baseClause) return null;
    return {
      code: baseClause.code || '',
      title: baseClause.title || '',
      type: baseClause.type || 'standard',
      appliesToStr: Array.isArray(baseClause.appliesTo) ? baseClause.appliesTo.join(', ') : '',
      required: !!baseClause.required,
      negotiable: baseClause.negotiable !== false,
      category: baseClause.category || '',
      language: baseClause.language || 'it',
      tagsStr: Array.isArray(baseClause.tags) ? baseClause.tags.join(', ') : '',
      bodyMarkdown: baseClause.bodyMarkdown || '',
      active: baseClause.active !== false,
    };
  }, [baseClause]);

  const buildBody = React.useCallback((f) => ({
    code: f.code,
    title: (f.title || '').trim(),
    type: f.type,
    appliesTo: (f.appliesToStr || '').split(',').map(x => x.trim()).filter(Boolean),
    required: !!f.required,
    negotiable: !!f.negotiable,
    category: (f.category || '').trim() ? (f.category || '').trim() : null,
    language: f.language,
    tags: (f.tagsStr || '').split(',').map(x => x.trim()).filter(Boolean),
    bodyMarkdown: (f.bodyMarkdown || '').trim() ? f.bodyMarkdown : null,
    active: !!f.active,
  }), []);

  const { form, set, isDirty, saving, serverError, save } = useEditableEntity(initial || {}, {
    url: () => selClause ? `/api/config/clauses/${selClause.id}` : null,
    actorId: user?.id,
    buildBody,
    onSaved: (json) => {
      addClause(json.data);
      pushToast({ title: `Clausola ${json.data.code}`, desc: `Aggiornata. ${json.changed ? 'Audit registrato.' : 'Nessuna modifica server.'}`, tone: 'ok' });
      onClose();
    },
  });

  if (!selClause) return <Modal open={false} onClose={onClose} title=""/>;

  const codeValid = !form.code || /^[A-Z][A-Z0-9_-]{1,63}$/.test(form.code);
  const titleValid = (form.title || '').trim().length > 0;
  const valid = codeValid && titleValid;

  const TYPE_OPTIONS = ['standard','warranty','penalty','confidentiality','liability','termination','custom'];
  const LANG_OPTIONS = ['it','en','fr','de','es'];

  return (
    <Modal open={!!selClause} onClose={onClose} title={`Clausola · ${selClause.code}`} size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="primary" size="sm" disabled={!valid || saving || !isDirty} onClick={save}>
          {saving ? 'Salvataggio…' : isDirty ? 'Salva modifiche' : 'Nessuna modifica'}
        </Btn>
      </>}>
      <div className="col" style={{gap:14}}>
        {serverError && (
          <div style={{ padding: '10px 12px', border: '1px solid var(--err, #c0392b)', borderRadius: 6, background: 'rgba(192,57,43,0.08)', color: 'var(--err, #c0392b)', fontSize: 12 }}>
            <strong>Errore salvataggio:</strong> {serverError}
          </div>
        )}

        <div className="grid grid-3">
          <div className="field"><label>Code</label>
            <input value={form.code || ''} onChange={e=>set('code', e.target.value.toUpperCase().replace(/[^A-Z0-9_-]/g,''))} style={{fontFamily:'var(--font-mono)'}}/>
            {form.code && !codeValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Code non valido</div>}
          </div>
          <div className="field" style={{gridColumn:'span 2'}}><label>Titolo</label>
            <input value={form.title || ''} onChange={e=>set('title', e.target.value)}/>
            {!titleValid && <div style={{fontSize:10.5, color:'var(--err)'}}>Obbligatorio</div>}
          </div>
          <div className="field"><label>Tipo</label>
            <select value={form.type || 'standard'} onChange={e=>set('type', e.target.value)}>
              {TYPE_OPTIONS.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
          </div>
          <div className="field"><label>Lingua</label>
            <select value={form.language || 'it'} onChange={e=>set('language', e.target.value)}>
              {LANG_OPTIONS.map(l => <option key={l} value={l}>{l}</option>)}
            </select>
          </div>
          <div className="field"><label>Categoria (opzionale)</label>
            <input value={form.category || ''} onChange={e=>set('category', e.target.value)} placeholder="es. legal, qualita"/>
          </div>
        </div>

        <div className="field"><label>Applicabile a (csv)</label>
          <input value={form.appliesToStr || ''} onChange={e=>set('appliesToStr', e.target.value)} placeholder="po, contract, vendor_agreement" style={{fontFamily:'var(--font-mono)', fontSize:11.5}}/>
        </div>

        <div className="field"><label>Tags (csv)</label>
          <input value={form.tagsStr || ''} onChange={e=>set('tagsStr', e.target.value)} placeholder="riservatezza, GDPR, default" style={{fontFamily:'var(--font-mono)', fontSize:11.5}}/>
        </div>

        <div className="field"><label>Body markdown (opzionale)</label>
          <textarea rows={6} value={form.bodyMarkdown || ''} onChange={e=>set('bodyMarkdown', e.target.value)} style={{fontFamily:'var(--font-mono)', fontSize:11}}/>
        </div>

        <div className="row" style={{gap:14, fontSize:11.5}}>
          <label className="row" style={{gap:6}}>
            <input type="checkbox" checked={!!form.required} onChange={e=>set('required', e.target.checked)}/>
            Obbligatoria
          </label>
          <label className="row" style={{gap:6}}>
            <input type="checkbox" checked={!!form.negotiable} onChange={e=>set('negotiable', e.target.checked)}/>
            Negoziabile
          </label>
          <label className="row" style={{gap:6}}>
            <input type="checkbox" checked={!!form.active} onChange={e=>set('active', e.target.checked)}/>
            Attiva
          </label>
        </div>

        {!isDirty && (
          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'6px 10px', background:'var(--bg-2)', borderRadius:4}}>
            Modifica un campo per abilitare "Salva modifiche". <code>PATCH /api/config/clauses/{selClause.id}</code> persisterà con audit automatico.
          </div>
        )}
      </div>
    </Modal>
  );
}

/**
 * Modale nidificato per selezionare un projectDocument con file allegato come
 * fonte di estrazione AI. Lista doc dei progetti — limitata a quelli con
 * `hasFile=true` (filePath valorizzato).
 */
function AiFillSourceModal({ tpl, onClose, onResult }) {
  const { user, pushToast } = useStore();
  const [docs, setDocs] = React.useState([]);
  const [selectedDocId, setSelectedDocId] = React.useState('');
  const [loading, setLoading] = React.useState(true);
  const [filling, setFilling] = React.useState(false);
  const [instruction, setInstruction] = React.useState('');

  React.useEffect(() => {
    let cancelled = false;
    setLoading(true);
    // Endpoint dedicato cross-project con join su project per UX dropdown
    fetch('/api/documents/list?hasFile=true&limit=200', { cache: 'no-store' })
      .then(r => r.json())
      .then(json => {
        if (cancelled) return;
        setDocs(json?.data || []);
      })
      .catch(() => {})
      .finally(() => !cancelled && setLoading(false));
    return () => { cancelled = true; };
  }, []);

  async function handleFill() {
    if (!selectedDocId) {
      pushToast({ title: 'Seleziona un documento', tone: 'warn' });
      return;
    }
    setFilling(true);
    try {
      const body = { sourceDocId: selectedDocId };
      if (instruction.trim()) body.instruction = instruction.trim();
      const res = await fetch(`/api/templates/${encodeURIComponent(tpl.id)}/ai-fill`, {
        method: 'POST',
        headers: { 'content-type': 'application/json', 'X-Actor-Persona-Id': user?.id || '' },
        body: JSON.stringify(body),
      });
      const j = await res.json().catch(() => ({}));
      if (!res.ok) {
        const userMsg = mapAiErrorToUser(j?.error, j?.detail);
        throw new Error(userMsg);
      }
      pushToast({
        title: 'Template compilato',
        desc: `${Object.keys(j.data?.values || {}).length} variabili in ${(j.data?.latencyMs?.total / 1000).toFixed(1)}s`,
        tone: 'ok',
      });
      onResult(j.data);
    } catch (err) {
      pushToast({ title: 'Compilazione AI non riuscita', desc: err?.message?.slice(0, 240) || 'Riprova più tardi.', tone: 'err' });
    } finally {
      setFilling(false);
    }
  }

  return (
    <Modal
      open
      onClose={onClose}
      title={`Compila ${tpl.code} con AI`}
      size="md"
      footer={
        <>
          <Btn variant="ghost" size="sm" onClick={onClose}>Annulla</Btn>
          <Btn variant="ai" size="sm" onClick={handleFill} disabled={filling || !selectedDocId}>
            {filling ? 'Estrazione AI…' : <><Icon name="sparkle" size={12}/> Estrai e compila</>}
          </Btn>
        </>
      }
    >
      <div className="col" style={{ gap: 14 }}>
        <div style={{ fontSize: 12, color: 'var(--text-2)' }}>
          Scegli un documento dal database (PDF, email, immagine) come fonte. L'AI estrarrà i valori per le {(tpl.variables || []).length} variabili del template e compilerà i campi.
        </div>
        <div className="field">
          <label>Sorgente</label>
          {loading ? (
            <div style={{ fontSize: 12, color: 'var(--text-3)' }}>Caricamento documenti…</div>
          ) : docs.length === 0 ? (
            <div style={{ fontSize: 12, color: 'var(--text-3)', padding: 10, background: 'var(--bg-2)', borderRadius: 6 }}>
              Nessun documento con file allegato. Carica un documento via Progetti CAPEX → Detail → Documenti.
            </div>
          ) : (
            <select value={selectedDocId} onChange={e => setSelectedDocId(e.target.value)}>
              <option value="">— Seleziona documento —</option>
              {docs.map(d => (
                <option key={d.id} value={d.id}>
                  {d.projectCode} · {d.type} · {d.title} ({d.originalFilename || d.id})
                </option>
              ))}
            </select>
          )}
        </div>
        <div className="field">
          <label>Istruzione opzionale</label>
          <textarea
            rows={3}
            value={instruction}
            onChange={e => setInstruction(e.target.value)}
            placeholder="Es: 'Estrai i dati dalla firma a fondo email' oppure 'Cerca nome, cognome, ruolo'"
            style={{ width: '100%', resize: 'vertical' }}
          />
          <div style={{ fontSize: 10.5, color: 'var(--text-3)', marginTop: 4 }}>
            Aiuta l'AI a focalizzarsi sulla parte giusta del documento. Vuoto = estrazione generica.
          </div>
        </div>
        <div style={{ fontSize: 10.5, color: 'var(--text-3)', padding: 8, background: 'var(--bg-2)', borderRadius: 4 }}>
          <Icon name="info" size={10}/> Variabili target: {(tpl.variables || []).map(v => <code key={v} style={{ marginRight: 4 }}>{`{{${v}}}`}</code>)}
        </div>
      </div>
    </Modal>
  );
}

// -------------- VERSIONS --------------
// FASE 3b: UI Versions con publish atomico via /api/config-versions/[id]/publish.
function CustVersions() {
  const { user, pushToast } = useStore();
  const [versions, setVersions] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [publishing, setPublishing] = React.useState(null);

  const reload = React.useCallback(async () => {
    setLoading(true);
    try {
      const res = await fetch('/api/config-versions');
      const json = await res.json().catch(() => ({}));
      setVersions(Array.isArray(json?.data) ? json.data : []);
    } catch (err) {
      pushToast({ title: 'Errore', desc: String(err?.message || err), tone: 'err' });
    } finally {
      setLoading(false);
    }
  }, [pushToast]);

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

  const draft = versions.find(v => v.state === 'draft');
  const lastPublished = versions.find(v => v.state === 'published');

  async function ensureDraft() {
    try {
      const res = await fetch('/api/config-versions', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({}),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(json?.error || `HTTP ${res.status}`);
      await reload();
      pushToast({ title: 'Draft attiva', desc: `${json.data.id}`, tone: 'ok' });
    } catch (err) {
      pushToast({ title: 'Errore creazione draft', desc: String(err?.message || err), tone: 'err' });
    }
  }

  async function publishCurrent() {
    if (!draft) return;
    if (!window.confirm(`Pubblicare ${draft.id} (${draft.label})? Operazione atomica e irreversibile.`)) return;
    setPublishing(draft.id);
    try {
      const res = await fetch(`/api/config-versions/${draft.id}/publish`, {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({}),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(json?.error || `HTTP ${res.status}`);
      await reload();
      pushToast({ title: 'Pubblicato', desc: `${json.data.id} ora published. Audit registrato.`, tone: 'ok' });
    } catch (err) {
      pushToast({ title: 'Errore publish', desc: String(err?.message || err), tone: 'err' });
    } finally {
      setPublishing(null);
    }
  }

  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12, alignItems: 'center' }}>
        <div style={{ fontSize: 12, color: 'var(--text-2)' }}>
          Ogni publish della configurazione crea una versione. Le istanze di workflow attive mantengono la versione con cui sono partite.
        </div>
        <span className="spacer"/>
        {draft ? (
          <Chip kind="warn">Draft attiva: {draft.id}</Chip>
        ) : (
          <ConfigWriteBtn variant="ghost" onClick={ensureDraft}><Icon name="plus" size={11}/> Crea nuova draft</ConfigWriteBtn>
        )}
        <Btn variant="primary" size="sm" onClick={publishCurrent} disabled={!draft || publishing === draft?.id}>
          <Icon name="upload" size={11}/>
          {publishing ? 'Pubblicazione…' : 'Publish draft'}
        </Btn>
      </div>
      {loading ? (
        <div style={{ fontSize: 12, color: 'var(--text-3)', padding: 20, textAlign: 'center' }}>Caricamento…</div>
      ) : versions.length === 0 ? (
        <div style={{ fontSize: 12, color: 'var(--text-3)', padding: 20, textAlign: 'center' }}>
          Nessuna versione registrata. Esegui una mutazione su una entity di config per creare automaticamente una draft.
        </div>
      ) : (
        <table className="tbl dense">
          <thead><tr>
            <th style={{width:160}}>ID</th>
            <th>Label</th>
            <th style={{width:90,textAlign:'center'}}>Stato</th>
            <th style={{width:160}}>Published at</th>
            <th style={{width:140}}>Published by</th>
            <th style={{width:140}}>Parent</th>
            <th style={{width:160}}>Created at</th>
          </tr></thead>
          <tbody>{versions.map(v => (
            <tr key={v.id}>
              <td className="mono" style={{fontSize:10.5}}>{v.id}</td>
              <td style={{fontWeight:500}}>{v.label}</td>
              <td style={{textAlign:'center'}}>
                <Chip kind={v.state==='published'?'ok':v.state==='draft'?'warn':''} dot>{v.state}</Chip>
              </td>
              <td className="mono" style={{fontSize:10.5, color:'var(--text-2)'}}>{v.publishedAt ? v.publishedAt.slice(0,19).replace('T',' ') : '—'}</td>
              <td style={{fontSize:11.5}}>{v.publishedBy || '—'}</td>
              <td className="mono" style={{fontSize:10.5, color:'var(--text-3)'}}>{v.parentVersionId || '—'}</td>
              <td className="mono" style={{fontSize:10.5, color:'var(--text-2)'}}>{v.createdAt.slice(0,19).replace('T',' ')}</td>
            </tr>
          ))}</tbody>
        </table>
      )}
      {lastPublished && (
        <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 12, lineHeight: 1.5 }}>
          Ultimo published: <code>{lastPublished.id}</code> · {lastPublished.publishedAt ? lastPublished.publishedAt.slice(0,19).replace('T',' ') : '—'}.
          Le mutazioni successive partiranno da questa versione come parent.
        </div>
      )}
    </>
  );
}

// -------------- USERS --------------
function CustUsers() {
  const { seed, seedCustom } = useStore();
  const users = (seed && seed.PERSONAS) || [];
  const roles = (seedCustom && seedCustom.ROLES) || [];
  const sites = (seedCustom && seedCustom.SITES) || [];
  const delegations = (seedCustom && seedCustom.DELEGATIONS) || [];
  const [sel, setSel] = React.useState(null);
  const [showNew, setShowNew] = React.useState(false);
  const [filter, setFilter] = React.useState('');
  const [statusFilter, setStatusFilter] = React.useState('all');

  const statusFor = (u) => {
    // derived: if has active delegation out, inferred status
    if (delegations.some(d => d.active && d.fromPersona === u.id)) return 'delegated';
    if (u.email && !u.email.endsWith('@lgs.local') && !u.email.endsWith('@leaven.local')) return 'external';
    return 'active';
  };
  const roleCodeFor = (u) => {
    // naive mapping role label -> code
    const byLabel = roles.find(r => r.name.toLowerCase() === (u.role||'').toLowerCase());
    if (byLabel) return byLabel.code;
    if (/cfo|direzione/i.test(u.role)) return 'CFO';
    if (/controller/i.test(u.role)) return 'CTRL';
    if (/procurement|buyer/i.test(u.role)) return 'BUYER';
    if (/project manager|pm/i.test(u.role)) return 'PM';
    if (/plant/i.test(u.role)) return 'PLANT';
    if (/engineer/i.test(u.role)) return 'ENG';
    if (/vendor/i.test(u.role)) return 'VENDOR_EXT';
    return '—';
  };

  const filtered = users.filter(u => {
    const q = filter.toLowerCase().trim();
    if (q && !u.name.toLowerCase().includes(q) && !u.email.toLowerCase().includes(q) && !(u.role||'').toLowerCase().includes(q)) return false;
    if (statusFilter !== 'all' && statusFor(u) !== statusFilter) return false;
    return true;
  });

  return (
    <>
      <div className="row" style={{ gap: 8, marginBottom: 12, alignItems:'center', flexWrap:'wrap' }}>
        <Chip>{users.length} utenti</Chip>
        <Chip kind="ok">{users.filter(u => statusFor(u)==='active').length} attivi</Chip>
        <Chip kind="warn">{users.filter(u => statusFor(u)==='delegated').length} con delega</Chip>
        <Chip>{users.filter(u => statusFor(u)==='external').length} esterni</Chip>
        <span className="spacer"/>
        <input placeholder="Cerca per nome, email, ruolo…" value={filter} onChange={e=>setFilter(e.target.value)}
          style={{fontSize:11.5, padding:'4px 8px', border:'1px solid var(--line)', borderRadius:4, background:'var(--bg-1)', minWidth:220}}/>
        <select value={statusFilter} onChange={e=>setStatusFilter(e.target.value)}
          style={{fontSize:11.5, padding:'4px 8px', border:'1px solid var(--line)', borderRadius:4, background:'var(--bg-1)'}}>
          <option value="all">Tutti gli stati</option>
          <option value="active">Solo attivi</option>
          <option value="delegated">Con delega in corso</option>
          <option value="external">Esterni</option>
        </select>
        <Btn variant="ghost" size="sm"><Icon name="download" size={11}/> Export CSV</Btn>
        <ConfigWriteBtn perm="user.create" onClick={()=>setShowNew(true)}><Icon name="plus" size={11}/> Nuovo utente</ConfigWriteBtn>
      </div>

      <table className="tbl dense">
        <thead><tr>
          <th style={{width:40}}></th>
          <th>Nome</th>
          <th>Email</th>
          <th style={{width:150}}>Ruolo</th>
          <th style={{width:140}}>Dipartimento</th>
          <th style={{width:100, textAlign:'center'}}>Stato</th>
          <th style={{width:120, textAlign:'center'}}>Ultimo accesso</th>
          <th style={{width:40}}></th>
        </tr></thead>
        <tbody>
          {filtered.length === 0 ? (
            <tr><td colSpan={8} style={{textAlign:'center', padding:20, fontSize:11.5, color:'var(--text-3)'}}>Nessun utente trovato</td></tr>
          ) : filtered.map((u, i) => {
            const status = statusFor(u);
            const deleg = delegations.find(d => d.active && d.fromPersona === u.id);
            return (
              <tr key={u.id} className="clickable" onClick={()=>setSel(u)}>
                <td>
                  <div style={{width:26, height:26, borderRadius:'50%', background:'var(--bg-2)', border:'1px solid var(--line)', display:'grid', placeItems:'center', fontSize:10, fontWeight:600, color:'var(--text-2)'}}>
                    {u.initials}
                  </div>
                </td>
                <td style={{fontWeight:500}}>{u.name}</td>
                <td className="mono" style={{fontSize:11}}>{u.email}</td>
                <td><Chip kind={roleCodeFor(u)==='—'?'':'ai'}>{roleCodeFor(u)}</Chip> <span style={{fontSize:11, color:'var(--text-3)', marginLeft:4}}>{u.role}</span></td>
                <td style={{fontSize:11.5, color:'var(--text-2)'}}>{u.dept}</td>
                <td style={{textAlign:'center'}}>
                  {status === 'active' && <Chip kind="ok" dot>attivo</Chip>}
                  {status === 'delegated' && <Chip kind="warn" dot title={deleg?`Delega a ${deleg.toUser} fino ${deleg.toDate}`:''}>in delega</Chip>}
                  {status === 'external' && <Chip dot>esterno</Chip>}
                </td>
                <td className="mono" style={{fontSize:10.5, textAlign:'center', color:'var(--text-3)'}}>{['5m fa','1h fa','ieri','3g fa','12m fa','ora','8h fa'][i % 7]}</td>
                <td style={{textAlign:'center'}}><Icon name="chevron-right" size={12}/></td>
              </tr>
            );
          })}
        </tbody>
      </table>

      <UserDetailModal open={!!sel} onClose={()=>setSel(null)} user={sel} roles={roles} sites={sites} delegations={delegations}/>
      <NewUserModal open={showNew} onClose={()=>setShowNew(false)} users={users} roles={roles} sites={sites}/>
    </>
  );
}

function UserDetailModal({ open, onClose, user, roles, sites, delegations }) {
  const [tab, setTab] = React.useState('profile');
  React.useEffect(() => { if (user) setTab('profile'); }, [user]);
  if (!user) return null;

  const activeDelegs = delegations.filter(d => d.fromPersona === user.id && d.active);
  const receivedDelegs = delegations.filter(d => d.toPersona === user.id && d.active);

  return (
    <Modal open={open} onClose={onClose} size="xl"
      title={
        <div className="row" style={{gap:10, alignItems:'center'}}>
          <div style={{width:34, height:34, borderRadius:'50%', background:'var(--accent)', color:'#fff', display:'grid', placeItems:'center', fontSize:13, fontWeight:600}}>
            {user.initials}
          </div>
          <div>
            <div style={{fontSize:14, fontWeight:600}}>{user.name}</div>
            <div style={{fontSize:11, color:'var(--text-3)', fontWeight:400}}>{user.role} · {user.email}</div>
          </div>
        </div>
      }
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose}>Chiudi</Btn>
        <Btn variant="ghost" size="sm"><Icon name="refresh" size={11}/> Reset MFA</Btn>
        <Btn variant="ghost" size="sm"><Icon name="lock" size={11}/> Sospendi</Btn>
        <Btn variant="primary" size="sm" onClick={onClose}>Salva</Btn>
      </>}>
      <div className="row" style={{gap:2, marginBottom:14, background:'var(--bg-2)', padding:2, borderRadius:6}}>
        <button className={`btn sm ${tab==='profile'?'primary':'ghost'}`} onClick={()=>setTab('profile')}>Profilo</button>
        <button className={`btn sm ${tab==='roles'?'primary':'ghost'}`} onClick={()=>setTab('roles')}>Ruoli & scope</button>
        <button className={`btn sm ${tab==='delegations'?'primary':'ghost'}`} onClick={()=>setTab('delegations')}>Deleghe ({activeDelegs.length+receivedDelegs.length})</button>
        <button className={`btn sm ${tab==='security'?'primary':'ghost'}`} onClick={()=>setTab('security')}>Sicurezza</button>
        <button className={`btn sm ${tab==='activity'?'primary':'ghost'}`} onClick={()=>setTab('activity')}>Attività</button>
      </div>

      {tab === 'profile' && (
        <div className="col" style={{gap:14}}>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Anagrafica</div>
            <div className="grid grid-2">
              <div className="field"><label>Nome e cognome</label><input defaultValue={user.name}/></div>
              <div className="field"><label>Email aziendale</label><input defaultValue={user.email} type="email"/></div>
              <div className="field"><label>Telefono</label><input defaultValue={'+39 ' + (335 + parseInt(user.id.slice(1), 10)*7) + ' ' + (1000000 + parseInt(user.id.slice(1), 10)*123).toString().slice(0,7)}/></div>
              <div className="field"><label>User ID</label><input defaultValue={user.id} disabled style={{fontFamily:'var(--font-mono)'}}/></div>
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Organizzazione</div>
            <div className="grid grid-3">
              <div className="field"><label>Dipartimento</label>
                <select defaultValue={user.dept}>
                  {['Program Management','Procurement','Finance','Operations','Engineering','Executive','External Vendor','IT','HSE','Legal'].map(d => <option key={d}>{d}</option>)}
                </select>
              </div>
              <div className="field"><label>Manager diretto</label>
                <select defaultValue="">
                  <option value="">—</option>
                  <option value="u06">Giulia Parodi (CFO)</option>
                  <option value="u01">Marco Ferretti (PM)</option>
                </select>
              </div>
              <div className="field"><label>Lingua</label>
                <select defaultValue="it"><option value="it">Italiano</option><option value="en">English</option></select>
              </div>
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Preferenze notifiche</div>
            <div className="grid grid-3">
              <label className="row" style={{gap:6, fontSize:11.5}}><input type="checkbox" defaultChecked/> Email giornaliere</label>
              <label className="row" style={{gap:6, fontSize:11.5}}><input type="checkbox" defaultChecked/> In-app realtime</label>
              <label className="row" style={{gap:6, fontSize:11.5}}><input type="checkbox"/> Teams push</label>
              <label className="row" style={{gap:6, fontSize:11.5}}><input type="checkbox" defaultChecked/> Escalation SLA</label>
              <label className="row" style={{gap:6, fontSize:11.5}}><input type="checkbox"/> Digest settimanale AI</label>
              <label className="row" style={{gap:6, fontSize:11.5}}><input type="checkbox"/> SMS emergenze</label>
            </div>
          </div>
        </div>
      )}

      {tab === 'roles' && (
        <div className="col" style={{gap:14}}>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Ruolo primario</div>
            <div className="card" style={{padding:10}}>
              <div className="row" style={{gap:10, alignItems:'center'}}>
                <Chip kind="ai">{(roles.find(r=>r.name===user.role) || {}).code || user.role}</Chip>
                <div style={{fontSize:12}}>{user.role}</div>
                <span className="spacer"/>
                <Btn variant="ghost" size="sm">Cambia ruolo</Btn>
              </div>
              {roles.find(r=>r.name===user.role) && (
                <div style={{marginTop:8, fontSize:11, color:'var(--text-2)'}}>
                  Limite di firma: <strong className="mono">{roles.find(r=>r.name===user.role).scope?.amountMax == null ? '∞' : fmtEUR(roles.find(r=>r.name===user.role).scope.amountMax, true)}</strong> ·
                  Sostituto: <strong>{roles.find(r=>r.name===user.role).substituteRole || '—'}</strong> ·
                  Escalation: <strong>{roles.find(r=>r.name===user.role).escalationRole || '—'}</strong>
                </div>
              )}
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Ruoli aggiuntivi</div>
            <div style={{display:'flex', flexWrap:'wrap', gap:4}}>
              <Chip>READ_ALL</Chip>
              <button className="btn sm ghost" style={{fontSize:10}}>+ Aggiungi ruolo</button>
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Scope assegnato</div>
            <div className="grid grid-2">
              <div className="field"><label>Siti visibili</label>
                <select multiple size={4} style={{height:90}}>
                  <option>* (tutti)</option>
                  {sites.map(s => <option key={s.id} value={s.id}>{s.code} · {s.name}</option>)}
                </select>
                <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:2}}>Tieni Cmd/Ctrl per selezione multipla</div>
              </div>
              <div className="field"><label>Business Unit</label>
                <select multiple size={4} style={{height:90}}>
                  <option>* (tutte)</option>
                  <option value="BU_AER">AER · Aerospace</option>
                  <option value="BU_PWR">PWR · Powertrain</option>
                  <option value="BU_MEC">MEC · Mechatronics</option>
                  <option value="BU_SRV">SRV · Services</option>
                </select>
              </div>
            </div>
          </div>

          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4}}>
            <Icon name="info" size={10}/> I permessi effettivi sono la somma di ruolo primario + ruoli aggiuntivi, intersecati con lo scope. Modifiche al ruolo vengono applicate al prossimo login.
          </div>
        </div>
      )}

      {tab === 'delegations' && (
        <div className="col" style={{gap:14}}>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Deleghe in uscita ({activeDelegs.length})</div>
            {activeDelegs.length === 0 ? (
              <div style={{fontSize:11, color:'var(--text-3)'}}>Nessuna delega attiva. {user.name} sta operando direttamente.</div>
            ) : (
              <table className="tbl dense">
                <thead><tr><th>Delegato a</th><th style={{width:160}}>Periodo</th><th>Scope</th><th>Motivo</th></tr></thead>
                <tbody>{activeDelegs.map(d => (
                  <tr key={d.id}>
                    <td style={{fontWeight:500}}>{d.toUser}</td>
                    <td className="mono" style={{fontSize:11}}>{d.fromDate} → {d.toDate}</td>
                    <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(d.scope||[]).map(s => <Chip key={s}>{s}</Chip>)}</div></td>
                    <td style={{fontSize:11, color:'var(--text-2)'}}>{d.reason}</td>
                  </tr>
                ))}</tbody>
              </table>
            )}
            <div style={{marginTop:8}}>
              <Btn variant="ghost" size="sm"><Icon name="plus" size={11}/> Nuova delega</Btn>
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Deleghe in arrivo ({receivedDelegs.length})</div>
            {receivedDelegs.length === 0 ? (
              <div style={{fontSize:11, color:'var(--text-3)'}}>Nessuna delega ricevuta.</div>
            ) : (
              <table className="tbl dense">
                <thead><tr><th>Delegante</th><th style={{width:160}}>Periodo</th><th>Scope</th><th>Motivo</th></tr></thead>
                <tbody>{receivedDelegs.map(d => (
                  <tr key={d.id}>
                    <td style={{fontWeight:500}}>{d.fromUser}</td>
                    <td className="mono" style={{fontSize:11}}>{d.fromDate} → {d.toDate}</td>
                    <td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{(d.scope||[]).map(s => <Chip key={s}>{s}</Chip>)}</div></td>
                    <td style={{fontSize:11, color:'var(--text-2)'}}>{d.reason}</td>
                  </tr>
                ))}</tbody>
              </table>
            )}
          </div>
        </div>
      )}

      {tab === 'security' && (
        <div className="col" style={{gap:14}}>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Autenticazione</div>
            <div className="grid grid-2">
              <div className="card" style={{padding:10}}>
                <div className="row" style={{gap:6, alignItems:'center'}}>
                  <Icon name="shield" size={14}/>
                  <div style={{fontSize:12, fontWeight:600}}>SSO SAML</div>
                  <span className="spacer"/>
                  <Chip kind="ok" dot>Entra ID</Chip>
                </div>
                <div style={{fontSize:11, color:'var(--text-2)', marginTop:4}}>Login federato obbligatorio. Nessuna password locale.</div>
              </div>
              <div className="card" style={{padding:10}}>
                <div className="row" style={{gap:6, alignItems:'center'}}>
                  <Icon name="lock" size={14}/>
                  <div style={{fontSize:12, fontWeight:600}}>MFA</div>
                  <span className="spacer"/>
                  <Chip kind="ok" dot>authenticator</Chip>
                </div>
                <div style={{fontSize:11, color:'var(--text-2)', marginTop:4}}>Enrolled 2025-11-14. Backup codes: 10 rimanenti.</div>
                <div style={{marginTop:8}}>
                  <Btn variant="ghost" size="sm">Reset MFA</Btn>
                </div>
              </div>
            </div>
          </div>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Sessioni attive (2)</div>
            <table className="tbl dense">
              <thead><tr><th>Dispositivo</th><th>IP</th><th>Ubicazione</th><th style={{width:100}}>Ultimo uso</th><th style={{width:60}}></th></tr></thead>
              <tbody>
                <tr><td>Chrome · macOS</td><td className="mono" style={{fontSize:10.5}}>10.4.12.88</td><td>Torino, IT</td><td className="mono" style={{fontSize:10.5}}>5m fa</td><td><Btn variant="ghost" size="sm">Chiudi</Btn></td></tr>
                <tr><td>Teams mobile · iOS</td><td className="mono" style={{fontSize:10.5}}>80.116.4.22</td><td>Milano, IT</td><td className="mono" style={{fontSize:10.5}}>2h fa</td><td><Btn variant="ghost" size="sm">Chiudi</Btn></td></tr>
              </tbody>
            </table>
          </div>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Stato account</div>
            <div className="row" style={{gap:14, fontSize:11.5}}>
              <label className="row" style={{gap:6}}><input type="checkbox" defaultChecked/> Account attivo</label>
              <label className="row" style={{gap:6}}><input type="checkbox" defaultChecked/> Può accedere ad API</label>
              <label className="row" style={{gap:6}}><input type="checkbox"/> Blocca login temporaneo</label>
            </div>
          </div>
        </div>
      )}

      {tab === 'activity' && (
        <div className="col" style={{gap:10}}>
          <div className="eyebrow">Ultime 20 azioni</div>
          <table className="tbl dense">
            <thead><tr>
              <th style={{width:130}}>Timestamp</th>
              <th style={{width:120}}>Azione</th>
              <th>Risorsa</th>
              <th style={{width:80, textAlign:'center'}}>Esito</th>
            </tr></thead>
            <tbody>
              {[
                { t: '09:14:22', a: 'rda.approve', r: 'RDA-2026-0141', ok: true },
                { t: '09:12:01', a: 'auth.login', r: 'SAML · Entra ID', ok: true },
                { t: 'ieri 17:42', a: 'doc.download', r: 'BUS_CASE-P2026-008', ok: true },
                { t: 'ieri 15:11', a: 'rda.comment', r: 'RDA-2026-0138', ok: true },
                { t: 'ieri 12:05', a: 'vendor.view', r: 'v03 · Comau', ok: true },
                { t: '2g fa 16:30', a: 'report.export', r: 'Portfolio CAPEX Q2', ok: true },
                { t: '3g fa 10:18', a: 'rda.reject', r: 'RDA-2026-0135', ok: true },
              ].map((r,i) => (
                <tr key={i}>
                  <td className="mono" style={{fontSize:11}}>{r.t}</td>
                  <td className="mono" style={{fontSize:11}}>{r.a}</td>
                  <td style={{fontSize:11.5}}>{r.r}</td>
                  <td style={{textAlign:'center'}}>{r.ok ? <Chip kind="ok" dot>ok</Chip> : <Chip kind="err" dot>ko</Chip>}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </Modal>
  );
}

function NewUserModal({ open, onClose, users, roles, sites }) {
  const [step, setStep] = React.useState(1);
  const [form, setForm] = React.useState({
    firstName: '', lastName: '', email: '', phone: '',
    dept: 'Program Management', manager: '',
    role: 'PM', additionalRoles: [],
    sitesScope: ['*'], buScope: ['*'],
    invitationType: 'sso', sendWelcome: true, mfaRequired: true,
  });
  React.useEffect(() => {
    if (open) {
      setStep(1);
      setForm({
        firstName: '', lastName: '', email: '', phone: '',
        dept: 'Program Management', manager: '',
        role: 'PM', additionalRoles: [],
        sitesScope: ['*'], buScope: ['*'],
        invitationType: 'sso', sendWelcome: true, mfaRequired: true,
      });
    }
  }, [open]);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));

  const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email);
  const emailUnique = !users.some(u => u.email.toLowerCase() === form.email.toLowerCase());
  const phoneValid = !form.phone || /^[+0-9\s()]{6,20}$/.test(form.phone);
  const step1Valid = form.firstName.trim() && form.lastName.trim() && emailValid && emailUnique && phoneValid;
  const step2Valid = form.role && form.sitesScope.length > 0 && form.buScope.length > 0;

  const suggestedEmail = (() => {
    if (!form.firstName || !form.lastName) return '';
    const a = form.firstName.trim().toLowerCase().slice(0,1);
    const b = form.lastName.trim().toLowerCase().replace(/\s/g,'');
    return `${a}.${b}@lgs.local`;
  })();

  const selectedRole = roles.find(r => r.code === form.role);

  return (
    <Modal open={open} onClose={onClose} title="Nuovo utente" size="lg"
      footer={<>
        <Btn variant="ghost" size="sm" onClick={onClose}>Annulla</Btn>
        {step > 1 && <Btn variant="ghost" size="sm" onClick={()=>setStep(s=>s-1)}>Indietro</Btn>}
        {step < 3 && <Btn variant="primary" size="sm" disabled={step===1 ? !step1Valid : !step2Valid} onClick={()=>setStep(s=>s+1)}>Avanti</Btn>}
        {step === 3 && <Btn variant="primary" size="sm" onClick={onClose}><Icon name="check" size={11}/> Crea utente & invia invito</Btn>}
      </>}>
      <div className="row" style={{gap:6, marginBottom:14, fontSize:11}}>
        {['Anagrafica','Ruolo & scope','Conferma'].map((label, i) => (
          <React.Fragment key={label}>
            <div className="row" style={{gap:6, alignItems:'center', color: step > i ? 'var(--ok)' : step === i+1 ? 'var(--text-1)' : 'var(--text-3)', fontWeight: step === i+1 ? 600 : 400}}>
              <div style={{width:18, height:18, borderRadius:'50%', background: step > i ? 'var(--ok)' : step === i+1 ? 'var(--accent)' : 'var(--bg-2)', color: step >= i+1 ? '#fff' : 'var(--text-3)', display:'grid', placeItems:'center', fontSize:10, fontWeight:600}}>
                {step > i ? '✓' : i+1}
              </div>
              {label}
            </div>
            {i < 2 && <div style={{flex:1, height:1, background:'var(--line)'}}/>}
          </React.Fragment>
        ))}
      </div>

      {step === 1 && (
        <div className="col" style={{gap:14}}>
          <div className="grid grid-2">
            <div className="field"><label>Nome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.firstName} onChange={e=>set('firstName', e.target.value)} placeholder="Marco"/>
            </div>
            <div className="field"><label>Cognome <span style={{color:'var(--err)'}}>*</span></label>
              <input value={form.lastName} onChange={e=>set('lastName', e.target.value)} placeholder="Rossi"/>
            </div>
          </div>
          <div className="field"><label>Email aziendale <span style={{color:'var(--err)'}}>*</span></label>
            <div className="row" style={{gap:6}}>
              <input value={form.email} onChange={e=>set('email', e.target.value)} placeholder="m.rossi@lgs.local" type="email" style={{flex:1}}/>
              {suggestedEmail && suggestedEmail !== form.email && (
                <Btn variant="ghost" size="sm" onClick={()=>set('email', suggestedEmail)}>Usa {suggestedEmail}</Btn>
              )}
            </div>
            {form.email && !emailValid && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}><Icon name="alert-triangle" size={10}/> Email non valida</div>}
            {emailValid && !emailUnique && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}><Icon name="alert-triangle" size={10}/> Utente con questa email già esistente</div>}
          </div>
          <div className="grid grid-2">
            <div className="field"><label>Telefono</label>
              <input value={form.phone} onChange={e=>set('phone', e.target.value)} placeholder="+39 335 1234567"/>
              {form.phone && !phoneValid && <div style={{fontSize:10.5, color:'var(--err)', marginTop:4}}>Formato telefono non valido</div>}
            </div>
            <div className="field"><label>Dipartimento</label>
              <select value={form.dept} onChange={e=>set('dept', e.target.value)}>
                {['Program Management','Procurement','Finance','Operations','Engineering','Executive','External Vendor','IT','HSE','Legal'].map(d => <option key={d}>{d}</option>)}
              </select>
            </div>
          </div>
          <div className="field"><label>Manager diretto</label>
            <select value={form.manager} onChange={e=>set('manager', e.target.value)}>
              <option value="">—</option>
              {users.map(u => <option key={u.id} value={u.id}>{u.name} ({u.role})</option>)}
            </select>
          </div>
        </div>
      )}

      {step === 2 && (
        <div className="col" style={{gap:14}}>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Ruolo primario <span style={{color:'var(--err)'}}>*</span></div>
            <div style={{display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:6}}>
              {roles.map(r => (
                <label key={r.id} className="card" style={{padding:10, cursor:'pointer', border: form.role === r.code ? '2px solid var(--accent)' : '1px solid var(--line)', background: form.role === r.code ? 'color-mix(in oklch, var(--accent) 8%, var(--bg-1))' : 'var(--bg-1)'}}>
                  <div className="row" style={{gap:8, alignItems:'flex-start'}}>
                    <input type="radio" name="role" checked={form.role === r.code} onChange={()=>set('role', r.code)} style={{marginTop:2}}/>
                    <div style={{flex:1}}>
                      <div className="row" style={{gap:6, alignItems:'center'}}>
                        <Chip kind="ai">{r.code}</Chip>
                        <div style={{fontSize:12, fontWeight:600}}>{r.name}</div>
                      </div>
                      <div style={{fontSize:10.5, color:'var(--text-3)', marginTop:4, lineHeight:1.4}}>{r.description}</div>
                      <div style={{fontSize:10, color:'var(--text-2)', marginTop:4, fontFamily:'var(--font-mono)'}}>
                        Limite firma: {r.scope?.amountMax == null ? '∞' : fmtEUR(r.scope.amountMax, true)} · {r.personaCount} persone
                      </div>
                    </div>
                  </div>
                </label>
              ))}
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Scope</div>
            <div className="grid grid-2">
              <div className="field"><label>Siti visibili</label>
                <div style={{display:'flex', flexWrap:'wrap', gap:4, marginBottom:6}}>
                  {form.sitesScope.map(s => (
                    <Chip key={s}>{s === '*' ? 'tutti' : s} <span style={{cursor:'pointer', marginLeft:4, color:'var(--text-3)'}} onClick={()=>set('sitesScope', form.sitesScope.filter(x=>x!==s))}>×</span></Chip>
                  ))}
                </div>
                <select value="" onChange={e=>{if(e.target.value){ set('sitesScope', [...new Set([...form.sitesScope, e.target.value])].filter(x => x !== '*' || form.sitesScope.length === 0));}}}>
                  <option value="">+ Aggiungi sito</option>
                  <option value="*">* (tutti)</option>
                  {sites.map(s => <option key={s.id} value={s.code}>{s.code} · {s.name}</option>)}
                </select>
              </div>
              <div className="field"><label>Business Unit</label>
                <div style={{display:'flex', flexWrap:'wrap', gap:4, marginBottom:6}}>
                  {form.buScope.map(b => (
                    <Chip key={b}>{b === '*' ? 'tutte' : b} <span style={{cursor:'pointer', marginLeft:4, color:'var(--text-3)'}} onClick={()=>set('buScope', form.buScope.filter(x=>x!==b))}>×</span></Chip>
                  ))}
                </div>
                <select value="" onChange={e=>{if(e.target.value){ set('buScope', [...new Set([...form.buScope, e.target.value])].filter(x => x !== '*' || form.buScope.length === 0));}}}>
                  <option value="">+ Aggiungi BU</option>
                  <option value="*">* (tutte)</option>
                  <option value="AER">AER · Aerospace</option>
                  <option value="PWR">PWR · Powertrain</option>
                  <option value="MEC">MEC · Mechatronics</option>
                  <option value="SRV">SRV · Services</option>
                </select>
              </div>
            </div>
          </div>

          {selectedRole && (
            <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4}}>
              <Icon name="info" size={10}/> Il ruolo <strong>{selectedRole.code}</strong> porta {(selectedRole.permissions||[]).length} permessi impliciti: {(selectedRole.permissions||[]).slice(0,5).join(', ')}{(selectedRole.permissions||[]).length > 5 ? ', …' : ''}
            </div>
          )}
        </div>
      )}

      {step === 3 && (
        <div className="col" style={{gap:14}}>
          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Riepilogo</div>
            <table className="tbl dense">
              <tbody>
                <tr><td style={{width:160, color:'var(--text-2)'}}>Nome</td><td style={{fontWeight:500}}>{form.firstName} {form.lastName}</td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Email</td><td className="mono" style={{fontSize:11.5}}>{form.email}</td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Telefono</td><td className="mono" style={{fontSize:11.5}}>{form.phone || '—'}</td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Dipartimento</td><td>{form.dept}</td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Manager</td><td>{users.find(u=>u.id===form.manager)?.name || '—'}</td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Ruolo primario</td><td><Chip kind="ai">{form.role}</Chip> {selectedRole?.name}</td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Scope siti</td><td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{form.sitesScope.map(s => <Chip key={s}>{s === '*' ? 'tutti' : s}</Chip>)}</div></td></tr>
                <tr><td style={{color:'var(--text-2)'}}>Scope BU</td><td><div style={{display:'flex', gap:3, flexWrap:'wrap'}}>{form.buScope.map(b => <Chip key={b}>{b === '*' ? 'tutte' : b}</Chip>)}</div></td></tr>
              </tbody>
            </table>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Modalità di invito</div>
            <div className="col" style={{gap:6}}>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="radio" name="invType" checked={form.invitationType==='sso'} onChange={()=>set('invitationType', 'sso')}/>
                <div>
                  <strong>SSO SAML (Entra ID)</strong> — consigliato per utenti interni. L'utente fa login con le credenziali aziendali, nessuna password locale.
                </div>
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="radio" name="invType" checked={form.invitationType==='invite'} onChange={()=>set('invitationType', 'invite')}/>
                <div>
                  <strong>Link di invito email</strong> — per vendor o esterni. Valido 7 giorni, MFA al primo accesso.
                </div>
              </label>
              <label className="row" style={{gap:6, fontSize:11.5}}>
                <input type="radio" name="invType" checked={form.invitationType==='api'} onChange={()=>set('invitationType', 'api')}/>
                <div>
                  <strong>Account tecnico (API)</strong> — non può fare login UI, solo chiavi API per integrazioni.
                </div>
              </label>
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{marginBottom:6}}>Opzioni</div>
            <div className="col" style={{gap:6, fontSize:11.5}}>
              <label className="row" style={{gap:6}}>
                <input type="checkbox" checked={form.sendWelcome} onChange={e=>set('sendWelcome', e.target.checked)}/>
                Invia email di benvenuto con guida al primo accesso
              </label>
              <label className="row" style={{gap:6}}>
                <input type="checkbox" checked={form.mfaRequired} onChange={e=>set('mfaRequired', e.target.checked)}/>
                Richiedi enrollment MFA al primo accesso
              </label>
            </div>
          </div>

          <div style={{fontSize:10.5, color:'var(--text-3)', padding:'8px 10px', background:'var(--bg-2)', borderRadius:4, lineHeight:1.5}}>
            <Icon name="info" size={10}/> Mock: <code>POST /api/config/users</code>. Al commit vengono provisioned in Entra ID via SCIM, create le ACL in Document DB e notificati manager e IT support.
          </div>
        </div>
      )}
    </Modal>
  );
}

// FASE 21 — Webhook outbound subscriptions (Customizing tab).
// Lista subscriptions con secret_hint mascherato + bottone "Nuovo webhook"
// che apre modal con form (name, url, eventCodes, secret one-time).
// Lista deliveries recenti con status colorato (delivered=verde, failed=rosso).
// Bottone "Test" su ogni subscription manda un webhook.test ping.
function CustWebhooks() {
  const { user, pushToast } = useStore();
  const [subs, setSubs] = React.useState(null);
  const [deliveries, setDeliveries] = React.useState(null);
  const [showNew, setShowNew] = React.useState(false);
  const [secretReveal, setSecretReveal] = React.useState(null); // { id, secret }

  const reload = React.useCallback(() => {
    setSubs(null); setDeliveries(null);
    Promise.all([
      fetch('/api/webhook-subscriptions').then(r => r.json()).catch(() => ({ data: [] })),
      fetch('/api/webhook-deliveries?limit=20').then(r => r.json()).catch(() => ({ data: [] })),
    ]).then(([s, d]) => { setSubs(s.data || []); setDeliveries(d.data || []); });
  }, []);

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

  async function handleTest(id) {
    const res = await fetch(`/api/webhook-subscriptions/${encodeURIComponent(id)}/test`, {
      method: 'POST',
      headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
    });
    const json = await res.json().catch(() => ({}));
    if (json.delivered) {
      pushToast({ title: 'Webhook test OK', desc: `HTTP ${json.httpStatus} · ${json.latencyMs}ms`, tone: 'ok' });
    } else if (json.reason === 'subscription_not_subscribed_to_webhook_test') {
      pushToast({ title: 'Test skip', desc: 'Aggiungi "webhook.test" o "*" agli eventCodes per ricevere il ping.', tone: 'warn' });
    } else {
      pushToast({ title: 'Webhook test failed', desc: json.error || `HTTP ${json.httpStatus ?? 'n/d'}`, tone: 'err' });
    }
    reload();
  }

  async function handleToggleActive(sub) {
    await fetch(`/api/webhook-subscriptions/${encodeURIComponent(sub.id)}`, {
      method: 'PATCH',
      headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
      body: JSON.stringify({ active: !sub.active }),
    });
    reload();
  }

  async function handleDelete(id) {
    if (!confirm('Eliminare questa webhook subscription?')) return;
    await fetch(`/api/webhook-subscriptions/${encodeURIComponent(id)}`, {
      method: 'DELETE',
      headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
    });
    pushToast({ title: 'Webhook eliminato', tone: 'ok' });
    reload();
  }

  // FASE 21.B — retry manuale di una delivery failed/retrying.
  async function handleRetryDelivery(deliveryId, force = false) {
    try {
      const res = await fetch(`/api/webhook-deliveries/${encodeURIComponent(deliveryId)}/retry`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        credentials: 'same-origin',
        body: JSON.stringify({ force }),
      });
      const j = await res.json().catch(() => ({}));
      if (!res.ok) {
        if (j.error === 'already_delivered') {
          pushToast({ title: 'Già delivered', desc: 'Nessun retry necessario.', tone: 'info' });
        } else {
          pushToast({ title: 'Retry fallito', desc: j.error || `HTTP ${res.status}`, tone: 'err' });
        }
        return;
      }
      const r = j.data || {};
      pushToast({
        title: r.status === 'delivered' ? 'Retry → delivered' : `Retry → ${r.status}`,
        desc: `attempts ${r.attempts} · HTTP ${r.httpStatus ?? '—'} · ${r.latencyMs}ms`,
        tone: r.status === 'delivered' ? 'ok' : (r.status === 'retrying' ? 'info' : 'err'),
      });
    } catch (e) {
      pushToast({ title: 'Retry errore', desc: String(e?.message || e).slice(0, 200), tone: 'err' });
    }
    reload();
  }

  return (
    <div className="col" style={{ gap: 14 }}>
      <div className="card">
        <div className="card-header">
          <div className="title"><Icon name="link" size={13}/> Webhook subscriptions</div>
          <div className="actions">
            <Btn variant="primary" size="sm" onClick={() => setShowNew(true)}><Icon name="plus" size={11}/> Nuovo webhook</Btn>
          </div>
        </div>
        <div className="card-body" style={{paddingTop: 6}}>
          {subs === null ? (
            <div style={{padding: 14, textAlign: 'center', color: 'var(--text-3)', fontSize: 12}}>Caricamento…</div>
          ) : subs.length === 0 ? (
            <div style={{padding: 14, color: 'var(--text-3)', fontSize: 12}}>
              Nessun webhook configurato. Crea una subscription per ricevere eventi domain (sla.warn, sla.breach, vendor.qualified, …) verso un endpoint HTTPS esterno firmato HMAC SHA-256.
            </div>
          ) : (
            <table className="tbl">
              <thead><tr><th>Nome</th><th>URL</th><th>Eventi</th><th>Secret hint</th><th>Stato</th><th></th></tr></thead>
              <tbody>
                {subs.map(s => (
                  <tr key={s.id}>
                    <td style={{fontWeight: 500}}>{s.name}</td>
                    <td className="mono" style={{fontSize: 11, color: 'var(--text-2)', maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis'}}>{s.url}</td>
                    <td style={{fontSize: 11}}>{s.eventCodes.join(', ')}</td>
                    <td className="mono" style={{fontSize: 11}}>{s.secretHint}</td>
                    <td><Chip kind={s.active ? 'ok' : ''} dot>{s.active ? 'attivo' : 'disabled'}</Chip></td>
                    <td>
                      <div className="row" style={{gap: 4}}>
                        <Btn variant="ghost" size="sm" onClick={() => handleTest(s.id)}>Test</Btn>
                        <Btn variant="ghost" size="sm" onClick={() => handleToggleActive(s)}>{s.active ? 'Disabilita' : 'Abilita'}</Btn>
                        <Btn variant="ghost" size="sm" onClick={() => handleDelete(s.id)}>×</Btn>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <div className="title"><Icon name="history" size={12}/> Recent deliveries ({deliveries?.length ?? '…'})</div>
          <div className="actions"><Btn variant="ghost" size="sm" onClick={reload}>Reload</Btn></div>
        </div>
        <div className="card-body" style={{paddingTop: 6}}>
          {deliveries === null ? (
            <div style={{padding: 14, textAlign: 'center', color: 'var(--text-3)', fontSize: 12}}>Caricamento…</div>
          ) : deliveries.length === 0 ? (
            <div style={{padding: 14, color: 'var(--text-3)', fontSize: 12}}>Nessuna delivery effettuata.</div>
          ) : (
            <table className="tbl">
              <thead><tr>
                <th>Quando</th>
                <th>Event</th>
                <th>URL</th>
                <th>Status</th>
                <th className="num">Attempts</th>
                <th className="num">HTTP</th>
                <th className="num">Latency</th>
                <th>Error</th>
                <th></th>
              </tr></thead>
              <tbody>
                {deliveries.map(d => {
                  const canRetry = d.status === 'failed' || d.status === 'retrying';
                  const statusKind = d.status === 'delivered' || d.status === 'success'
                    ? 'ok'
                    : d.status === 'failed'
                      ? 'err'
                      : d.status === 'retrying'
                        ? 'warn'
                        : d.status === 'inbound_received'
                          ? 'info'
                          : '';
                  return (
                    <tr key={d.id}>
                      <td className="mono" style={{fontSize: 10.5}}>{new Date(d.createdAt).toLocaleString('it-IT', {day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'})}</td>
                      <td style={{fontSize: 11}}>{d.eventCode}</td>
                      <td className="mono" style={{fontSize: 10.5, maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis'}}>{d.url}</td>
                      <td><Chip kind={statusKind} dot>{d.status}</Chip></td>
                      <td className="num mono" style={{fontSize: 11}}>{d.attempts ?? 1}</td>
                      <td className="num" style={{fontSize: 11}}>{d.httpStatus ?? '—'}</td>
                      <td className="num" style={{fontSize: 11}}>{d.latencyMs}ms</td>
                      <td style={{fontSize: 10.5, color: 'var(--err)'}}>{d.error?.slice(0, 60) || ''}</td>
                      <td>
                        {canRetry ? (
                          <Btn variant="ghost" size="sm" onClick={() => handleRetryDelivery(d.id, true)} title="Retry manuale (force, ignora MAX_ATTEMPTS)">
                            <Icon name="refresh" size={11}/>
                          </Btn>
                        ) : <span style={{color: 'var(--text-3)', fontSize: 10}}>—</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>
      </div>

      {showNew && <NewWebhookModal onClose={() => setShowNew(false)} onCreated={(secret) => { setShowNew(false); setSecretReveal(secret); reload(); }} user={user} pushToast={pushToast}/>}
      {secretReveal && (
        <Modal open onClose={() => setSecretReveal(null)} title="Webhook creato — salva subito il secret" size="md" footer={
          <Btn variant="primary" size="sm" onClick={() => setSecretReveal(null)}>Capito, ho salvato</Btn>
        }>
          <div className="col" style={{gap: 12}}>
            <div style={{padding: 10, background: 'rgba(192,57,43,0.08)', border: '1px solid var(--warn)', borderRadius: 4, fontSize: 12}}>
              ⚠ Questo secret non sarà più recuperabile. Salvalo ORA in modo sicuro lato consumer.
            </div>
            <div className="field"><label>Secret HMAC SHA-256</label>
              <div className="mono" style={{padding: 10, background: 'var(--bg-2)', borderRadius: 4, fontSize: 11.5, wordBreak: 'break-all', userSelect: 'all'}}>{secretReveal.secret}</div>
            </div>
            <div style={{fontSize: 11, color: 'var(--text-3)'}}>
              Header firma inviato dal dispatcher: <code>X-Veridanto-Signature: t={'{timestamp}'},v1={'{hmac_hex}'}</code>
            </div>
          </div>
        </Modal>
      )}
    </div>
  );
}

function NewWebhookModal({ onClose, onCreated, user, pushToast }) {
  const [name, setName] = React.useState('');
  const [url, setUrl] = React.useState('https://');
  const [eventCodes, setEventCodes] = React.useState('sla.warn, sla.breach');
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  async function handleCreate() {
    setSubmitting(true); setError(null);
    try {
      const eventList = eventCodes.split(',').map(s => s.trim()).filter(Boolean);
      const res = await fetch('/api/webhook-subscriptions', {
        method: 'POST',
        headers: { 'content-type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        body: JSON.stringify({ name, url, eventCodes: eventList }),
      });
      const json = await res.json().catch(() => ({}));
      if (!res.ok) {
        setError(json.error || `HTTP ${res.status}`);
        return;
      }
      pushToast({ title: 'Webhook creato', desc: name, tone: 'ok' });
      onCreated({ id: json.data.id, secret: json.secretOnce });
    } catch (err) {
      setError(String(err?.message || err));
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <Modal open onClose={onClose} title="Nuovo webhook outbound" size="md" footer={<>
      <Btn variant="ghost" size="sm" onClick={onClose} disabled={submitting}>Annulla</Btn>
      <Btn variant="primary" size="sm" onClick={handleCreate} disabled={submitting || !name || !url || !eventCodes}>{submitting ? 'Creazione…' : 'Crea webhook'}</Btn>
    </>}>
      <div className="col" style={{gap: 12}}>
        {error && <div style={{padding: '8px 10px', border: '1px solid var(--err)', borderRadius: 4, background: 'rgba(192,57,43,0.08)', color: 'var(--err)', fontSize: 12}}>{error}</div>}
        <div className="field"><label>Nome subscription</label><input value={name} onChange={e => setName(e.target.value)} placeholder="es. SAP integration"/></div>
        <div className="field"><label>URL endpoint (HTTPS)</label><input value={url} onChange={e => setUrl(e.target.value)} placeholder="https://example.com/webhook"/></div>
        <div className="field"><label>Eventi (virgola-separati o "*" per tutti)</label><input value={eventCodes} onChange={e => setEventCodes(e.target.value)} className="mono" style={{fontSize: 11}}/></div>
        <div style={{fontSize: 11, color: 'var(--text-3)'}}>
          Il secret HMAC verrà generato server-side e mostrato UNA VOLTA SOLA dopo la creazione. Salvalo immediatamente lato consumer.
        </div>
      </div>
    </Modal>
  );
}

Object.assign(window, {
  CustCategories, CustCapexClasses, CustSites, CustSLA, CustCalendars, CustDelegations, CustNotifications,
  CustAI, CustTemplates, CustVersions, CustUsers, CustWebhooks,
});
