// ============================================================
// forms.jsx — modali "Nuovo Vendor / RdA / Progetto" + shared UI
// ============================================================

// -------- Shared form primitives --------
function FormField({ label, hint, required, children, cols = 1, error }) {
  return (
    <div className="field" style={{ gridColumn: `span ${cols}`, margin: 0 }}>
      <label style={{ display: 'flex', gap: 4, alignItems: 'baseline' }}>
        <span>{label}</span>
        {required && <span style={{ color: 'var(--err)' }}>*</span>}
        {hint && <span className="spacer"/>}
        {hint && <span style={{ fontSize: 10.5, color: 'var(--text-3)', fontWeight: 400 }}>{hint}</span>}
      </label>
      {children}
      {error && <div style={{ fontSize: 10.5, color: 'var(--err)', marginTop: 3 }}>{error}</div>}
    </div>
  );
}

function FormGrid({ cols = 2, children, gap = 14 }) {
  return <div style={{ display: 'grid', gridTemplateColumns: `repeat(${cols}, 1fr)`, gap }}>{children}</div>;
}

function FormSection({ title, desc, children }) {
  return (
    <div style={{ marginBottom: 18 }}>
      <div style={{ paddingBottom: 8, borderBottom: '1px solid var(--line)', marginBottom: 12 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-0)' }}>{title}</div>
        {desc && <div style={{ fontSize: 11, color: 'var(--text-2)', marginTop: 2 }}>{desc}</div>}
      </div>
      {children}
    </div>
  );
}

function Stepper({ steps, current }) {
  return (
    <div className="row" style={{ gap: 0, marginBottom: 18, padding: '12px 0', borderBottom: '1px solid var(--line)' }}>
      {steps.map((s, i) => {
        const done = i < current;
        const active = i === current;
        return (
          <React.Fragment key={i}>
            <div className="row" style={{ gap: 8, flex: 'none' }}>
              <div style={{
                width: 24, height: 24, borderRadius: '50%',
                display: 'grid', placeItems: 'center',
                background: done ? 'var(--ok)' : active ? 'var(--accent)' : 'var(--bg-2)',
                color: done || active ? '#fff' : 'var(--text-3)',
                border: '1px solid ' + (done ? 'var(--ok)' : active ? 'var(--accent)' : 'var(--line)'),
                fontSize: 11, fontWeight: 600,
              }}>
                {done ? <Icon name="check" size={11}/> : i + 1}
              </div>
              <div>
                <div style={{ fontSize: 11.5, fontWeight: active ? 600 : 400, color: active ? 'var(--text-0)' : 'var(--text-2)' }}>{s.label}</div>
                <div style={{ fontSize: 10, color: 'var(--text-3)' }}>{s.hint}</div>
              </div>
            </div>
            {i < steps.length - 1 && <div style={{ flex: 1, height: 1, background: done ? 'var(--ok)' : 'var(--line)', margin: '0 12px' }}/>}
          </React.Fragment>
        );
      })}
    </div>
  );
}

// -------- Editable detail modal hook (FASE 3a.15) --------
//
// useEditableEntity(initial, opts) — hook helper per Detail modal con:
//   - form state controllato (set, setForm)
//   - dirty tracking (deep-equal su snapshot iniziale)
//   - PATCH submit con loading + serverError + audit-friendly headers
//   - reset all'apertura/cambio entità
//
// Pattern di consumo:
//   const { form, set, setForm, isDirty, saving, serverError, save, reset } =
//     useEditableEntity(initialForm, {
//       url: () => `/api/config/categories/${sel.id}`,
//       actorId: user?.id,
//       onSaved: (json) => { updateCategory(json.data); pushToast(...); },
//       buildBody: (form) => ({ name: form.name, ... }), // mapping form → body PATCH
//     });
//
// L'hook NON gestisce validation (lasciato al chiamante via `valid` flag prop)
// né close-after-save (chiamante decide se chiudere o restare aperto).

// FASE 3a.16+: hook helper per il pattern "GET pre-fetch al mount" (vedi
// memory-bank/patterns/get-pre-fetch-detail-modal.md). Quando `sel` cambia,
// fa un GET su `urlFn(sel.id)` e ritorna `serverData`. Se assente o errore,
// resta null e il chiamante usa il fallback (`serverData ?? sel`).
//
// Race-condition safe: usa il flag `cancelled` interno per evitare set state
// dopo unmount o cambio `sel.id` rapido.
function useFetchedDetail(sel, urlFn) {
  const [serverData, setServerData] = React.useState(null);
  React.useEffect(() => {
    if (!sel) { setServerData(null); return; }
    let cancelled = false;
    const url = typeof urlFn === 'function' ? urlFn(sel) : urlFn;
    if (!url) return;
    fetch(url)
      .then(r => r.ok ? r.json() : null)
      .then(j => { if (!cancelled && j?.data) setServerData(j.data); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [sel?.id, urlFn]);
  return serverData;
}

// Traduce una risposta di errore (status + body) in un messaggio CHIARO e azionabile
// per l'utente, invece di un opaco "HTTP 500" o "forbidden".
function buildSaveErrorMessage(status, json) {
  if (json && json.error === 'validation_error' && Array.isArray(json.issues)) {
    return 'Dati non validi — ' + json.issues.map((i) => `${(i.path || []).join('.') || 'campo'}: ${i.message}`).join(' · ');
  }
  const detail = json && json.detail ? ` (${json.detail})` : '';
  switch (status) {
    case 400: return `Richiesta non valida${detail}.`;
    case 401: return 'Sessione non riconosciuta — esci e rientra, poi riprova.';
    case 403: return `Permesso insufficiente: per modificare la configurazione serve il permesso "config.update"${detail}.`;
    case 404: return 'Elemento non trovato (forse eliminato, o appartiene a un altro tenant).';
    case 409: return `Conflitto: lo stato è cambiato, ricarica e riprova${detail}.`;
    case 422: return `Regola di validazione non rispettata${detail || (json && json.error ? ` (${json.error})` : '')}.`;
    case 500:
    case 502:
    case 503: return `Errore del server (${status})${detail} — riprova tra poco.`;
    default: return json && json.error ? `${json.error}${detail}` : `Errore HTTP ${status}.`;
  }
}

function notifySaveError(msg) {
  // eslint-disable-next-line no-console
  console.error('[useEditableEntity] save error:', msg);
  if (typeof window !== 'undefined' && typeof window.__pushToast === 'function') {
    window.__pushToast({ title: 'Salvataggio non riuscito', desc: msg, tone: 'err' });
  }
}

function useEditableEntity(initial, opts = {}) {
  const [form, setForm] = React.useState(initial);
  const [original, setOriginal] = React.useState(() => deepClone(initial));
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);

  // Reset quando initial cambia (es. nuova row selezionata)
  React.useEffect(() => {
    setForm(initial);
    setOriginal(deepClone(initial));
    setServerError(null);
    setSaving(false);
  }, [JSON.stringify(initial)]);

  const isDirty = React.useMemo(() => {
    return JSON.stringify(form) !== JSON.stringify(original);
  }, [form, original]);

  const set = React.useCallback((key, value) => {
    setForm((f) => ({ ...f, [key]: value }));
  }, []);

  const reset = React.useCallback(() => {
    setForm(deepClone(original));
    setServerError(null);
  }, [original]);

  const save = React.useCallback(async () => {
    if (saving) return null;
    setServerError(null);
    setSaving(true);
    try {
      const url = typeof opts.url === 'function' ? opts.url() : opts.url;
      if (!url) throw new Error('useEditableEntity: opts.url mancante');
      const body = opts.buildBody ? opts.buildBody(form) : form;
      const res = await fetch(url, {
        method: opts.method || 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          'X-Actor-Persona-Id': opts.actorId || '',
        },
        body: JSON.stringify(body),
      });
      const json = await res.json().catch(() => null);
      if (!res.ok) {
        const msg = buildSaveErrorMessage(res.status, json);
        setServerError(msg);
        setSaving(false);
        notifySaveError(msg);
        return null;
      }
      // Sync original con dati server (es. timestamps aggiornati)
      const next = (opts.normalizeAfterSave ? opts.normalizeAfterSave(json, form) : form);
      setForm(next);
      setOriginal(deepClone(next));
      setSaving(false);
      if (opts.onSaved) opts.onSaved(json);
      return json;
    } catch (err) {
      const msg = err?.message ? `Errore di rete: ${err.message}` : 'Errore di rete — controlla la connessione e riprova.';
      setServerError(msg);
      setSaving(false);
      notifySaveError(msg);
      return null;
    }
  }, [form, saving, opts]);

  return { form, setForm, set, isDirty, saving, serverError, save, reset, setServerError };
}

function deepClone(obj) {
  if (obj === null || obj === undefined) return obj;
  if (typeof obj !== 'object') return obj;
  return JSON.parse(JSON.stringify(obj));
}

// -------- Simple attachment list (for forms that don't need the full Uploader) --------
function AttachmentsList({ files, onAdd, onRemove, required = [] }) {
  const inputRef = React.useRef();
  return (
    <div style={{ border: '1px dashed var(--line)', borderRadius: 8, padding: 10, background: 'var(--bg-2)' }}>
      <div className="row" style={{ gap: 8, alignItems: 'center', marginBottom: 8 }}>
        <Icon name="attach" size={13}/>
        <span style={{ fontSize: 11.5, color: 'var(--text-2)' }}>{files.length} file · PDF, DOC, XLS, immagini</span>
        <span className="spacer"/>
        <Btn variant="ghost" size="sm" onClick={() => inputRef.current?.click()}><Icon name="upload" size={11}/> Aggiungi</Btn>
      </div>
      <input ref={inputRef} type="file" multiple style={{ display: 'none' }} onChange={(e) => {
        const list = Array.from(e.target.files || []);
        list.forEach(f => onAdd({ name: f.name, size: f.size, type: f.type, kind: guessKind(f.name) }));
        e.target.value = '';
      }}/>
      {files.length === 0 && required.length > 0 && (
        <div style={{ fontSize: 11, color: 'var(--text-3)', fontStyle: 'italic' }}>
          Allegati consigliati: {required.join(', ')}
        </div>
      )}
      {files.map((f, i) => (
        <div key={i} className="row" style={{ gap: 8, padding: '5px 0', borderBottom: i === files.length - 1 ? 'none' : '1px dashed var(--line)', fontSize: 11.5 }}>
          <Icon name="file_pdf" size={12}/>
          <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{f.name}</span>
          <Chip>{f.kind}</Chip>
          <span style={{ color: 'var(--text-3)', fontSize: 10.5 }} className="mono">{fmtBytes(f.size)}</span>
          <button className="btn ghost icon" onClick={() => onRemove(i)} style={{ padding: 2 }}><Icon name="x" size={11}/></button>
        </div>
      ))}
    </div>
  );
}

function fmtBytes(n) {
  if (!n) return '—';
  if (n < 1024) return n + ' B';
  if (n < 1024*1024) return (n/1024).toFixed(0) + ' KB';
  return (n/(1024*1024)).toFixed(1) + ' MB';
}
function guessKind(name) {
  const n = name.toLowerCase();
  if (n.includes('offerta') || n.includes('quote')) return 'offerta';
  if (n.includes('specifica') || n.includes('spec')) return 'specifica';
  if (n.includes('capitolato')) return 'capitolato';
  if (n.includes('contratto') || n.includes('contract')) return 'contratto';
  if (n.includes('durc')) return 'DURC';
  if (n.includes('iso')) return 'certificazione';
  if (n.includes('camerale') || n.includes('cciaa')) return 'camerale';
  if (n.includes('checklist')) return 'checklist';
  if (n.includes('gantt') || n.includes('piano')) return 'piano';
  if (n.includes('budget') || n.includes('cba')) return 'budget';
  return 'documento';
}

Object.assign(window, { FormField, FormGrid, FormSection, Stepper, AttachmentsList, fmtBytes });

// ============================================================
// NEW VENDOR MODAL
// ============================================================
function NewVendorModal({ open, onClose }) {
  const { addVendor, pushToast, seed, user, extras } = useStore();
  const empty = { name: '', category: '', country: 'IT', vatId: '', website: '', contact: '', email: '', phone: '', rating: 4.0, onTime: 85, projects: 0, risk: 'low', certifications: [], notes: '', files: [], paymentTerms: '60gg DF' };
  const [f, setF] = React.useState(empty);
  const [errors, setErrors] = React.useState({});
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  React.useEffect(() => {
    if (open) {
      setF(empty);
      setErrors({});
      setServerError(null);
      setSaving(false);
    }
  }, [open]);

  const set = (k, v) => setF(s => ({ ...s, [k]: v }));
  const toggleCert = (c) => setF(s => ({ ...s, certifications: s.certifications.includes(c) ? s.certifications.filter(x => x !== c) : [...s.certifications, c] }));

  const validate = () => {
    const e = {};
    if (!f.name.trim()) e.name = 'Ragione sociale obbligatoria';
    if (!f.category) e.category = 'Seleziona una categoria';
    if (!f.vatId.trim()) e.vatId = 'P.IVA obbligatoria';
    if (!f.email.trim()) e.email = 'Email obbligatoria';
    setErrors(e);
    return Object.keys(e).length === 0;
  };

  // FASE 2b.3: il submit chiama POST /api/vendors e usa la response come fonte di verità
  // per id/timestamp. Su errore validazione/rete restiamo nel modal con banner inline.
  // I campi non persistiti server-side (contact, phone, notes, files) restano client-only.
  const submit = async () => {
    if (!validate() || saving) return;
    setServerError(null);
    setSaving(true);
    const payload = {
      name: f.name.trim(),
      country: f.country,
      category: f.category || null,
      rating: Number(f.rating),
      projects: Number(f.projects),
      onTime: Number(f.onTime),
      risk: f.risk,
      website: f.website || null,
      contactEmail: f.email.trim() || null,
      taxId: f.vatId.trim() || null,
      paymentTerms: f.paymentTerms || null,
      certifications: f.certifications,
    };
    try {
      const res = await fetch('/api/vendors', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Actor-Persona-Id': user?.id || '',
        },
        body: JSON.stringify(payload),
      });
      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;
      addVendor(created);
      pushToast({
        title: 'Vendor creato',
        desc: `${created.name} (${created.id}) salvato nel database. Verifiche documentali automatiche avviate.`,
        tone: 'ok',
      });
      setSaving(false);
      onClose();
    } catch (err) {
      setServerError(err?.message || 'Errore di rete');
      setSaving(false);
    }
  };

  const certOptions = ['ISO 9001', 'ISO 14001', 'ISO 45001', 'IATF 16949', 'AS9100', 'ISO 27001'];
  const countries = ['IT', 'DE', 'FR', 'UK', 'ES', 'US', 'JP', 'CN', 'CH', 'AT', 'PL', 'DK'];
  const risks = ['low', 'medium', 'high'];

  return (
    <Modal open={open} onClose={onClose} size="lg" title="Nuovo vendor" footer={
      <>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <Btn variant="ai" size="sm" onClick={() => pushToast({ title: 'Precompilazione AI', desc: 'Dati anagrafici recuperati da registro imprese (simulato).', tone: 'ok' })} disabled={saving}>
          <Icon name="sparkle" size={11}/> Precompila da P.IVA
        </Btn>
        <Btn variant="primary" size="sm" onClick={submit} disabled={saving}>{saving ? 'Salvataggio…' : 'Crea vendor'}</Btn>
      </>
    }>
      {serverError && (
        <div style={{ margin: '0 0 12px', 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>
      )}
      <FormSection title="Anagrafica" desc="Dati base del fornitore">
        <FormGrid cols={2}>
          <FormField label="Ragione sociale" required error={errors.name}>
            <input value={f.name} onChange={e => set('name', e.target.value)} placeholder="es. Siemens Italia S.p.A."/>
          </FormField>
          <FormField label="P.IVA / VAT ID" required error={errors.vatId}>
            <input value={f.vatId} onChange={e => set('vatId', e.target.value)} placeholder="IT00751160152" className="mono"/>
          </FormField>
          <FormField label="Categoria merceologica" required error={errors.category}>
            {/* s127: autocomplete (typeahead + spazio→tendina). FASE 3a.1: merge extras.categoriesExt + seed.CATEGORIES, dedup. */}
            {(() => {
              const extraNames = (extras?.categoriesExt || []).map(c => c.name).filter(Boolean);
              const merged = [...extraNames, ...(seed.CATEGORIES || []).filter(n => !extraNames.includes(n))];
              const opts = [...merged.map(c => ({ value: c, label: c })), { value: 'Altro', label: 'Altro' }];
              return <window.Autocomplete value={f.category} onChange={v => set('category', v)} options={opts} placeholder="Cerca categoria… (spazio per lista)" testId="vendor-category-ac" />;
            })()}
          </FormField>
          <FormField label="Paese">
            <select value={f.country} onChange={e => set('country', e.target.value)}>
              {countries.map(c => <option key={c} value={c}>{c}</option>)}
            </select>
          </FormField>
          <FormField label="Sito web">
            <input value={f.website} onChange={e => set('website', e.target.value)} placeholder="https://"/>
          </FormField>
          <FormField label="Livello di rischio iniziale">
            <div className="row" style={{ gap: 4 }}>
              {risks.map(r => (
                <button key={r} type="button" className={`btn sm ${f.risk === r ? 'primary' : 'ghost'}`} onClick={() => set('risk', r)}>
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: r === 'low' ? 'var(--ok)' : r === 'medium' ? 'var(--warn)' : 'var(--err)', display: 'inline-block', marginRight: 4 }}/>
                  {r}
                </button>
              ))}
            </div>
          </FormField>
        </FormGrid>
      </FormSection>

      <FormSection title="Contatto commerciale">
        <FormGrid cols={2}>
          <FormField label="Nome referente"><input value={f.contact} onChange={e => set('contact', e.target.value)} placeholder="Nome Cognome"/></FormField>
          <FormField label="Email" required error={errors.email}><input type="email" value={f.email} onChange={e => set('email', e.target.value)} placeholder="referente@vendor.com"/></FormField>
          <FormField label="Telefono"><input value={f.phone} onChange={e => set('phone', e.target.value)} placeholder="+39 ..."/></FormField>
          <FormField label="Referente tecnico"><input placeholder="opzionale"/></FormField>
        </FormGrid>
      </FormSection>

      <FormSection title="Qualificazione" desc="Valutazione iniziale · l'AI aggiornerà su base storica">
        <FormGrid cols={3}>
          <FormField label="Rating iniziale (0-5)"><input type="number" step="0.1" min="0" max="5" value={f.rating} onChange={e => set('rating', e.target.value)}/></FormField>
          <FormField label="On-time atteso (%)"><input type="number" min="0" max="100" value={f.onTime} onChange={e => set('onTime', e.target.value)}/></FormField>
          <FormField label="Progetti storici"><input type="number" min="0" value={f.projects} onChange={e => set('projects', e.target.value)}/></FormField>
          <FormField label="Termini di pagamento" cols={3}>
            <select value={f.paymentTerms} onChange={e => set('paymentTerms', e.target.value)}>
              {['30gg DF','60gg DF','90gg DF','Anticipo 30% + saldo','Milestone 20/40/40','Contrassegno'].map(t => <option key={t}>{t}</option>)}
            </select>
          </FormField>
        </FormGrid>
        <div style={{ marginTop: 12 }}>
          <label style={{ fontSize: 11.5, color: 'var(--text-2)', display: 'block', marginBottom: 6 }}>Certificazioni possedute</label>
          <div className="row" style={{ gap: 6, flexWrap: 'wrap' }}>
            {certOptions.map(c => (
              <button key={c} type="button" className={`btn sm ${f.certifications.includes(c) ? 'primary' : 'ghost'}`} onClick={() => toggleCert(c)}>{c}</button>
            ))}
          </div>
        </div>
      </FormSection>

      <FormSection title="Documentazione" desc="Allegati obbligatori per l'accreditamento">
        <AttachmentsList
          files={f.files}
          onAdd={(file) => set('files', [...f.files, file])}
          onRemove={(i) => set('files', f.files.filter((_, j) => j !== i))}
          required={['Visura camerale', 'DURC', 'ISO 9001', 'Scheda anagrafica']}
        />
      </FormSection>

      <FormSection title="Note">
        <FormField label=""><textarea rows={3} value={f.notes} onChange={e => set('notes', e.target.value)} placeholder="Note interne, criteri di accreditamento, condizioni contrattuali..."/></FormField>
      </FormSection>
    </Modal>
  );
}

