// ============================================================
// Alerts.jsx — FASE 16 (sessione 61) — integrazione anomaly_flag live
// ============================================================
//
// Sostituisce il vecchio mock `seed.ALERTS` con dati live da
// `/api/anomaly-flags`. Detail panel con bottoni Ack/Risolvi/Riapri che
// chiamano PATCH `/api/anomaly-flags/[id]` (state machine sessione 48).
//
// Lo schema è quello di `anomaly_flag` (FASE 11.H sessione 47): id, severity
// (low|medium|high|critical), state (open|resolved|dismissed), title,
// description, entityType, entityId, flaggedBy, resolvedBy, resolvedAt,
// resolutionNote, createdAt.
//
// Detail panel evidence:
//  - Severity + state chip + audit timeline (flagged at/by, resolved at/by, note)
//  - Entity link contestuale ("Vai al progetto/RDA/vendor/document")
//  - Bottoni state machine: Ack (dismiss), Risolvi… (apre modal con note), Riapri
//
// Pattern coerente con CustAnomalies (sessione 48): riusa AnomalyResolveModal
// per la transizione resolved (note obbligatoria, min 3 char).

const ALERT_SEVERITY_TONES = { critical: 'err', high: 'err', medium: 'warn', low: 'info' };
const ALERT_SEVERITY_LABELS = { critical: 'critica', high: 'alta', medium: 'media', low: 'bassa' };
const ALERT_STATE_LABELS = { open: 'aperta', resolved: 'risolta', dismissed: 'archiviata' };
const ALERT_STATE_TONES = { open: 'warn', resolved: 'ok', dismissed: '' };
const ENTITY_NAV_TARGET = { project: 'project_detail', rda: 'rda', vendor: 'vendors', milestone: 'project_detail', document: 'documents' };

const EVIDENCE_TYPE_LABELS = { document: 'Documento', numeric: 'KPI/metric', log: 'Log', invocation: 'AI run', reference: 'Reference', screenshot: 'Screenshot' };
const EVIDENCE_TYPE_TONES = { document: 'info', numeric: 'warn', log: '', invocation: 'info', reference: '', screenshot: 'info' };
const EVIDENCE_SOURCE_LABELS = { ai: 'AI', system: 'Sistema', user: 'Utente' };

// ============================================================
// FASE 16 (sessione 91) — "Cosa devi fare": feed personale role-based.
// ============================================================
// Aggrega da /api/my-tasks le attività assegnate al ruolo della persona
// loggata: documenti da firmare (gate-ruoli doc_type.signerRoles) + step di
// workflow in attesa della sua approvazione. Un click porta dritto al punto
// di lavoro (project detail tab Documenti col documento evidenziato, o tab
// Workflow). Connette firma, workflow e Customizing in un unico cruscotto
// "entra la mattina e sai cosa fare".
const MY_TASK_KIND_META = {
  sign_document: { icon: 'docs', label: 'Firma documento' },
  workflow_approval: { icon: 'workflow', label: 'Approvazione workflow' },
};
const MY_TASK_PRIORITY = {
  high: { tone: 'err', label: 'urgente' },
  medium: { tone: 'warn', label: 'da fare' },
  low: { tone: 'info', label: 'quando puoi' },
};

const MY_TASKS_PAGE_SIZE = 5;