// ============================================================
// NEW PROJECT MODAL — multi-step
// ============================================================
function NewProjectModal({ open, onClose }) {
  const { addProject, addMilestone, addProjectDoc, pushToast, seed, user, extras } = useStore();
  const [saving, setSaving] = React.useState(false);
  const [serverError, setServerError] = React.useState(null);
  // FASE 1 Cockpit: catalogo ruoli configurabili fetchato al mount del modal.
  const [projectRoles, setProjectRoles] = React.useState([]);
  const empty = {
    // step 1 anagrafica
    code: '', name: '', description: '', site: '', category: '', pm: 'u01', sponsor: '',
    // step 2 economics
    budget: '', capexClass: 'growth', contingency: 10, currency: 'EUR',
    costCenter: '', wbsRoot: '',
    // step 3 planning
    start: '', end: '', phase: 'requisito', health: 'ok', progress: 0,
    milestones: [
      { title: 'Requisiti approvati', due: '', status: 'planned' },
      { title: 'RdA emessa', due: '', status: 'planned' },
      { title: 'Aggiudicazione vendor', due: '', status: 'planned' },
      { title: 'FAT / SAT', due: '', status: 'planned' },
      { title: 'Collaudo & chiusura', due: '', status: 'planned' },
    ],
    // step 4 team & stakeholders — Fase 1 Cockpit
    // team shape: Array<{personaId, roleInProject}> (POST /api/projects accetta members[])
    team: [], stakeholders: '',
    // step 5 risk & compliance
    risks: [], compliance: [], businessCase: '',
    // step 6 attachments
    files: [],
  };
  const [f, setF] = React.useState(empty);
  const [step, setStep] = React.useState(0);
  const [errors, setErrors] = React.useState({});
  React.useEffect(() => { if (open) { setF(empty); setStep(0); setErrors({}); setServerError(null); setSaving(false); } }, [open]);

  // FASE 1 Cockpit: fetch catalogo ruoli all'apertura del modal.
  React.useEffect(() => {
    if (!open) return;
    let aborted = false;
    fetch('/api/config/project-roles', {
      headers: { 'X-Actor-Persona-Id': user?.id || '' },
    })
      .then((r) => r.json().catch(() => ({})))
      .then((json) => {
        if (aborted) return;
        if (Array.isArray(json?.data)) setProjectRoles(json.data);
      })
      .catch(() => { if (!aborted) setProjectRoles([]); });
    return () => { aborted = true; };
  }, [open, user?.id]);

  const set = (k, v) => setF(s => ({ ...s, [k]: v }));
  const setMs = (i, patch) => set('milestones', f.milestones.map((m, idx) => idx === i ? { ...m, ...patch } : m));
  const addMs = () => set('milestones', [...f.milestones, { title: '', due: '', status: 'planned' }]);
  const rmMs = (i) => set('milestones', f.milestones.filter((_, idx) => idx !== i));

  const steps = [
    { label: 'Anagrafica', hint: 'Nome, sito, categoria' },
    { label: 'Economics', hint: 'Budget, CAPEX class' },
    { label: 'Planning', hint: 'Date e milestones' },
    { label: 'Team & Stakeholder', hint: 'Responsabili' },
    { label: 'Rischi & Compliance', hint: 'Governance' },
    { label: 'Allegati', hint: 'Business case, drawings' },
  ];

  const validateStep = () => {
    const e = {};
    if (step === 0) {
      if (!f.name.trim()) e.name = 'Nome obbligatorio';
      if (!f.site) e.site = 'Sito obbligatorio';
      if (!f.category) e.category = 'Categoria obbligatoria';
    }
    if (step === 1) {
      if (!f.budget || Number(f.budget) <= 0) e.budget = 'Budget > 0';
    }
    if (step === 2) {
      if (!f.start) e.start = 'Data inizio obbligatoria';
      if (!f.end) e.end = 'Data fine obbligatoria';
      else if (f.start && f.end && new Date(f.end) <= new Date(f.start)) e.end = 'La fine deve essere > inizio';
    }
    setErrors(e);
    return Object.keys(e).length === 0;
  };
  const next = () => { if (validateStep()) setStep(s => Math.min(s + 1, steps.length - 1)); };
  const back = () => setStep(s => Math.max(s - 1, 0));

  // FASE 2b.1: il submit chiama POST /api/projects e usa la response come fonte
  // di verità per id/code/timestamps. Su errore validazione/rete restiamo nel modal.
  const submit = async () => {
    if (!validateStep() || saving) return;
    setServerError(null);
    setSaving(true);
    const payload = {
      name: f.name,
      description: f.description || null,
      site: f.site,
      category: f.category,
      pm: f.pm || null,
      sponsor: f.sponsor || null,
      budget: Number(f.budget),
      capexClass: f.capexClass,
      contingency: Number(f.contingency),
      costCenter: f.costCenter || null,
      wbsRoot: f.wbsRoot || null,
      start: f.start,
      end: f.end,
      phase: f.phase,
      health: f.health,
      progress: Number(f.progress),
    };
    if (f.code) payload.code = f.code;
    // FASE 1 Cockpit: include team iniziale. La route esegue INSERT progetto +
    // INSERT N project_member in transazione e auto-aggiunge il PM se non
    // già presente (vedi apps/web/src/app/api/projects/route.ts).
    if (Array.isArray(f.team) && f.team.length > 0) {
      payload.members = f.team.map(m => ({
        personaId: m.personaId,
        roleInProject: m.roleInProject,
        ...(m.notes ? { notes: m.notes } : {}),
      }));
    }
    try {
      const res = await fetch('/api/projects', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Actor-Persona-Id': user?.id || '',
        },
        body: JSON.stringify(payload),
      });
      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?.detail || json?.error || `Errore HTTP ${res.status}`);
        setServerError(msg);
        setSaving(false);
        return;
      }
      const created = json.data;
      addProject(created);
      // FASE 2b.4: persistere le milestone in DB via /api/projects/<id>/milestones
      // (non più solo `addMilestone()` locale, che si perdeva al refresh).
      const validMilestones = f.milestones.filter(m => m.title);
      let savedMs = 0;
      let failedMs = 0;
      for (let i = 0; i < validMilestones.length; i++) {
        const m = validMilestones[i];
        try {
          const msRes = await fetch(`/api/projects/${created.id}/milestones`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'X-Actor-Persona-Id': user?.id || '' },
            body: JSON.stringify({
              title: m.title,
              due: m.due || null,
              status: m.status || 'planned',
              orderIndex: i,
            }),
          });
          if (msRes.ok) {
            const msJson = await msRes.json().catch(() => null);
            if (msJson?.data) {
              addMilestone(created.id, msJson.data);
              savedMs++;
            }
          } else {
            failedMs++;
          }
        } catch {
          failedMs++;
        }
      }
      // Allegati: ancora solo locali (FASE 2b.6 introdurrà MinIO/S3).
      f.files.forEach(file => addProjectDoc(created.id, { title: file.name, kind: file.kind, size: fmtBytes(file.size), uploaded: new Date().toISOString().slice(0, 10) }));
      pushToast({
        title: 'Progetto creato',
        desc: `${created.name} (${created.id}) salvato. ${savedMs} milestone in DB${failedMs ? ` (${failedMs} fallite)` : ''}, ${f.files.length} allegati locali.`,
        tone: failedMs ? 'warn' : 'ok',
      });
      setSaving(false);
      onClose();
    } catch (err) {
      setServerError(err?.message || 'Errore di rete');
      setSaving(false);
    }
  };

  const risksPool = ['Rischio budget', 'Rischio schedule', 'Vendor single-source', 'Normativo / compliance', 'Tecnico / integrazione', 'Risorse interne', 'Cambio scope', 'Supply chain', 'Cambio valutario'];
  const compliancePool = ['ISO 9001 QMS', 'ISO 14001 Ambiente', 'ISO 45001 Sicurezza', 'ATEX', 'CE Macchine 2006/42', 'NIS2 / Cyber', 'GDPR', 'Bilancio sostenibilità'];
  // (era `teamRoles` hardcoded — rimosso Fase 1 Cockpit: il catalogo viene dal DB via projectRoles)

  return (
    <Modal open={open} onClose={onClose} size="lg" title="Nuovo progetto CAPEX" footer={
      <>
        <Btn variant="ghost" size="sm" onClick={onClose} disabled={saving}>Annulla</Btn>
        <span className="spacer"/>
        {step > 0 && <Btn variant="ghost" size="sm" onClick={back} disabled={saving}><Icon name="chev_l" size={11}/> Indietro</Btn>}
        {step < steps.length - 1
          ? <Btn variant="primary" size="sm" onClick={next} disabled={saving}>Avanti <Icon name="chev_r" size={11}/></Btn>
          : <Btn variant="primary" size="sm" onClick={submit} disabled={saving}>{saving ? 'Salvataggio…' : 'Crea progetto'}</Btn>}
      </>
    }>
      <Stepper steps={steps} current={step}/>
      {serverError && (
        <div style={{ margin: '10px 0', 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>
      )}

      {step === 0 && (
        <FormSection title="Anagrafica progetto" desc="Informazioni base identificative">
          <FormGrid cols={2}>
            <FormField label="Nome progetto" required error={errors.name} cols={2}>
              <input value={f.name} onChange={e => set('name', e.target.value)} placeholder="es. Linea assemblaggio ala EFA — fase 4"/>
            </FormField>
            <FormField label="Codice interno" hint="auto se vuoto">
              <input value={f.code} onChange={e => set('code', e.target.value)} placeholder="DEMO-CMR-ATL-015" className="mono"/>
            </FormField>
            <FormField label="Categoria CAPEX" required error={errors.category}>
              {/* s127: autocomplete. FASE 3a.1: include categorie aggiunte in sessione via Customizing */}
              {(() => {
                const extraNames = (extras?.categoriesExt || []).map(c => c.name).filter(Boolean);
                const merged = [...extraNames, ...(seed.CATEGORIES || []).filter(n => !extraNames.includes(n))];
                const opts = merged.map(c => ({ value: c, label: c }));
                return <window.Autocomplete value={f.category} onChange={v => set('category', v)} options={opts} placeholder="Cerca categoria… (spazio per lista)" testId="project-category-ac" />;
              })()}
            </FormField>
            <FormField label="Sito produttivo" required error={errors.site}>
              <window.Autocomplete
                value={f.site}
                onChange={v => set('site', v)}
                options={(seed.SITES || []).map(s => ({ value: s, label: s }))}
                placeholder="Cerca sito… (spazio per lista)"
                testId="project-site-ac"
              />
            </FormField>
            <FormField label="Project Manager">
              <PersonaAutocomplete
                value={f.pm || null}
                onChange={(id) => set('pm', id || '')}
                personasFallback={seed.PERSONAS || []}
                boostRoleIds={['PM_CAPEX']}
                placeholder="Cerca project manager…"
              />
            </FormField>
            <FormField label="Sponsor / approver esecutivo">
              <PersonaAutocomplete
                value={f.sponsor || null}
                onChange={(id) => set('sponsor', id || '')}
                personasFallback={seed.PERSONAS || []}
                placeholder="Cerca sponsor…"
              />
            </FormField>
            <FormField label="Descrizione / obiettivo" cols={2}>
              <textarea rows={3} value={f.description} onChange={e => set('description', e.target.value)} placeholder="Perché questo progetto, cosa realizza, quale problema risolve..."/>
            </FormField>
          </FormGrid>
        </FormSection>
      )}

      {step === 1 && (
        <FormSection title="Economics" desc="Budget, classificazione CAPEX, centro di costo">
          <FormGrid cols={3}>
            <FormField label="Budget totale (€)" required error={errors.budget}>
              <input type="number" min="0" value={f.budget} onChange={e => set('budget', e.target.value)} placeholder="0" className="num"/>
            </FormField>
            <FormField label="Valuta">
              <select value={f.currency} onChange={e => set('currency', e.target.value)}><option>EUR</option><option>USD</option><option>GBP</option></select>
            </FormField>
            <FormField label="Contingency (%)">
              <input type="number" min="0" max="50" value={f.contingency} onChange={e => set('contingency', e.target.value)}/>
            </FormField>
            <FormField label="Classificazione CAPEX" cols={3}>
              <div className="row" style={{ gap: 4, flexWrap: 'wrap' }}>
                {[['growth','Growth / Espansione'],['maintenance','Maintenance'],['compliance','Compliance'],['efficiency','Efficiency'],['hse','HSE / Sicurezza'],['it_ot','IT / OT'],['replacement','Replacement'],['new_capacity','Nuova capacità'],['strategic','Strategico / R&D']].map(([v, l]) => (
                  <button key={v} type="button" className={`btn sm ${f.capexClass === v ? 'primary' : 'ghost'}`} onClick={() => set('capexClass', v)}>{l}</button>
                ))}
              </div>
            </FormField>
            <FormField label="Centro di costo"><input value={f.costCenter} onChange={e => set('costCenter', e.target.value)} className="mono" placeholder="CC-4312"/></FormField>
            <FormField label="WBS root"><input value={f.wbsRoot} onChange={e => set('wbsRoot', e.target.value)} className="mono" placeholder="WBS-2026-P15"/></FormField>
          </FormGrid>
          <div style={{ marginTop: 12, padding: 10, background: 'var(--bg-2)', borderRadius: 8, fontSize: 11.5, color: 'var(--text-2)', border: '1px solid var(--line)' }}>
            <Icon name="sparkle" size={11}/> L'AI confronterà automaticamente il budget inserito con progetti simili dell'archivio storico e segnalerà scostamenti significativi nella sezione Benchmark.
          </div>
        </FormSection>
      )}

      {step === 2 && (
        <FormSection title="Pianificazione & milestones" desc="Date chiave e avanzamento atteso">
          <FormGrid cols={3}>
            <FormField label="Data inizio" required error={errors.start}>
              <input type="date" value={f.start} onChange={e => set('start', e.target.value)}/>
            </FormField>
            <FormField label="Data fine prevista" required error={errors.end}>
              <input type="date" value={f.end} onChange={e => set('end', e.target.value)}/>
            </FormField>
            <FormField label="Fase iniziale">
              <select value={f.phase} onChange={e => set('phase', e.target.value)}>
                {['requisito','pianificazione','offerta','rda','esecuzione','chiusura'].map(p => <option key={p} value={p}>{phaseLabel(p)}</option>)}
              </select>
            </FormField>
          </FormGrid>
          <div style={{ marginTop: 16 }}>
            <div className="row" style={{ marginBottom: 8 }}>
              <label style={{ fontSize: 11.5, color: 'var(--text-2)' }}>Milestones del progetto</label>
              <span className="spacer"/>
              <Btn variant="ghost" size="sm" onClick={addMs}><Icon name="plus" size={11}/> Aggiungi</Btn>
            </div>
            <div style={{ border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
              <table className="tbl dense" style={{ margin: 0 }}>
                <thead><tr><th>Titolo milestone</th><th style={{width:150}}>Scadenza</th><th style={{width:140}}>Stato</th><th style={{width:30}}></th></tr></thead>
                <tbody>
                  {f.milestones.map((m, i) => (
                    <tr key={i}>
                      <td><input value={m.title} onChange={e => setMs(i, { title: e.target.value })} style={{ width: '100%' }} placeholder="es. Kick-off"/></td>
                      <td><input type="date" value={m.due} onChange={e => setMs(i, { due: e.target.value })} style={{ width: '100%' }}/></td>
                      <td>
                        <select value={m.status} onChange={e => setMs(i, { status: e.target.value })} style={{ width: '100%' }}>
                          <option value="planned">pianificata</option>
                          <option value="in_progress">in corso</option>
                          <option value="done">completata</option>
                          <option value="at_risk">a rischio</option>
                        </select>
                      </td>
                      <td><button className="btn ghost icon" onClick={() => rmMs(i)}><Icon name="x" size={11}/></button></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </FormSection>
      )}

      {step === 3 && (
        <FormSection title="Team & Stakeholder" desc="Chi lavora al progetto, chi deve essere informato">
          <div style={{ marginBottom: 12 }}>
            <label style={{ fontSize: 11.5, color: 'var(--text-2)', display: 'block', marginBottom: 6 }}>Team di progetto</label>
            {/* FASE 1 Cockpit — ProjectTeamEditor + auto-PM dal campo step 1.
                Il payload submit invia `members[]` a POST /api/projects che li
                inserisce in transazione (oltre ad auto-aggiungere il PM se non
                già presente). */}
            {(() => {
              // PM auto-locked: lo step 1 ha già selezionato un PM (f.pm).
              // Lo mostro come membro locked così l'utente sa che è incluso.
              const pmId = f.pm || null;
              const teamWithPm = pmId && !f.team.some(m => m.personaId === pmId && m.roleInProject === 'PM')
                ? [{ personaId: pmId, roleInProject: 'PM', locked: true }, ...f.team]
                : f.team.map(m => m.personaId === pmId && m.roleInProject === 'PM' ? { ...m, locked: true } : m);
              return (
                <ProjectTeamEditor
                  members={teamWithPm}
                  roles={projectRoles}
                  personasFallback={seed.PERSONAS || []}
                  onAdd={(m) => set('team', [...f.team, m])}
                  onRemove={(m) => set('team', f.team.filter(t => !(t.personaId === m.personaId && t.roleInProject === m.roleInProject)))}
                  // s130 — wizard creazione: record project_member ancora
                  // inesistente, cambio ruolo solo lato state locale. Sarà
                  // persistito al submit dal POST /api/projects con il batch
                  // members[]. (Il PM step1 è locked → editor non mostra l'AC).
                  onRoleChange={(m, newRoleCode) => {
                    if (!newRoleCode || newRoleCode === m.roleInProject) return;
                    set('team', f.team.map(t =>
                      t.personaId === m.personaId && t.roleInProject === m.roleInProject
                        ? { ...t, roleInProject: newRoleCode }
                        : t,
                    ));
                  }}
                  emptyHint={pmId ? `Il PM (${(seed.PERSONAS||[]).find(p=>p.id===pmId)?.name || pmId}) è già incluso. Aggiungi altri membri qui sotto.` : 'Nessun PM selezionato nello step 1 — torna indietro o aggiungi membri qui sotto.'}
                />
              );
            })()}
          </div>
          <FormField label="Stakeholder esterni" hint="separati da virgola">
            <textarea rows={2} value={f.stakeholders} onChange={e => set('stakeholders', e.target.value)} placeholder="Direzione, HR, controllo di gestione..."/>
          </FormField>
        </FormSection>
      )}

      {step === 4 && (
        <FormSection title="Rischi & Compliance" desc="Matrice iniziale e requisiti normativi">
          <label style={{ fontSize: 11.5, color: 'var(--text-2)', display: 'block', marginBottom: 6 }}>Rischi identificati</label>
          <div className="row" style={{ gap: 6, flexWrap: 'wrap', marginBottom: 14 }}>
            {risksPool.map(r => {
              const on = f.risks.includes(r);
              return <button key={r} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={() => set('risks', on ? f.risks.filter(x => x !== r) : [...f.risks, r])}>{r}</button>;
            })}
          </div>
          <label style={{ fontSize: 11.5, color: 'var(--text-2)', display: 'block', marginBottom: 6 }}>Compliance / Certificazioni applicabili</label>
          <div className="row" style={{ gap: 6, flexWrap: 'wrap', marginBottom: 14 }}>
            {compliancePool.map(c => {
              const on = f.compliance.includes(c);
              return <button key={c} type="button" className={`btn sm ${on ? 'primary' : 'ghost'}`} onClick={() => set('compliance', on ? f.compliance.filter(x => x !== c) : [...f.compliance, c])}>{c}</button>;
            })}
          </div>
          <FormField label="Business case / razionale approvazione">
            <textarea rows={4} value={f.businessCase} onChange={e => set('businessCase', e.target.value)} placeholder="Payback atteso, NPV, IRR, vantaggi competitivi..."/>
          </FormField>
        </FormSection>
      )}

      {step === 5 && (
        <FormSection title="Allegati iniziali" desc="Business case, disegni, autorizzazioni, offerte indicative">
          <AttachmentsList
            files={f.files}
            onAdd={(file) => set('files', [...f.files, file])}
            onRemove={(i) => set('files', f.files.filter((_, j) => j !== i))}
            required={['Business case', 'Layout / disegni', 'Preventivi indicativi', 'Autorizzazione CFO', 'Stima risparmi / benefici']}
          />
          <div style={{ marginTop: 14, padding: 10, background: 'var(--bg-2)', borderRadius: 8, fontSize: 11.5, color: 'var(--text-2)', border: '1px solid var(--line)' }}>
            <Icon name="sparkle" size={11}/> Riepilogo: <strong>{f.name || '(senza nome)'}</strong> · {fmtEUR(Number(f.budget) || 0, true)} · {f.milestones.length} milestones · {f.files.length} allegati.
          </div>
        </FormSection>
      )}
    </Modal>
  );
}

Object.assign(window, { NewVendorModal, NewProjectModal });