function MyTasksPanel() {
  const { user, navigate, pushToast, seed, seedCustom } = useStore();
  const [tasks, setTasks] = React.useState(null); // null = loading
  const [err, setErr] = React.useState(null);
  const [page, setPage] = React.useState(0);
  // FASE 5 (s110) — toggle vista flat / raggruppata per progetto, persistito.
  const [groupMode, setGroupMode] = React.useState(() => {
    try { return localStorage.getItem('mytasks-group-mode') === 'by-project' ? 'by-project' : 'flat'; }
    catch { return 'flat'; }
  });
  const setGroupModePersist = React.useCallback((mode) => {
    setGroupMode(mode);
    try { localStorage.setItem('mytasks-group-mode', mode); } catch { /* noop */ }
  }, []);
  // Sessione 101 — azione in-place: un task di approvazione workflow si gestisce
  // con un modale aperto QUI, senza navigare a una rotta gated.
  const [actionTask, setActionTask] = React.useState(null);
  // Sessione 102 — stesso principio per i task `sign_document`: il VENDOR
  // esterno potrebbe non avere accesso a `project_detail`. Carico il doc via
  // /api/documents/[id] e apro SignatureRequestModal IN-PLACE.
  const [signDoc, setSignDoc] = React.useState(null);
  const [signLoading, setSignLoading] = React.useState(false);

  const reload = React.useCallback(async () => {
    setErr(null);
    setPage(0);
    try {
      const r = await fetch('/api/my-tasks', {
        credentials: 'same-origin',
        cache: 'no-store',
        headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { setErr(j?.error || `HTTP ${r.status}`); setTasks([]); return; }
      setTasks(Array.isArray(j.data) ? j.data : []);
    } catch (e) {
      setErr(String(e?.message || e)); setTasks([]);
    }
  }, [user?.id]);

  React.useEffect(() => { reload(); }, [reload]);
  React.useEffect(() => {
    window.addEventListener('my_tasks_changed', reload);
    return () => window.removeEventListener('my_tasks_changed', reload);
  }, [reload]);

  async function goTo(task) {
    // Sessione 101 — un task di approvazione workflow si gestisce con un modale
    // APERTO QUI dentro il centro attività: l'utente potrebbe non avere i
    // permessi sulla rotta del progetto/workflow (es. un Buyer), ma una notifica
    // su qualcosa che deve fare DEVE essere sempre azionabile.
    if (task.kind === 'workflow_approval' && task.meta && task.meta.instanceId) {
      setActionTask(task);
      return;
    }
    // `sign_document` — principio human-on-the-loop (regressione sessione 119):
    // "Apri" NON deve aprire la firma. Porta l'utente alla lista Documenti del
    // progetto ed EVIDENZIA il documento (bordo rosso, via DocsTab.focusDocId);
    // è poi l'utente, nel suo ruolo, a decidere se aprirlo e firmarlo.
    //
    // Eccezione (pattern task-azionabile-in-place, sessione 102): un ruolo che
    // NON può accedere alla rotta ProjectDetail (es. Vendor/Buyer esterno) non
    // verrebbe portato da nessuna parte → per lui il task resta azionabile con
    // la firma IN-PLACE (fetch doc + SignatureRequestModal).
    if (task.kind === 'sign_document' && task.entityId) {
      const canSeeProject = typeof window !== 'undefined' && typeof window.canAccessRoute === 'function'
        ? window.canAccessRoute('project_detail', user, seedCustom)
        : true;
      if (canSeeProject && task.navParam && task.navParam.id) {
        const { id, tab, docId } = task.navParam;
        // routeParam posizionale `projectId|tab|docId` — vedi ProjectDetail.
        navigate('project_detail', [id, tab || 'docs', docId || task.entityId].join('|'));
        return;
      }
      // Fallback ruolo senza accesso al ProjectDetail → firma in-place.
      if (signLoading) return;
      setSignLoading(true);
      try {
        const r = await fetch(`/api/documents/${encodeURIComponent(task.entityId)}`, {
          credentials: 'same-origin',
          cache: 'no-store',
          headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
        });
        const j = await r.json().catch(() => ({}));
        if (!r.ok || !j.data) {
          pushToast({ title: 'Documento non disponibile', desc: j?.error || `HTTP ${r.status}`, tone: 'err' });
          return;
        }
        setSignDoc(j.data);
      } catch (e) {
        pushToast({ title: 'Errore caricamento documento', desc: String(e?.message || e), tone: 'err' });
      } finally {
        setSignLoading(false);
      }
      return;
    }
    if (task.navTarget === 'project_detail' && task.navParam && task.navParam.id) {
      const { id, tab, docId } = task.navParam;
      // routeParam è una stringa (persistita in localStorage): codifica
      // posizionale `projectId|tab|docId` — vedi ProjectDetail.
      navigate('project_detail', [id, tab || '', docId || ''].join('|'));
    } else {
      navigate(task.navTarget, (task.navParam && task.navParam.id) || null);
    }
  }

  const WorkflowModal = (typeof window !== 'undefined' && window.WorkflowInstanceDetailModal) || null;
  const SignModal = (typeof window !== 'undefined' && window.SignatureRequestModal) || null;

  const count = tasks ? tasks.length : 0;
  const totalPages = Math.max(1, Math.ceil(count / MY_TASKS_PAGE_SIZE));
  const safePage = Math.min(page, totalPages - 1);
  const pageTasks = tasks ? tasks.slice(safePage * MY_TASKS_PAGE_SIZE, safePage * MY_TASKS_PAGE_SIZE + MY_TASKS_PAGE_SIZE) : [];

  // FASE 5 — label progetto per gli header in modalità "per progetto".
  const projectLabel = React.useMemo(() => {
    const map = new Map();
    for (const p of seed?.PROJECTS || []) map.set(p.id, { code: p.code, name: p.name });
    return map;
  }, [seed]);

  // FASE 5 — raggruppamento per progetto (su TUTTI i task, no paginazione).
  const groupedByProject = React.useMemo(() => {
    if (!tasks) return [];
    const groups = new Map();
    for (const t of tasks) {
      const key = t.projectId || '__none__';
      const arr = groups.get(key) || [];
      arr.push(t);
      groups.set(key, arr);
    }
    return Array.from(groups.entries()).map(([projectId, items]) => ({ projectId, items }));
  }, [tasks]);

  // Render singolo task (riusato da vista flat e raggruppata).
  const renderTaskRow = (t) => {
    const km = MY_TASK_KIND_META[t.kind] || { icon: 'docs', label: t.kind };
    const pm = MY_TASK_PRIORITY[t.priority] || { tone: 'info', label: t.priority };
    const sigStatus = t.meta && t.meta.signatureStatus;
    return (
      <div
        key={t.id}
        className="row"
        style={{
          gap: 10, alignItems: 'center', padding: '10px 12px',
          background: 'var(--bg-2)', borderRadius: 6,
          borderLeft: `3px solid var(--${pm.tone})`,
        }}
      >
        <Icon name={km.icon} size={16} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="row" style={{ gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
            <span style={{ fontSize: 12.5, fontWeight: 600 }}>{t.title}</span>
            <Chip kind={pm.tone} dot>{pm.label}</Chip>
            {sigStatus === 'pending' && <Chip kind="warn">firma avviata</Chip>}
          </div>
          <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2 }}>
            {km.label} · {t.subtitle}
          </div>
        </div>
        <Btn variant="primary" size="sm" onClick={() => goTo(t)} disabled={signLoading && t.kind === 'sign_document'}>
          {signLoading && t.kind === 'sign_document' ? 'Caricamento…' : <>Vai <Icon name="chev_r" size={12} /></>}
        </Btn>
      </div>
    );
  };

  return (
    <React.Fragment>
    <div className="card" style={{ marginBottom: 14 }}>
      <div className="card-header">
        <div className="title" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          Cosa devi fare
          {count > 0 && <Chip kind="warn">{count}</Chip>}
        </div>
        <div className="actions" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {count > 0 && (
            <div className="row" style={{ gap: 0, border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden' }}>
              <button
                className="btn ghost sm"
                onClick={() => setGroupModePersist('flat')}
                data-action="tasks-view-flat"
                style={{ borderRadius: 0, background: groupMode === 'flat' ? 'var(--bg-2)' : 'transparent', fontWeight: groupMode === 'flat' ? 600 : 400 }}
              >
                Tutti
              </button>
              <button
                className="btn ghost sm"
                onClick={() => setGroupModePersist('by-project')}
                data-action="tasks-view-by-project"
                style={{ borderRadius: 0, background: groupMode === 'by-project' ? 'var(--bg-2)' : 'transparent', fontWeight: groupMode === 'by-project' ? 600 : 400 }}
              >
                Per progetto
              </button>
            </div>
          )}
          <button className="btn ghost icon" onClick={reload} title="Aggiorna i task">
            <Icon name="refresh" />
          </button>
        </div>
      </div>
      <div className="card-body">
        <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginBottom: 10 }}>
          Attività assegnate al tuo ruolo: documenti da firmare e approvazioni di
          workflow in attesa. Un click ti porta dritto al punto di lavoro.
        </div>
        {tasks === null && (
          <div style={{ padding: 18, textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
            Caricamento attività…
          </div>
        )}
        {err && (
          <div style={{ padding: 12, color: 'var(--err)', fontSize: 12 }}>
            Impossibile caricare i task: {err}
          </div>
        )}
        {tasks && count === 0 && !err && (
          <div style={{ padding: 18, textAlign: 'center', color: 'var(--text-3)', fontSize: 12.5 }}>
            Nessuna attività in sospeso per il tuo ruolo. Sei in pari ✓
          </div>
        )}
        {tasks && count > 0 && (
          <React.Fragment>
          {groupMode === 'flat' ? (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {pageTasks.map((t) => renderTaskRow(t))}
            </div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }} data-tasks-grouped="true">
              {groupedByProject.map((g) => {
                const lbl = projectLabel.get(g.projectId);
                return (
                  <div key={g.projectId}>
                    <div className="row" style={{ gap: 6, alignItems: 'center', marginBottom: 6 }}>
                      <Icon name="projects" size={12} />
                      <span style={{ fontSize: 12, fontWeight: 600 }}>
                        {g.projectId === '__none__' ? 'Senza progetto' : (lbl?.name || g.projectId)}
                      </span>
                      {lbl?.code && <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{lbl.code}</span>}
                      <Chip kind="info">{g.items.length}</Chip>
                    </div>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                      {g.items.map((t) => renderTaskRow(t))}
                    </div>
                  </div>
                );
              })}
            </div>
          )}
          {groupMode === 'flat' && totalPages > 1 && (
            <div className="row" style={{ gap: 4, justifyContent: 'center', marginTop: 12, flexWrap: 'wrap' }}>
              <button
                className="btn ghost sm"
                disabled={safePage === 0}
                onClick={() => setPage(safePage - 1)}
              >‹</button>
              {Array.from({ length: totalPages }, (_, i) => (
                <button
                  key={i}
                  className={`btn sm ${i === safePage ? 'primary' : 'ghost'}`}
                  onClick={() => setPage(i)}
                >{i + 1}</button>
              ))}
              <button
                className="btn ghost sm"
                disabled={safePage >= totalPages - 1}
                onClick={() => setPage(safePage + 1)}
              >›</button>
              <span style={{ fontSize: 10.5, color: 'var(--text-3)', marginLeft: 6, alignSelf: 'center' }}>
                pagina {safePage + 1}/{totalPages}
              </span>
            </div>
          )}
          </React.Fragment>
        )}
      </div>
    </div>
    {actionTask && actionTask.kind === 'workflow_approval' && WorkflowModal && (
      <WorkflowModal
        instanceId={actionTask.meta.instanceId}
        user={user}
        pushToast={pushToast}
        onTransition={reload}
        onClose={() => setActionTask(null)}
      />
    )}
    {signDoc && SignModal && (
      <SignModal
        doc={signDoc}
        prefill={{
          email: user?.email || '',
          name: user?.name || '',
          role: (Array.isArray(user?.roleIds) && user.roleIds[0]) || '',
        }}
        onComplete={() => { setSignDoc(null); reload(); }}
        onClose={() => setSignDoc(null)}
      />
    )}
    </React.Fragment>
  );
}

// FASE 7 Project Cockpit (s111) — Feed "Notifiche recenti" (7gg) sotto MyTasksPanel.
// Separato dai task azionabili: qui solo notifiche informative (modifiche team).
const NOTIF_ACTION_ICON = { added: 'users', removed: 'users', role_changed: 'edit' };

function RecentNotificationsPanel() {
  const { user, navigate } = useStore();
  const [data, setData] = React.useState(null); // null = loading
  const [errored, setErrored] = React.useState(false);

  const reload = React.useCallback(async () => {
    if (!user?.id) return;
    try {
      const r = await fetch('/api/my-notifications?limit=10', {
        credentials: 'same-origin',
        cache: 'no-store',
        headers: { 'X-Actor-Persona-Id': user.id },
      });
      if (!r.ok) { setErrored(true); setData({ items: [], unreadCount: 0 }); return; }
      const j = await r.json();
      setData(j.data || { items: [], unreadCount: 0 });
      setErrored(false);
    } catch {
      setErrored(true); setData({ items: [], unreadCount: 0 });
    }
  }, [user?.id]);

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

  const markRead = async (id) => {
    try {
      await fetch(`/api/my-notifications/${encodeURIComponent(id)}/read`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'X-Actor-Persona-Id': user?.id || '' },
      });
    } catch { /* best-effort */ }
  };

  const openNotif = (n) => {
    // Optimistic: segna letta localmente + persisti.
    setData((d) => d ? {
      items: d.items.map((it) => it.id === n.id ? { ...it, read: true } : it),
      unreadCount: Math.max(0, d.unreadCount - (n.read ? 0 : 1)),
    } : d);
    markRead(n.id);
    if (n.projectId) navigate('project_detail', n.projectId);
  };

  const items = data?.items ?? [];
  const unread = data?.unreadCount ?? 0;

  return (
    <div className="card" style={{ marginBottom: 14 }} data-widget="recent-notifications">
      <div className="card-header">
        <div className="title" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="bell" size={12}/> Notifiche recenti
          {unread > 0 && <Chip kind="info" data-testid="notif-unread">{unread}</Chip>}
        </div>
        <div className="actions">
          <button className="btn ghost icon" onClick={reload} title="Aggiorna notifiche">
            <Icon name="refresh" />
          </button>
        </div>
      </div>
      <div className="card-body">
        {data === null && (
          <div style={{ padding: 14, textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>Caricamento…</div>
        )}
        {data !== null && errored && (
          <div style={{ padding: 12, color: 'var(--text-3)', fontSize: 12 }}>Feed notifiche non disponibile.</div>
        )}
        {data !== null && !errored && items.length === 0 && (
          <div style={{ padding: 14, textAlign: 'center', color: 'var(--text-3)', fontSize: 12.5 }}>
            Nessuna notifica negli ultimi 7 giorni.
          </div>
        )}
        {data !== null && !errored && items.length > 0 && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {items.map((n) => (
              <div
                key={n.id}
                className="clickable"
                data-notif-id={n.id}
                data-notif-read={n.read ? 'true' : 'false'}
                onClick={() => openNotif(n)}
                style={{
                  display: 'flex', gap: 10, alignItems: 'flex-start', padding: '9px 12px',
                  background: n.read ? 'transparent' : 'var(--bg-2)', borderRadius: 6,
                  border: '1px solid var(--line)',
                }}
              >
                <span style={{ marginTop: 5, width: 7, height: 7, borderRadius: '50%', flexShrink: 0, background: n.read ? 'var(--text-3)' : 'var(--accent)' }} />
                <Icon name={NOTIF_ACTION_ICON[n.action] || 'bell'} size={14} />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="row" style={{ gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                    <span style={{ fontSize: 12.5, fontWeight: n.read ? 500 : 600 }}>{n.actionLabel}</span>
                    {n.projectCode && <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{n.projectCode}</span>}
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--text-2)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {n.body || n.subject}
                  </div>
                </div>
                <span style={{ fontSize: 10, color: 'var(--text-3)', flexShrink: 0 }}>
                  {(n.createdAt || '').slice(0, 10)}
                </span>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function Alerts() {
  const { user, navigate, pushToast } = useStore();
  const [data, setData] = React.useState(null);
  const [filters, setFilters] = React.useState({ state: '', severity: '' });
  const [sel, setSel] = React.useState(null);
  const [resolveModal, setResolveModal] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [evidence, setEvidence] = React.useState(null); // { data, byType, bySource } per sel
  const [evidenceForm, setEvidenceForm] = React.useState({ open: false, type: 'reference', title: '', description: '' });
  const [evidenceBusy, setEvidenceBusy] = React.useState(false);
  // Sessione 64 — RISK_ANALYZER agent state
  const [riskBusy, setRiskBusy] = React.useState(false);
  const [riskResult, setRiskResult] = React.useState(null); // { recommendations, ... }
  const [riskCreating, setRiskCreating] = React.useState({}); // { [recoIndex]: true }

  const reload = React.useCallback(async () => {
    setData(null);
    try {
      const params = new URLSearchParams();
      if (filters.state) params.set('state', filters.state);
      if (filters.severity) params.set('severity', filters.severity);
      params.set('limit', '200');
      const r = await fetch('/api/anomaly-flags?' + params.toString(), {
        credentials: 'same-origin',
        cache: 'no-store',
        headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
      });
      const j = await r.json();
      setData(j);
    } catch (e) {
      setData({ data: [], total: 0, kpi: null, meta: null, error: String(e?.message || e) });
    }
  }, [filters, user?.id]);

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

  // Refresh sel dopo reload (mantieni dato fresco se ancora presente)
  React.useEffect(() => {
    if (!sel || !data?.data) return;
    const fresh = data.data.find((r) => r.id === sel.id);
    if (fresh && JSON.stringify(fresh) !== JSON.stringify(sel)) setSel(fresh);
    else if (!fresh) setSel(null); // rimossa dai filtri attuali
  }, [data]);

  // FASE 16 (sessione 63) — Fetch alert_evidence quando il flag selezionato cambia
  const reloadEvidence = React.useCallback(async (flagId) => {
    if (!flagId) { setEvidence(null); return; }
    try {
      const r = await fetch(`/api/anomaly-flags/${encodeURIComponent(flagId)}/evidence`, {
        credentials: 'same-origin',
        cache: 'no-store',
        headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
      });
      if (!r.ok) { setEvidence(null); return; }
      const j = await r.json();
      setEvidence(j);
    } catch {
      setEvidence(null);
    }
  }, [user?.id]);
  React.useEffect(() => { reloadEvidence(sel?.id); setEvidenceForm({ open: false, type: 'reference', title: '', description: '' }); }, [sel?.id, reloadEvidence]);

  async function submitEvidence() {
    if (!sel || evidenceBusy) return;
    if (!evidenceForm.title || evidenceForm.title.trim().length < 3) {
      pushToast({ title: 'Title obbligatorio', desc: 'Min 3 caratteri', tone: 'warn' });
      return;
    }
    setEvidenceBusy(true);
    try {
      const r = await fetch(`/api/anomaly-flags/${encodeURIComponent(sel.id)}/evidence`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}) },
        credentials: 'same-origin',
        body: JSON.stringify({
          evidenceType: evidenceForm.type,
          title: evidenceForm.title.trim(),
          description: evidenceForm.description.trim() || null,
          source: 'user',
        }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) {
        pushToast({
          title: 'Errore evidence',
          desc: j?.error === 'validation_error'
            ? `Validazione: ${(j.issues || []).map((i) => i.message).join(' · ')}`
            : (j?.detail || j?.error || `HTTP ${r.status}`),
          tone: 'err',
        });
        return;
      }
      pushToast({ title: 'Evidence aggiunta', desc: j?.data?.id || 'OK', tone: 'ok' });
      setEvidenceForm({ open: false, type: 'reference', title: '', description: '' });
      await reloadEvidence(sel.id);
    } finally {
      setEvidenceBusy(false);
    }
  }

  const kpi = data?.kpi;
  const rows = data?.data || [];
  const meta = data?.meta;

  async function patchState(flag, nextState, resolutionNote) {
    if (busy) return;
    setBusy(true);
    try {
      const body = { state: nextState };
      if (nextState === 'resolved' || nextState === 'dismissed') {
        if (resolutionNote) body.resolutionNote = resolutionNote;
      }
      const r = await fetch(`/api/anomaly-flags/${encodeURIComponent(flag.id)}`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}),
        },
        credentials: 'same-origin',
        body: JSON.stringify(body),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) {
        pushToast({
          title: 'Errore aggiornamento alert',
          desc: j?.error === 'validation_error'
            ? `Validazione: ${(j.issues || []).map((i) => i.message).join(' · ')}`
            : (j?.detail || j?.error || `HTTP ${r.status}`),
          tone: 'err',
        });
        return false;
      }
      const titleByState = {
        open: 'Alert riaperto',
        resolved: 'Alert risolto',
        dismissed: 'Alert archiviato',
      };
      pushToast({
        title: titleByState[nextState],
        desc: j?.changed === false ? 'Nessuna modifica' : 'Audit registrato',
        tone: nextState === 'open' ? 'info' : 'ok',
      });
      await reload();
      // Quick win sessione 63 — Broadcast event refresh Sidebar badge + Dashboard widget.
      // Solo se changed=true (skip idempotent: count non cambia).
      if (j?.changed !== false) window.dispatchEvent(new Event('anomaly_count_changed'));
      return true;
    } finally {
      setBusy(false);
    }
  }

  // Sessione 64 — RISK_ANALYZER trigger
  async function runRiskAnalyzer() {
    if (riskBusy) return;
    setRiskBusy(true);
    setRiskResult(null);
    try {
      // Sessione 67 — Inietta X-AI-*-Override se l'utente ha override attivo
      // (sessione 66 AiPersonalOverrideCard). Helper inline per evitare
      // dipendenze cross-file nel prototipo browser-side.
      const aiOverrideHeaders = (() => {
        try {
          if (!user?.id) return {};
          const raw = localStorage.getItem(`lgs.ai.user.${user.id}.preferences`);
          if (!raw) return {};
          const j = JSON.parse(raw);
          const headers = {};
          if (j?.modeOverride && j.modeOverride !== 'default') headers['X-AI-Mode-Override'] = j.modeOverride;
          if (j?.providerOverride && j.providerOverride !== 'default') headers['X-AI-Provider-Override'] = j.providerOverride;
          return headers;
        } catch { return {}; }
      })();
      const r = await fetch('/api/ai/risk-analyzer/run', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}),
          ...aiOverrideHeaders,
        },
        credentials: 'same-origin',
        body: JSON.stringify({}),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) {
        pushToast({
          title: 'RISK_ANALYZER fallito',
          desc: j?.detail === 'no_ai_provider_configured'
            ? 'Nessun provider AI configurato. Imposta una API key in Customizing > AI.'
            : (j?.detail || j?.error || `HTTP ${r.status}`),
          tone: 'err',
        });
        return;
      }
      setRiskResult(j);
      pushToast({
        title: `RISK_ANALYZER ${j.provider}`,
        desc: `${j.recommendations?.length ?? 0} raccomandazioni · ${j.durationMs}ms`,
        tone: j.recommendations?.length > 0 ? 'ok' : 'info',
      });
    } catch (e) {
      pushToast({ title: 'RISK_ANALYZER errore rete', desc: String(e?.message || e), tone: 'err' });
    } finally {
      setRiskBusy(false);
    }
  }

  async function applyRiskReco(reco, idx) {
    if (riskCreating[idx]) return;
    setRiskCreating((s) => ({ ...s, [idx]: true }));
    try {
      const r = await fetch('/api/anomaly-flags', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(user?.id ? { 'X-Actor-Persona-Id': user.id } : {}),
        },
        credentials: 'same-origin',
        body: JSON.stringify({
          severity: reco.severity,
          title: reco.title,
          description: reco.description,
          entityType: reco.entityType,
          entityId: reco.entityId,
          source: 'risk_analyzer',
        }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) {
        pushToast({
          title: 'Crea anomaly fallito',
          desc: j?.error === 'validation_error'
            ? `Validazione: ${(j.issues || []).map((i) => i.message).join(' · ')}`
            : (j?.detail || j?.error || `HTTP ${r.status}`),
          tone: 'err',
        });
        return;
      }
      pushToast({ title: 'Anomaly creata', desc: j?.data?.id || 'OK', tone: 'ok' });
      // Marca questa reco come "creata" (rimuovi dalla lista per evitare duplicati)
      setRiskResult((prev) => prev ? {
        ...prev,
        recommendations: prev.recommendations.filter((_, i) => i !== idx),
      } : prev);
      await reload();
      window.dispatchEvent(new Event('anomaly_count_changed'));
    } finally {
      setRiskCreating((s) => { const n = { ...s }; delete n[idx]; return n; });
    }
  }

  function handleEntityNav(flag) {
    const target = ENTITY_NAV_TARGET[flag.entityType];
    if (!target) {
      pushToast({ title: 'Entity non navigabile', desc: `${flag.entityType} · ${flag.entityId}`, tone: 'info' });
      return;
    }
    // Human-on-the-loop: NON portare l'utente su una lista generica facendogli
    // perdere il riferimento. Passa sempre l'entityId così la pagina target
    // SELEZIONA/APRE l'oggetto specifico dell'anomalia (project_detail apre il
    // progetto; rda/vendors/documents aprono il detail dell'id via routeParam).
    // È poi l'utente, nel suo ruolo, a decidere l'azione.
    navigate(target, flag.entityId || null);
  }

  return (
    <div className="page fade-in">
      <div className="page-header">
        <div>
          <div className="eyebrow">Operatività · cosa devi fare</div>
          <h1 className="page-title">Le mie attività</h1>
          <div className="page-sub">
            I documenti da firmare e gli step di workflow che ti competono, più
            gli alert di rischio rilevati dall'AI sul portfolio CAPEX. Le azioni
            Ack e Risolvi sugli alert sono auditate.
          </div>
        </div>
        <div className="actions">
          <Btn variant="primary" size="sm" onClick={runRiskAnalyzer} disabled={riskBusy}>
            <Icon name="sparkle" size={11}/> {riskBusy ? 'Analisi in corso…' : 'AI Risk analysis'}
          </Btn>
          <Btn variant="ghost" size="sm" onClick={reload} disabled={data === null}>
            <Icon name="refresh" size={11}/> Reload
          </Btn>
        </div>
      </div>

      <MyTasksPanel />

      <RecentNotificationsPanel />

      <div className="grid grid-4" style={{ marginBottom: 14 }}>
        <div className="card">
          <Stat label="Totale" value={kpi?.total ?? '—'} />
        </div>
        <div className="card">
          <Stat
            label="Aperte"
            value={kpi?.byState?.open ?? '—'}
            tone={kpi?.byState?.open > 0 ? 'down' : 'up'}
          />
        </div>
        <div className="card">
          <Stat
            label="Critical · High"
            value={kpi ? `${kpi.bySeverity.critical} · ${kpi.bySeverity.high}` : '—'}
            tone={kpi && (kpi.bySeverity.critical + kpi.bySeverity.high) > 0 ? 'down' : ''}
          />
        </div>
        <div className="card">
          <Stat
            label="Risolte · Archiviate"
            value={kpi ? `${kpi.byState.resolved} · ${kpi.byState.dismissed}` : '—'}
          />
        </div>
      </div>

      <div className="card" style={{ marginBottom: 14 }}>
        <div className="card-body tight row" style={{ gap: 10 }}>
          <div className="row" style={{ gap: 4 }}>
            {['', 'critical', 'high', 'medium', 'low'].map((s) => (
              <button
                key={s || 'all-sev'}
                onClick={() => setFilters((f) => ({ ...f, severity: s }))}
                className={`btn sm ${filters.severity === s ? 'primary' : 'ghost'}`}
              >
                {s ? ALERT_SEVERITY_LABELS[s] : 'Tutte severity'}
              </button>
            ))}
          </div>
          <div className="divider-v"/>
          <div className="row" style={{ gap: 4 }}>
            {['', 'open', 'resolved', 'dismissed'].map((s) => (
              <button
                key={s || 'all-state'}
                onClick={() => setFilters((f) => ({ ...f, state: s }))}
                className={`btn sm ${filters.state === s ? 'primary' : 'ghost'}`}
              >
                {s ? ALERT_STATE_LABELS[s] : 'Tutti stati'}
              </button>
            ))}
          </div>
        </div>
      </div>

      <div className="grid" style={{ gridTemplateColumns: sel ? '1fr 440px' : '1fr', gap: 14 }}>
        <div className="col" style={{ gap: 10 }}>
          {data === null && (
            <div style={{ padding: 30, textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
              Caricamento alert…
            </div>
          )}
          {data?.error && (
            <div style={{ padding: 16, color: 'var(--err)', fontSize: 12 }}>
              Errore lettura alert: {data.error}
            </div>
          )}
          {data && !data.error && rows.length === 0 && (
            <div style={{ padding: 30, textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
              Nessun alert per i filtri correnti. Le anomalie vengono create dal copilota AI via tool <code>flag_anomaly</code>.
            </div>
          )}
          {rows.map((a) => (
            <div
              key={a.id}
              className={`alert-card${sel?.id === a.id ? ' active' : ''}`}
              style={{ cursor: 'pointer' }}
              onClick={() => setSel(a)}
            >
              <div
                className="bar"
                style={{
                  background:
                    a.severity === 'critical' ? 'var(--err)' :
                    a.severity === 'high' ? 'var(--err)' :
                    a.severity === 'medium' ? 'var(--warn)' : 'var(--info)',
                  opacity: a.severity === 'high' ? 0.75 : 1,
                }}
              />
              <div style={{ flex: 1 }}>
                <div className="row" style={{ gap: 8, flexWrap: 'wrap' }}>
                  <span className="ttl">{a.title}</span>
                  <Chip kind={ALERT_SEVERITY_TONES[a.severity] || 'info'} dot>
                    {ALERT_SEVERITY_LABELS[a.severity] || a.severity}
                  </Chip>
                  <Chip kind={ALERT_STATE_TONES[a.state] || ''}>
                    {ALERT_STATE_LABELS[a.state] || a.state}
                  </Chip>
                </div>
                <div className="meta">
                  {a.entityType}/<code style={{ fontSize: 10.5 }}>{a.entityId}</code> · rilevato {new Date(a.createdAt).toLocaleString('it-IT', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
                  {a.flaggedBy && <> · da <code>{a.flaggedBy}</code></>}
                </div>
                {a.description && <div className="reason">{a.description}</div>}
              </div>
              <Icon name="chev_r" size={14}/>
            </div>
          ))}
        </div>

        {sel && (
          <div className="card">
            <div className="card-header">
              <div className="title" style={{ paddingRight: 8 }}>{sel.title}</div>
              <div className="actions">
                <button className="btn ghost icon" onClick={() => setSel(null)} title="Chiudi pannello">
                  <Icon name="x"/>
                </button>
              </div>
            </div>
            <div className="card-body">
              <div className="row" style={{ gap: 6, flexWrap: 'wrap' }}>
                <Chip kind={ALERT_SEVERITY_TONES[sel.severity] || 'info'} dot>
                  {ALERT_SEVERITY_LABELS[sel.severity] || sel.severity}
                </Chip>
                <Chip kind={ALERT_STATE_TONES[sel.state] || ''}>
                  {ALERT_STATE_LABELS[sel.state] || sel.state}
                </Chip>
              </div>
              <div className="mono" style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 8 }}>
                {sel.entityType}/{sel.entityId} · creato {new Date(sel.createdAt).toLocaleString('it-IT')}
              </div>

              {sel.description && (
                <>
                  <div className="sep"/>
                  <div className="eyebrow">Descrizione</div>
                  <p style={{ fontSize: 12.5, lineHeight: 1.6, marginTop: 4 }}>{sel.description}</p>
                </>
              )}

              <div className="sep"/>
              <div className="eyebrow">Evidence</div>
              <div style={{ fontSize: 11.5, lineHeight: 1.7, marginTop: 4, paddingLeft: 4 }}>
                <div>
                  <span style={{ color: 'var(--text-3)' }}>Entità:</span>{' '}
                  <strong>{sel.entityType}</strong> · <code>{sel.entityId}</code>
                </div>
                <div>
                  <span style={{ color: 'var(--text-3)' }}>Flagged by:</span>{' '}
                  <code>{sel.flaggedBy || '(AI system)'}</code>
                </div>
                <div>
                  <span style={{ color: 'var(--text-3)' }}>Flagged at:</span>{' '}
                  {new Date(sel.createdAt).toLocaleString('it-IT')}
                </div>
                {sel.state !== 'open' && sel.resolvedBy && (
                  <>
                    <div>
                      <span style={{ color: 'var(--text-3)' }}>{sel.state === 'resolved' ? 'Resolved by:' : 'Dismissed by:'}</span>{' '}
                      <code>{sel.resolvedBy}</code>
                    </div>
                    <div>
                      <span style={{ color: 'var(--text-3)' }}>{sel.state === 'resolved' ? 'Resolved at:' : 'Dismissed at:'}</span>{' '}
                      {sel.resolvedAt ? new Date(sel.resolvedAt).toLocaleString('it-IT') : '—'}
                    </div>
                  </>
                )}
              </div>

              {sel.resolutionNote && (
                <>
                  <div className="sep"/>
                  <div className="eyebrow">Resolution note</div>
                  <div style={{ fontSize: 11.5, lineHeight: 1.6, marginTop: 4, padding: 8, background: 'var(--bg-2)', borderRadius: 4 }}>
                    {sel.resolutionNote}
                  </div>
                </>
              )}

              <div className="sep"/>
              <div className="row" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
                <div className="eyebrow" style={{ margin: 0 }}>
                  Evidence collegate {evidence ? `· ${evidence.total}` : ''}
                </div>
                {!evidenceForm.open && (
                  <button
                    className="btn ghost"
                    style={{ fontSize: 10.5, padding: '4px 8px' }}
                    onClick={() => setEvidenceForm({ ...evidenceForm, open: true })}
                  >+ Aggiungi</button>
                )}
              </div>
              {evidence === null && (
                <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4 }}>
                  Lettura evidence non disponibile.
                </div>
              )}
              {evidence && evidence.total === 0 && !evidenceForm.open && (
                <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4 }}>
                  Nessuna evidence collegata. Aggiungi un riferimento (link, log, screenshot) per documentare la flag.
                </div>
              )}
              {evidence && evidence.total > 0 && (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 6 }}>
                  {evidence.data.map((ev) => (
                    <div key={ev.id} style={{ padding: 8, background: 'var(--bg-2)', borderRadius: 4, borderLeft: '3px solid var(--info)' }}>
                      <div className="row" style={{ gap: 6, flexWrap: 'wrap' }}>
                        <Chip kind={EVIDENCE_TYPE_TONES[ev.evidenceType] || ''}>{EVIDENCE_TYPE_LABELS[ev.evidenceType] || ev.evidenceType}</Chip>
                        <span style={{ fontSize: 11.5, fontWeight: 500 }}>{ev.title}</span>
                      </div>
                      {ev.description && <div style={{ fontSize: 11, color: 'var(--text-2)', marginTop: 4 }}>{ev.description}</div>}
                      <div className="mono" style={{ fontSize: 9.5, color: 'var(--text-3)', marginTop: 4 }}>
                        {EVIDENCE_SOURCE_LABELS[ev.source] || ev.source} · {new Date(ev.recordedAt).toLocaleString('it-IT', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
                        {ev.addedByPersonaId && <> · <code>{ev.addedByPersonaId}</code></>}
                      </div>
                    </div>
                  ))}
                </div>
              )}
              {evidenceForm.open && (
                <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-2)', borderRadius: 4, display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <select
                    className="ipt"
                    value={evidenceForm.type}
                    onChange={(e) => setEvidenceForm({ ...evidenceForm, type: e.target.value })}
                    disabled={evidenceBusy}
                  >
                    {Object.entries(EVIDENCE_TYPE_LABELS).map(([k, v]) => (
                      <option key={k} value={k}>{v}</option>
                    ))}
                  </select>
                  <input
                    className="ipt"
                    type="text"
                    placeholder="Titolo (3-200 char)"
                    value={evidenceForm.title}
                    onChange={(e) => setEvidenceForm({ ...evidenceForm, title: e.target.value })}
                    disabled={evidenceBusy}
                    maxLength={200}
                  />
                  <textarea
                    className="ipt"
                    placeholder="Descrizione (opzionale, max 1000 char)"
                    value={evidenceForm.description}
                    onChange={(e) => setEvidenceForm({ ...evidenceForm, description: e.target.value })}
                    disabled={evidenceBusy}
                    rows={3}
                    maxLength={1000}
                    style={{ resize: 'vertical' }}
                  />
                  <div className="row" style={{ gap: 6 }}>
                    <Btn variant="primary" size="sm" onClick={submitEvidence} disabled={evidenceBusy || evidenceForm.title.trim().length < 3}>
                      {evidenceBusy ? 'Salvataggio…' : 'Aggiungi'}
                    </Btn>
                    <Btn variant="ghost" size="sm" onClick={() => setEvidenceForm({ ...evidenceForm, open: false })} disabled={evidenceBusy}>
                      Annulla
                    </Btn>
                  </div>
                </div>
              )}

              <div className="sep"/>
              <div className="row" style={{ gap: 6, flexWrap: 'wrap' }}>
                <Btn variant="primary" size="sm" onClick={() => handleEntityNav(sel)}>
                  Vai al/alla {sel.entityType}
                </Btn>
                {sel.state === 'open' && (
                  <>
                    <Btn variant="ghost" size="sm" onClick={() => setResolveModal(sel)} disabled={busy}>
                      Risolvi…
                    </Btn>
                    <Btn variant="ghost" size="sm" onClick={() => patchState(sel, 'dismissed')} disabled={busy}>
                      Ack (archivia)
                    </Btn>
                  </>
                )}
                {sel.state !== 'open' && (
                  <Btn variant="ghost" size="sm" onClick={() => patchState(sel, 'open')} disabled={busy}>
                    Riapri
                  </Btn>
                )}
              </div>
            </div>
          </div>
        )}
      </div>

      {resolveModal && typeof window !== 'undefined' && window.AnomalyResolveModal && (
        <window.AnomalyResolveModal
          flag={resolveModal}
          onClose={() => setResolveModal(null)}
          onSaved={() => { setResolveModal(null); reload(); }}
        />
      )}

      {riskResult && typeof window !== 'undefined' && window.Modal && (
        <window.Modal
          open
          onClose={() => setRiskResult(null)}
          title={`RISK_ANALYZER · ${riskResult.provider}/${riskResult.model}`}
          size="lg"
          footer={
            <>
              <Btn variant="ghost" size="sm" onClick={() => setRiskResult(null)}>Chiudi</Btn>
            </>
          }
        >
          <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginBottom: 12 }}>
            Context analizzato: {riskResult.ctxSize?.anomaliesOpen ?? 0} anomalie aperte ·{' '}
            {riskResult.ctxSize?.rdaPending ?? 0} RDA pending · {riskResult.ctxSize?.projectsBurnHigh ?? 0} progetti burn alto
            {riskResult.droppedCount > 0 && (
              <> · <span style={{ color: 'var(--warn)' }}>{riskResult.droppedCount} reco droppate (entityId hallucinati)</span></>
            )}
          </div>
          {(!riskResult.recommendations || riskResult.recommendations.length === 0) ? (
            <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-3)', fontSize: 12.5 }}>
              Nessuna raccomandazione dal modello. Il portfolio appare in linea, oppure tutte le anomalie sono già aperte.
            </div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {riskResult.recommendations.map((reco, idx) => (
                <div key={idx} style={{ padding: 12, background: 'var(--bg-2)', borderRadius: 6, borderLeft: `3px solid ${reco.severity === 'critical' || reco.severity === 'high' ? 'var(--err)' : reco.severity === 'medium' ? 'var(--warn)' : 'var(--info)'}` }}>
                  <div className="row" style={{ gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                    <Chip kind={ALERT_SEVERITY_TONES[reco.severity] || 'info'} dot>
                      {ALERT_SEVERITY_LABELS[reco.severity] || reco.severity}
                    </Chip>
                    <strong style={{ fontSize: 12.5 }}>{reco.title}</strong>
                  </div>
                  <div style={{ fontSize: 11.5, marginTop: 6, lineHeight: 1.5 }}>{reco.description}</div>
                  <div className="mono" style={{ fontSize: 10, color: 'var(--text-3)', marginTop: 6 }}>
                    {reco.entityType}/<code>{reco.entityId}</code>
                  </div>
                  <div style={{ fontSize: 10.5, marginTop: 6, padding: 6, background: 'var(--bg-1)', borderRadius: 4, fontStyle: 'italic', color: 'var(--text-2)' }}>
                    <strong>Rationale:</strong> {reco.rationale}
                  </div>
                  <div style={{ marginTop: 8 }}>
                    <Btn
                      variant="primary"
                      size="sm"
                      onClick={() => applyRiskReco(reco, idx)}
                      disabled={!!riskCreating[idx]}
                    >
                      {riskCreating[idx] ? 'Creazione…' : 'Crea anomaly'}
                    </Btn>
                  </div>
                </div>
              ))}
            </div>
          )}
        </window.Modal>
      )}
    </div>
  );
}
Object.assign(window, { Alerts });
