// ============================================================
// Dashboard.jsx
// ============================================================

// Sessione 59 — Widget anomalies aperte: legge live da /api/anomaly-flags?state=open
// con polling 60s + refresh on route change. Mostra count totale, distribuzione per
// severity (chip color-coded) e top 5 items. Pattern coerente con Sidebar dynamic
// counter (sessione 58, pattern cumulativo 134).
const ANOMALY_SEVERITY_TONES = { critical: 'err', high: 'err', medium: 'warn', low: 'info' };
const ANOMALY_SEVERITY_LABELS = { critical: 'critica', high: 'alta', medium: 'media', low: 'bassa' };

function AnomaliesOpenWidget() {
  const { user, navigate } = useStore();
  const [items, setItems] = React.useState([]);
  const [kpi, setKpi] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [errored, setErrored] = React.useState(false);

  React.useEffect(() => {
    if (!user?.id) return; // niente polling prima dell'idratazione auth
    let cancelled = false;
    async function load() {
      try {
        const r = await fetch('/api/anomaly-flags?state=open&limit=5', {
          credentials: 'same-origin',
          cache: 'no-store',
          headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
        });
        if (!r.ok) {
          if (!cancelled) { setErrored(true); setItems([]); setKpi(null); }
          return;
        }
        const j = await r.json();
        if (!cancelled) {
          setItems(Array.isArray(j.data) ? j.data : []);
          setKpi(j.kpi ?? null);
          setErrored(false);
        }
      } catch {
        if (!cancelled) { setErrored(true); setItems([]); setKpi(null); }
      } finally {
        if (!cancelled) setLoading(false);
      }
    }
    load();
    // Rete di sicurezza ogni 60s; il refresh tempestivo è event-driven.
    const interval = setInterval(load, 60_000);
    // Quick win sessione 63 — Refresh on-demand quando un PATCH lato FE altera
    // il count (Alerts.jsx, AnomalyResolveModal in Customizing). Sync con Sidebar.
    window.addEventListener('anomaly_count_changed', load);
    return () => {
      cancelled = true;
      clearInterval(interval);
      window.removeEventListener('anomaly_count_changed', load);
    };
  }, [user?.id]);

  const open = kpi?.byState?.open ?? 0;
  const sev = kpi?.bySeverity ?? { critical: 0, high: 0, medium: 0, low: 0 };

  return (
    <div className="card">
      <div className="card-header">
        <div>
          <div className="title"><Icon name="alerts" size={12}/> Anomalie aperte</div>
          <div className="desc">Flag rilevati dall'AI sul portfolio (state=open)</div>
        </div>
        <div className="actions">
          <Btn variant="ghost" size="sm" onClick={() => navigate('customizing', 'anomalies')}>
            Vedi tutte <Icon name="chev_r" size={12}/>
          </Btn>
        </div>
      </div>
      <div className="card-body" style={{ paddingTop: 12 }}>
        {loading && <div style={{ fontSize: 12, color: 'var(--text-3)' }}>Caricamento…</div>}
        {!loading && errored && (
          <div style={{ fontSize: 12, color: 'var(--text-3)' }}>
            Lettura anomalie non disponibile.
          </div>
        )}
        {!loading && !errored && open === 0 && (
          <div style={{ fontSize: 12.5, color: 'var(--text-2)' }}>
            <Chip kind="ok" dot>nessuna anomalia aperta</Chip>
          </div>
        )}
        {!loading && !errored && open > 0 && (
          <>
            <div className="row" style={{ gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
              <strong style={{ fontSize: 18, fontFamily: 'var(--font-mono)' }}>{open}</strong>
              <span style={{ fontSize: 11.5, color: 'var(--text-2)' }}>
                aperte · totale {kpi?.total ?? open}
              </span>
            </div>
            <div className="row" style={{ gap: 6, marginBottom: 12, flexWrap: 'wrap' }}>
              {sev.critical > 0 && <Chip kind="err" dot>{sev.critical} critic{sev.critical === 1 ? 'a' : 'he'}</Chip>}
              {sev.high > 0 && <Chip kind="err" dot>{sev.high} alt{sev.high === 1 ? 'a' : 'e'}</Chip>}
              {sev.medium > 0 && <Chip kind="warn" dot>{sev.medium} medi{sev.medium === 1 ? 'a' : 'e'}</Chip>}
              {sev.low > 0 && <Chip kind="info" dot>{sev.low} bass{sev.low === 1 ? 'a' : 'e'}</Chip>}
            </div>
            <div style={{ borderTop: '1px solid var(--line)', paddingTop: 8 }}>
              {items.slice(0, 5).map((a) => (
                <div
                  key={a.id}
                  className="clickable"
                  onClick={() => navigate('customizing', 'anomalies')}
                  style={{ padding: '8px 0', borderBottom: '1px solid var(--line)', display: 'flex', gap: 8, alignItems: 'flex-start' }}
                >
                  <Chip kind={ANOMALY_SEVERITY_TONES[a.severity] || 'info'} dot>
                    {ANOMALY_SEVERITY_LABELS[a.severity] || a.severity}
                  </Chip>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {a.title}
                    </div>
                    <div className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>
                      {a.entityType}/{a.entityId}
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// FASE 5 Project Cockpit (s110) — Widget "I miei progetti" cross-progetto.
// Legge /api/my-projects (auth-only): asTeamMember (sezione principale) +
// asTaskActor (collassabile: progetti con task ma non team-member).
const MYPROJ_HEALTH_TONE = { ok: 'ok', warn: 'warn', err: 'err' };
const MYPROJ_PRIORITY_TONE = { high: 'err', medium: 'warn', low: 'info' };
const MYPROJ_PRIORITY_LABEL = { high: 'urgente', medium: 'media', low: 'bassa' };

function MyProjectRow({ entry, isOther, onOpen }) {
  return (
    <div
      className="clickable"
      onClick={() => onOpen(entry.projectId)}
      data-myproj-id={entry.projectId}
      style={{ padding: '9px 0', borderBottom: '1px solid var(--line)', display: 'flex', gap: 10, alignItems: 'flex-start' }}
    >
      <Chip kind={MYPROJ_HEALTH_TONE[entry.healthSnapshot] || 'info'} dot>{entry.healthSnapshot}</Chip>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 12.5, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
          {entry.projectName}
        </div>
        <div className="row" style={{ gap: 6, marginTop: 3, flexWrap: 'wrap', alignItems: 'center' }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{entry.projectCode}</span>
          {isOther
            ? <Chip kind="warn">Non sei nel team</Chip>
            : entry.myRolesInProject.length > 0 && <Chip kind="info">{entry.myRolesInProject.join(' + ')}</Chip>}
        </div>
        {entry.topPriorityTask && (
          <div style={{ fontSize: 11, color: 'var(--text-2)', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            <Chip kind={MYPROJ_PRIORITY_TONE[entry.topPriorityTask.priority] || 'info'}>
              {MYPROJ_PRIORITY_LABEL[entry.topPriorityTask.priority] || entry.topPriorityTask.priority}
            </Chip>{' '}
            {entry.topPriorityTask.title}
          </div>
        )}
      </div>
      <div style={{ textAlign: 'right', flexShrink: 0 }}>
        <div className="mono" style={{ fontSize: 16, fontWeight: 700, color: entry.openTaskCount > 0 ? 'var(--accent)' : 'var(--text-3)' }}>
          {entry.openTaskCount}
        </div>
        <div style={{ fontSize: 9.5, color: 'var(--text-3)' }}>da fare</div>
      </div>
    </div>
  );
}

function MyProjectsCard() {
  const { user, navigate } = useStore();
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [errored, setErrored] = React.useState(false);
  const [showOthers, setShowOthers] = React.useState(false);

  React.useEffect(() => {
    if (!user?.id) return;
    let cancelled = false;
    (async () => {
      try {
        const r = await fetch('/api/my-projects', {
          credentials: 'same-origin',
          cache: 'no-store',
          headers: { 'X-Actor-Persona-Id': user.id },
        });
        if (!r.ok) { if (!cancelled) { setErrored(true); setData(null); } return; }
        const j = await r.json();
        if (!cancelled) { setData(j.data || null); setErrored(false); }
      } catch {
        if (!cancelled) { setErrored(true); setData(null); }
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [user?.id]);

  const team = data?.asTeamMember ?? [];
  const others = data?.asTaskActor ?? [];
  // Deep-link al tab "Il mio lavoro" (evita lo smart default landing).
  const openProject = (pid) => navigate('project_detail', `${pid}|mywork`);

  return (
    <div className="card" style={{ marginBottom: 16 }} data-widget="my-projects">
      <div className="card-header">
        <div>
          <div className="title"><Icon name="projects" size={12}/> I miei progetti {team.length > 0 ? `(${team.length})` : ''}</div>
          <div className="desc">Progetti dove sei nel team · clic per andare al tuo lavoro</div>
        </div>
      </div>
      <div className="card-body" style={{ paddingTop: 12 }}>
        {loading && <div style={{ fontSize: 12, color: 'var(--text-3)' }}>Caricamento…</div>}
        {!loading && errored && <div style={{ fontSize: 12, color: 'var(--text-3)' }}>Vista "I miei progetti" non disponibile.</div>}
        {!loading && !errored && team.length === 0 && others.length === 0 && (
          <div style={{ fontSize: 12.5 }}><Chip kind="ok" dot>nessun progetto assegnato</Chip></div>
        )}
        {!loading && !errored && team.map((e) => (
          <MyProjectRow key={e.projectId} entry={e} onOpen={openProject} />
        ))}
        {!loading && !errored && others.length > 0 && (
          <div style={{ marginTop: 10, borderTop: '1px solid var(--line)', paddingTop: 8 }}>
            <div
              className="clickable"
              onClick={() => setShowOthers((s) => !s)}
              data-action="toggle-other-projects"
              style={{ fontSize: 11.5, color: 'var(--text-2)', display: 'flex', gap: 6, alignItems: 'center' }}
            >
              <Icon name={showOthers ? 'chev_d' : 'chev_r'} size={12}/> Altri progetti con task ({others.length})
            </div>
            {showOthers && others.map((e) => (
              <MyProjectRow key={e.projectId} entry={e} isOther onOpen={openProject} />
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

// Sessione 65 — KPI strip role-specific.
// Mappa il `user.role` (string libera dal bootstrap) a una `roleKey` generica
// per scegliere quali 4 KPI mostrare nella strip principale. Default fallback
// se nessun match.
function detectRoleKey(roleName) {
  const n = (roleName || '').toLowerCase();
  // Match per substring "financ" copre 'finance', 'financial' (Chief Financial Officer)
  if (n.includes('cfo') || n.includes('financ') || n.includes('controller')) return 'cfo';
  if (n.includes('pm') || n.includes('project manager') || n.includes('project mgr') || n.includes('project capex')) return 'pm';
  if (n.includes('buyer') || n.includes('procurement') || n.includes('purchas')) return 'buyer';
  if (n.includes('plant') || n.includes('site manager') || n.includes('production')) return 'plant';
  if (n.includes('engineer') || n.includes('engineering') || n.includes('tech lead')) return 'eng';
  return 'default';
}

// Sessione 65 — Brief severity color mapping (UI).
const BRIEF_SEVERITY_TONES = { ok: 'ok', warning: 'warn', critical: 'err' };
const BRIEF_SEVERITY_LABELS = { ok: 'in linea', warning: 'attenzione', critical: 'critica' };

function Dashboard() {
  const { navigate, seed, pushToast, user } = useStore();
  // Sessione 65 — PORTFOLIO_BRIEFER modale state
  const [briefBusy, setBriefBusy] = React.useState(false);
  const [briefResult, setBriefResult] = React.useState(null);
  // Sessione 65 chiusura FASE 20 — Comms high-signal live aggregate.
  // null=loading, []=empty, [...]=fetched. Fallback su seed.COMMUNICATIONS se errore.
  const [commsLive, setCommsLive] = React.useState(null);
  const [commsLiveError, setCommsLiveError] = React.useState(false);

  // Sessione 65 chiusura FASE 20 — fetch /api/communications live; fallback su seed se fail.
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const r = await fetch('/api/communications', {
          credentials: 'same-origin',
          cache: 'no-store',
          headers: user?.id ? { 'X-Actor-Persona-Id': user.id } : {},
        });
        if (!r.ok) {
          if (!cancelled) { setCommsLiveError(true); setCommsLive([]); }
          return;
        }
        const j = await r.json();
        if (!cancelled) {
          setCommsLive(Array.isArray(j.data) ? j.data : []);
          setCommsLiveError(false);
        }
      } catch {
        if (!cancelled) { setCommsLiveError(true); setCommsLive([]); }
      }
    })();
    return () => { cancelled = true; };
  }, [user?.id]);
  const { KPI, PROJECTS, ALERTS, COMMUNICATIONS, RDA, VENDORS } = seed;
  const roleKey = detectRoleKey(user?.role);
  const atRisk = PROJECTS.filter((p) => p.health !== 'ok');
  const errProjects = PROJECTS.filter((p) => p.health === 'err');
  const warnProjects = PROJECTS.filter((p) => p.health === 'warn');
  const inProgress = PROJECTS.filter((p) => p.status === 'in_progress').length;

  // FASE MVP gap #2: brief AI calcolato dinamicamente dalle aggregazioni DB.
  // Niente hardcoded: counts/severity derivano da seed (alimentato da /api/bootstrap).
  const briefSeverity = errProjects.length >= 3 ? 'critica' : errProjects.length >= 1 || atRisk.length >= 4 ? 'moderatamente critica' : 'sotto controllo';
  const briefSeverityChipKind = errProjects.length >= 3 ? 'err' : errProjects.length >= 1 ? 'warn' : 'ok';
  const mostCritical = errProjects.length > 0
    ? [...errProjects].sort((a, b) => (a.progress || 0) - (b.progress || 0))[0]
    : (warnProjects.length > 0 ? warnProjects[0] : null);
  const incompleteRda = (RDA || []).filter((r) => r.status === 'incomplete' || r.status === 'draft' || r.docCompleteness < 1).length;
  const gatesPassed = PROJECTS.filter((p) => p.phase === 'esecuzione' || p.phase === 'chiusura').length;

  // P5 sessione 34 — KPI sparkline e delta calcolati dai dati reali, non più hardcoded.
  // Cashflow: 12 mesi piano vs attuale → ricavo serie attuale per sparkline + delta YoY ultimo vs primo.
  const cashflow = KPI?.cashflow || [];
  const cashflowActuals = cashflow.map((c) => Number(c.a) || 0);
  const cashflowPlanned = cashflow.map((c) => Number(c.p) || 0);
  const sumActual = cashflowActuals.reduce((a, b) => a + b, 0);
  const sumPlanned = cashflowPlanned.reduce((a, b) => a + b, 0);
  const capexDeltaPct = sumPlanned > 0 ? ((sumActual - sumPlanned) / sumPlanned) * 100 : 0;
  const capexDeltaLabel = (capexDeltaPct >= 0 ? '▲ +' : '▼ ') + capexDeltaPct.toFixed(1) + '% vs piano';
  const capexDeltaTone = Math.abs(capexDeltaPct) <= 5 ? 'up' : (capexDeltaPct < -5 ? 'down' : 'up');

  // Early warning (30gg): calcolo dai recenti 30/60/90gg di ALERTS.
  const now = new Date();
  function alertsInDays(d) {
    return ALERTS.filter((a) => {
      try { return (now - new Date(a.detected)) / (1000 * 3600 * 24) <= d; } catch { return false; }
    }).length;
  }
  const alertsLast30 = alertsInDays(30);
  const alertsLast60 = alertsInDays(60);
  const alertsPrev30 = Math.max(0, alertsLast60 - alertsLast30);
  const alertsTrendPct = alertsPrev30 > 0 ? ((alertsLast30 - alertsPrev30) / alertsPrev30) * 100 : 0;
  const alertsTrendLabel = (alertsTrendPct === 0 ? '— flat' : (alertsTrendPct > 0 ? '▲ +' : '▼ ') + Math.abs(alertsTrendPct).toFixed(0) + '% vs 30gg prec.');
  // Sparkline early-warning: bucket ALERTS per mese (ultimi 8 mesi).
  const alertsSpark = React.useMemo(() => {
    const buckets = new Array(8).fill(0);
    const start = new Date(now.getFullYear(), now.getMonth() - 7, 1);
    for (const a of ALERTS) {
      try {
        const d = new Date(a.detected);
        const idx = (d.getFullYear() - start.getFullYear()) * 12 + (d.getMonth() - start.getMonth());
        if (idx >= 0 && idx < 8) buckets[idx]++;
      } catch {}
    }
    return buckets.length && buckets.some(v => v > 0) ? buckets : [1, 1, 1, 1, 1, 1, 1, 1];
  }, [ALERTS]);
  // Sparkline cashflow attuale (M€).
  const cashflowSpark = cashflowActuals.length ? cashflowActuals.slice(-8) : [1, 1, 1, 1, 1, 1, 1, 1];
  // Sparkline AI hours saved: serie crescente proporzionale (KPI è cumulativo).
  const aiTimeSaved = KPI?.portfolio?.aiTimeSaved || 0;
  const aiSpark = React.useMemo(() => {
    if (aiTimeSaved <= 0) return [0, 0, 0, 0, 0, 0, 0, 1];
    return [0.18, 0.27, 0.34, 0.5, 0.65, 0.79, 0.93, 1.0].map((p) => Math.round(aiTimeSaved * p));
  }, [aiTimeSaved]);

  // Esporta dashboard snapshot come JSON download.
  function handleExport() {
    const blob = new Blob([JSON.stringify({
      generatedAt: new Date().toISOString(),
      kpi: KPI,
      projects: PROJECTS.map((p) => ({ id: p.id, code: p.code, name: p.name, site: p.site, phase: p.phase, progress: p.progress, budget: p.budget, health: p.health })),
      alerts: { last30: alertsLast30, prev30: alertsPrev30, trendPct: alertsTrendPct },
      capex: { committed: KPI.portfolio?.committed, spent: KPI.portfolio?.spent, deltaVsPlanPct: capexDeltaPct },
    }, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `veridanto-dashboard-${new Date().toISOString().slice(0, 10)}.json`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    pushToast({ title: 'Dashboard esportata', desc: 'Snapshot scaricato in JSON.', tone: 'ok' });
  }

  // Sessione 65 — PORTFOLIO_BRIEFER agent reale (sostituisce stub copilot).
  async function handleBriefAi() {
    if (briefBusy) return;
    setBriefBusy(true);
    setBriefResult(null);
    try {
      // Sessione 67 — Inietta X-AI-*-Override se l'utente ha override attivo
      // (sessione 66 AiPersonalOverrideCard).
      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/portfolio-briefer/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: 'Brief AI 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;
      }
      setBriefResult(j);
      pushToast({
        title: `Brief AI · ${j.brief?.severity}`,
        desc: `${j.provider}/${j.model} · ${j.brief?.headlines?.length ?? 0} headline · ${j.brief?.risks?.length ?? 0} rischi · ${j.durationMs}ms`,
        tone: BRIEF_SEVERITY_TONES[j.brief?.severity] || 'info',
      });
    } catch (e) {
      pushToast({ title: 'Brief AI errore rete', desc: String(e?.message || e), tone: 'err' });
    } finally {
      setBriefBusy(false);
    }
  }

  function handleShareBrief() {
    const text = `Veridanto · Brief CAPEX ${new Date().toLocaleDateString('it-IT')}: portfolio ${briefSeverity}. ${atRisk.length} progetti a rischio, ${incompleteRda} RdA incomplete, ${gatesPassed} gate superati.`;
    if (navigator.clipboard) {
      navigator.clipboard.writeText(text).then(() => {
        pushToast({ title: 'Brief copiato', desc: 'Incollalo dove serve.', tone: 'ok' });
      }).catch(() => pushToast({ title: 'Copia fallita', desc: text.slice(0, 80), tone: 'err' }));
    } else {
      pushToast({ title: 'Brief', desc: text, tone: 'info' });
    }
  }

  return (
    <div className="page fade-in">
      <div className="page-header">
        <div>
          <div className="eyebrow">Industrial portfolio · AI-assisted</div>
          <h1 className="page-title">Portfolio CAPEX in un colpo d'occhio</h1>
          <div className="page-sub">{inProgress} progett{inProgress === 1 ? 'o attivo' : 'i attivi'} su {(seed.SITES || []).length} sit{(seed.SITES || []).length === 1 ? 'o' : 'i'}. L'agente osserva in continuo mail, meeting, SAL e archivio storico per anticipare deviazioni e preparare bozze documentali sotto la tua supervisione.</div>
        </div>
        <div className="actions">
          <Btn variant="ghost" size="sm" onClick={handleExport}><Icon name="download" size={13}/> Esporta</Btn>
          <Btn variant="ai" size="sm" onClick={handleBriefAi} disabled={briefBusy}>
            <Icon name="sparkle" size={13}/> {briefBusy ? 'Brief in corso…' : 'Brief AI della settimana'}
          </Btn>
        </div>
      </div>

      {(() => {
        // Sessione 65 — KPI strip role-specific.
        // Calcoli condivisi (dati da seed bootstrap, no hardcoded).
        const portfolioBurnPct = KPI.portfolio.capexTotal > 0
          ? Math.round(((KPI.portfolio.committed + KPI.portfolio.spent) / KPI.portfolio.capexTotal) * 100)
          : 0;
        const projectsAvgProgress = PROJECTS.length > 0
          ? Math.round(PROJECTS.reduce((acc, p) => acc + (p.progress || 0), 0) / PROJECTS.length)
          : 0;
        const projectsOk = PROJECTS.filter((p) => p.health === 'ok').length;
        const projectsHealthPct = PROJECTS.length > 0
          ? Math.round((projectsOk / PROJECTS.length) * 100)
          : 0;
        const milestonesOverdue = PROJECTS.reduce((acc, p) => {
          const ml = Array.isArray(p.milestones) ? p.milestones : [];
          return acc + ml.filter((m) => {
            try {
              if (m.status === 'done') return false;
              return m.due && new Date(m.due) < now;
            } catch { return false; }
          }).length;
        }, 0);
        const rdaPending = (RDA || []).filter((r) => r.status === 'draft' || r.status === 'in_review' || r.status === 'incomplete').length;
        const rdaTotal = (RDA || []).length;
        const vendorsActive = (VENDORS || []).filter((v) => v.active !== false && (v.status === 'qualified' || !v.status)).length;
        const benchmarks = seed.BENCHMARK || [];
        const benchmarkAvgDeviationBp = benchmarks.length > 0
          ? Math.round(benchmarks.reduce((acc, b) => acc + Math.abs(b.deltaBp || 0), 0) / benchmarks.length)
          : 0;
        const rdaCommittedTotal = (RDA || []).reduce((acc, r) => acc + (r.amountEur || 0), 0);
        const savingEstimate = Math.round(rdaCommittedTotal * 0.05); // approssimato 5% target

        // Variants per role. Ogni variant ha 4 cards.
        const variants = {
          default: [
            { label: 'CAPEX approvato 2026', value: fmtEUR(KPI.portfolio.capexTotal, true), delta: capexDeltaLabel, tone: capexDeltaTone, spark: <Spark data={cashflowSpark} width={140} /> },
            { label: 'Impegnato · speso', value: fmtEUR(KPI.portfolio.committed, true), delta: 'speso ' + fmtEUR(KPI.portfolio.spent, true), tone: '', spark: <Meter value={(KPI.portfolio.spent / Math.max(KPI.portfolio.committed, 1)) * 100} tone="ok" thick /> },
            { label: 'Ore uomo risparmiate · AI', value: KPI.portfolio.aiTimeSaved, unit: ' h', delta: '≈ ' + fmtEUR(KPI.portfolio.aiTimeSaved * 85, true) + ' equivalenti', tone: 'up', spark: <Spark data={aiSpark} width={140} tone="ok" /> },
            { label: 'Early warning (30gg)', value: alertsLast30, delta: alertsTrendLabel, tone: alertsTrendPct < 0 ? 'up' : alertsTrendPct > 0 ? 'down' : '', spark: <Spark data={alertsSpark} width={140} tone="warn" /> },
          ],
          cfo: [
            { label: 'CAPEX approvato 2026', value: fmtEUR(KPI.portfolio.capexTotal, true), delta: capexDeltaLabel, tone: capexDeltaTone, spark: <Spark data={cashflowSpark} width={140} /> },
            { label: 'Impegnato · speso', value: fmtEUR(KPI.portfolio.committed, true), delta: 'speso ' + fmtEUR(KPI.portfolio.spent, true), tone: '', spark: <Meter value={(KPI.portfolio.spent / Math.max(KPI.portfolio.committed, 1)) * 100} tone="ok" thick /> },
            { label: 'Burn rate portfolio', value: portfolioBurnPct, unit: ' %', delta: portfolioBurnPct >= 80 ? 'attenzione' : 'in linea', tone: portfolioBurnPct >= 80 ? 'down' : 'up', spark: <Meter value={portfolioBurnPct} tone={portfolioBurnPct >= 80 ? 'warn' : 'ok'} thick /> },
            { label: 'Forecast deviation', value: capexDeltaPct.toFixed(1), unit: ' %', delta: capexDeltaPct >= 0 ? 'sopra piano' : 'sotto piano', tone: Math.abs(capexDeltaPct) <= 5 ? 'up' : 'down', spark: <Spark data={cashflowSpark} width={140} /> },
          ],
          pm: [
            { label: 'Progetti attivi', value: inProgress, delta: PROJECTS.length + ' totali', tone: '', spark: null },
            { label: 'Milestone overdue', value: milestonesOverdue, delta: milestonesOverdue === 0 ? 'tutto in linea' : 'da recuperare', tone: milestonesOverdue === 0 ? 'up' : 'down', spark: null },
            { label: 'Health portfolio', value: projectsHealthPct, unit: ' %', delta: projectsOk + '/' + PROJECTS.length + ' ok', tone: projectsHealthPct >= 70 ? 'up' : 'down', spark: <Meter value={projectsHealthPct} tone={projectsHealthPct >= 70 ? 'ok' : 'warn'} thick /> },
            { label: 'Avanzamento medio', value: projectsAvgProgress, unit: ' %', delta: 'media portfolio', tone: '', spark: <Meter value={projectsAvgProgress} tone="ok" thick /> },
          ],
          buyer: [
            { label: 'RDA in attesa', value: rdaPending, delta: rdaTotal + ' totali', tone: rdaPending === 0 ? 'up' : '', spark: null },
            { label: 'Vendor attivi', value: vendorsActive, delta: 'qualificati', tone: '', spark: null },
            { label: 'Benchmark deviation', value: (benchmarkAvgDeviationBp / 10).toFixed(1), unit: ' %', delta: 'media |delta|', tone: benchmarkAvgDeviationBp / 10 <= 5 ? 'up' : 'down', spark: null },
            { label: 'Saving stimato (5%)', value: fmtEUR(savingEstimate, true), delta: 'su impegnato RDA', tone: 'up', spark: null },
          ],
          plant: [
            { label: 'Progetti per sito', value: (seed.SITES || []).length, delta: PROJECTS.length + ' progetti', tone: '', spark: null },
            { label: 'Anomalie aperte', value: alertsLast30, delta: alertsTrendLabel, tone: alertsTrendPct < 0 ? 'up' : 'down', spark: <Spark data={alertsSpark} width={140} tone="warn" /> },
            { label: 'Avanzamento medio', value: projectsAvgProgress, unit: ' %', delta: 'media portfolio', tone: '', spark: <Meter value={projectsAvgProgress} tone="ok" thick /> },
            { label: 'Milestone overdue', value: milestonesOverdue, delta: milestonesOverdue === 0 ? 'in linea' : 'da recuperare', tone: milestonesOverdue === 0 ? 'up' : 'down', spark: null },
          ],
          eng: [
            { label: 'Progetti attivi', value: inProgress, delta: PROJECTS.length + ' totali', tone: '', spark: null },
            { label: 'RDA in attesa', value: rdaPending, delta: rdaTotal + ' totali', tone: '', spark: null },
            { label: 'Anomalie aperte', value: alertsLast30, delta: alertsTrendLabel, tone: alertsTrendPct < 0 ? 'up' : 'down', spark: <Spark data={alertsSpark} width={140} tone="warn" /> },
            { label: 'Ore uomo risparmiate · AI', value: KPI.portfolio.aiTimeSaved, unit: ' h', delta: '≈ ' + fmtEUR(KPI.portfolio.aiTimeSaved * 85, true) + ' equivalenti', tone: 'up', spark: <Spark data={aiSpark} width={140} tone="ok" /> },
          ],
        };
        const cards = variants[roleKey] || variants.default;
        return (
          <div className="grid grid-4" style={{ marginBottom: 16 }} data-role-kpi={roleKey}>
            {cards.map((c, i) => (
              <div className="card" key={i}>
                <Stat label={c.label} value={c.value} unit={c.unit} delta={c.delta} tone={c.tone} spark={c.spark} />
              </div>
            ))}
          </div>
        );
      })()}

      <MyProjectsCard />

      <div className="grid" style={{ gridTemplateColumns: '2fr 1fr', marginBottom: 16 }}>
        <div className="card">
          <div className="card-header">
            <div>
              <div className="title">Health portfolio · {PROJECTS.length} progett{PROJECTS.length === 1 ? 'o' : 'i'}</div>
              <div className="desc">Stato operativo e avanzamento per progetto attivo</div>
            </div>
            <div className="actions">
              <Btn variant="ghost" size="sm" onClick={() => navigate('projects')}>Vedi tutti <Icon name="chev_r" size={12}/></Btn>
            </div>
          </div>
          <table className="tbl">
            <thead>
              <tr>
                <th>Progetto</th>
                <th>Sito</th>
                <th>Fase</th>
                <th style={{ width: 160 }}>Avanzamento</th>
                <th className="num">Budget</th>
                <th>Health</th>
              </tr>
            </thead>
            <tbody>
              {PROJECTS.slice(0, 7).map((p) => (
                <tr key={p.id} className="clickable" onClick={() => navigate('project_detail', p.id)}>
                  <td>
                    <div style={{ fontWeight: 500 }}>{p.name}</div>
                    <div className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{p.code}</div>
                  </td>
                  <td style={{ color: 'var(--text-2)', fontSize: 11.5 }}>{p.site}</td>
                  <td><PhaseChip phase={p.phase} /></td>
                  <td>
                    <div className="row" style={{ gap: 8 }}>
                      <Meter value={p.progress} tone={p.health === 'err' ? 'err' : p.health === 'warn' ? 'warn' : 'ok'} />
                      <span className="num" style={{ fontSize: 11 }}>{p.progress}%</span>
                    </div>
                  </td>
                  <td className="num">{fmtEUR(p.budget, true)}</td>
                  <td><Health h={p.health} /></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        <div className="card">
          <div className="card-header">
            <div>
              <div className="title"><Icon name="sparkle" size={12}/> Brief AI della settimana</div>
              <div className="desc">Sintesi generata sui segnali degli ultimi 7 giorni</div>
            </div>
          </div>
          <div className="card-body" style={{ fontSize: 12.5, lineHeight: 1.6, color: 'var(--text-1)' }}>
            <p style={{ margin: 0 }}>
              Settimana <strong>{briefSeverity}</strong>. {atRisk.length === 0 ? (
                <>Nessun progetto fuori parametri sui KPI principali.</>
              ) : (
                <>{atRisk.length} progett{atRisk.length === 1 ? 'o mostra' : 'i mostrano'} segnali di deviazione{mostCritical ? <>: <strong>{mostCritical.code} ({mostCritical.name})</strong> è il più urgente con avanzamento al {mostCritical.progress || 0}%.</> : '.'}</>
              )}
            </p>
            <p style={{ marginTop: 10 }}>
              {incompleteRda > 0 ? <>{incompleteRda} RdA al momento incomplet{incompleteRda === 1 ? 'a' : 'e'}; il copilota AI può preparare email di follow-up al click.</> : <>Tutte le RdA hanno la documentazione completa.</>}
              {' '}
              {gatesPassed > 0 && <>Sono stati superati {gatesPassed} gate fase su {PROJECTS.length} progetti.</>}
            </p>
            <div className="sep" />
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {errProjects.length > 0 && <Chip kind="err" dot>{errProjects.length} progett{errProjects.length === 1 ? 'o critic' : 'i critic'}{errProjects.length === 1 ? 'o' : 'i'}</Chip>}
              {warnProjects.length > 0 && <Chip kind="warn" dot>{warnProjects.length} in warning</Chip>}
              {incompleteRda > 0 && <Chip kind="warn" dot>{incompleteRda} RdA incomplet{incompleteRda === 1 ? 'a' : 'e'}</Chip>}
              {gatesPassed > 0 && <Chip kind="ok" dot>{gatesPassed} gate superati</Chip>}
              {errProjects.length === 0 && warnProjects.length === 0 && incompleteRda === 0 && <Chip kind="ok" dot>portfolio in linea</Chip>}
            </div>
            <div style={{ marginTop: 12, display: 'flex', gap: 6 }}>
              <Btn variant="ai" size="sm" onClick={() => navigate('alerts')}>Vai agli alert</Btn>
              <Btn variant="ghost" size="sm" onClick={handleShareBrief}>Condividi brief</Btn>
            </div>
          </div>
        </div>
      </div>

      <div className="grid" style={{ gridTemplateColumns: '1fr 2fr', marginBottom: 16 }}>
        <AnomaliesOpenWidget />
        <div className="card">
          <div className="card-header">
            <div>
              <div className="title">Cashflow CAPEX 12 mesi</div>
              <div className="desc">Pianificato vs attuale (M€)</div>
            </div>
          </div>
          <div className="card-body" style={{ paddingTop: 8 }}>
            <MiniBars data={KPI.cashflow} width={520} height={110} />
            <div className="row" style={{ marginTop: 8, fontSize: 11, color: 'var(--text-2)' }}>
              <span className="row"><span style={{ width: 10, height: 10, background: 'var(--bg-4)', display: 'inline-block' }} /> pianificato</span>
              <span className="row"><span style={{ width: 10, height: 10, background: 'var(--accent)', display: 'inline-block' }} /> attuale</span>
            </div>
          </div>
        </div>
      </div>

      <div className="grid" style={{ gridTemplateColumns: '1fr 1fr' }}>
        <div className="card">
          <div className="card-header">
            <div className="title">Top segnali early-warning</div>
            <div className="actions"><Btn variant="ghost" size="sm" onClick={() => navigate('alerts')}>Vedi tutti</Btn></div>
          </div>
          <div className="card-body" style={{ padding: 0 }}>
            {ALERTS.slice(0, 3).map((a) => (
              <div key={a.id} className="alert-card" style={{ borderRadius: 0, borderLeft: 'none', borderRight: 'none', borderTop: 'none' }}>
                <div className="bar" style={{ background: a.severity === 'err' ? 'var(--err)' : a.severity === 'warn' ? 'var(--warn)' : a.severity === 'ok' ? 'var(--ok)' : 'var(--info)' }} />
                <div>
                  <div className="ttl">{a.title}</div>
                  <div className="meta">{a.project} · {a.source} · conf. {(a.confidence*100).toFixed(0)}%</div>
                </div>
                <Icon name="chev_r" size={14} />
              </div>
            ))}
          </div>
        </div>

        {(() => {
          // Sessione 65 chiusura FASE 20 — Comms high-signal embedded.
          // Sorgente: API live (/api/communications) → filter signal !== 'ok' → top 4.
          // Fallback: seed.COMMUNICATIONS top 4 se fetch fallisce o restituisce vuoto.
          const HIGH_SIGNAL_PRIORITY = { delay: 0, risk: 1, action: 2, decision: 3, offer: 4, ok: 5 };
          const useLive = commsLive !== null && commsLive.length > 0 && !commsLiveError;
          let topComms = [];
          let sourceLabel = '';
          if (useLive) {
            const filtered = commsLive.filter((c) => c.signal && c.signal !== 'ok');
            const ordered = (filtered.length > 0 ? filtered : commsLive)
              .slice()
              .sort((a, b) => {
                const pa = HIGH_SIGNAL_PRIORITY[a.signal] ?? 9;
                const pb = HIGH_SIGNAL_PRIORITY[b.signal] ?? 9;
                if (pa !== pb) return pa - pb;
                return (b.date || '').localeCompare(a.date || '');
              });
            topComms = ordered.slice(0, 4);
            sourceLabel = `live · ${commsLive.length} totali · ${filtered.length} high-signal`;
          } else if (commsLive === null) {
            topComms = COMMUNICATIONS.slice(0, 4);
            sourceLabel = 'caricamento…';
          } else {
            topComms = COMMUNICATIONS.slice(0, 4);
            sourceLabel = commsLiveError ? 'fallback seed (API non disponibile)' : 'fallback seed (no data)';
          }
          return (
            <div className="card" data-comms-source={useLive ? 'live' : 'seed'}>
              <div className="card-header">
                <div>
                  <div className="title">Comunicazioni rilevanti</div>
                  <div className="desc" style={{ fontSize: 10, color: 'var(--text-3)' }}>{sourceLabel}</div>
                </div>
                <div className="actions"><Btn variant="ghost" size="sm" onClick={() => navigate('communications')}>Vai</Btn></div>
              </div>
              <div className="card-body" style={{ padding: 0 }}>
                {topComms.length === 0 && (
                  <div style={{ padding: 16, fontSize: 11, color: 'var(--text-3)', textAlign: 'center' }}>
                    Nessuna comunicazione rilevante.
                  </div>
                )}
                {topComms.map((c) => (
                  <div key={c.id} style={{ padding: '10px 14px', borderBottom: '1px solid var(--line)' }}>
                    <div className="row" style={{ gap: 6, marginBottom: 3 }}>
                      <Icon name={c.kind === 'email' ? 'mail' : c.kind === 'meeting' ? 'users' : 'docs'} size={12} />
                      <span style={{ fontSize: 12, fontWeight: 500 }}>{c.subject}</span>
                    </div>
                    <div style={{ fontSize: 10.5, fontFamily: 'var(--font-mono)', color: 'var(--text-3)' }}>{(c.project || c.projectId || '—')} · {c.date}</div>
                    <div style={{ marginTop: 4 }}>
                      {c.signal === 'delay' && <Chip kind="err" dot>ritardo</Chip>}
                      {c.signal === 'risk' && <Chip kind="warn" dot>rischio</Chip>}
                      {c.signal === 'action' && <Chip kind="info" dot>action item</Chip>}
                      {c.signal === 'ok' && <Chip kind="ok" dot>ok</Chip>}
                      {c.signal === 'decision' && <Chip kind="info" dot>decisione</Chip>}
                      {c.signal === 'offer' && <Chip kind="info" dot>offerta</Chip>}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          );
        })()}
      </div>

      {briefResult && typeof window !== 'undefined' && window.Modal && (
        <window.Modal
          open
          onClose={() => setBriefResult(null)}
          title={`Brief AI · ${briefResult.provider}/${briefResult.model}`}
          size="lg"
          footer={
            <>
              <Btn variant="ghost" size="sm" onClick={() => setBriefResult(null)}>Chiudi</Btn>
            </>
          }
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <div className="row" style={{ gap: 8, alignItems: 'center' }}>
              <Chip kind={BRIEF_SEVERITY_TONES[briefResult.brief?.severity] || 'info'} dot>
                {BRIEF_SEVERITY_LABELS[briefResult.brief?.severity] || briefResult.brief?.severity}
              </Chip>
              <span style={{ fontSize: 11, color: 'var(--text-3)' }}>
                Context: {briefResult.ctxSize?.totalProjects ?? 0} progetti · {briefResult.ctxSize?.topRiskProjects ?? 0} top risk · {briefResult.ctxSize?.anomaliesOpen ?? 0} anomalie
              </span>
            </div>

            <div>
              <div className="eyebrow">Sintesi</div>
              <p style={{ fontSize: 12.5, lineHeight: 1.6, marginTop: 6 }}>{briefResult.brief?.summary}</p>
            </div>

            {briefResult.brief?.headlines?.length > 0 && (
              <div>
                <div className="eyebrow">Headline</div>
                <ul style={{ marginTop: 6, paddingLeft: 18, fontSize: 12, lineHeight: 1.6 }}>
                  {briefResult.brief.headlines.map((h, i) => <li key={i}>{h}</li>)}
                </ul>
              </div>
            )}

            {briefResult.brief?.risks?.length > 0 && (
              <div>
                <div className="eyebrow">Rischi</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 6 }}>
                  {briefResult.brief.risks.map((r, i) => (
                    <div key={i} style={{ padding: 10, background: 'var(--bg-2)', borderRadius: 4, borderLeft: '3px solid var(--err)' }}>
                      <div style={{ fontSize: 12, fontWeight: 500 }}>{r.title}</div>
                      <div style={{ fontSize: 11.5, marginTop: 4, color: 'var(--text-2)' }}>{r.description}</div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {briefResult.brief?.actions?.length > 0 && (
              <div>
                <div className="eyebrow">Azioni raccomandate</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 6 }}>
                  {briefResult.brief.actions.map((a, i) => (
                    <div key={i} style={{ padding: 8, background: 'var(--bg-2)', borderRadius: 4, borderLeft: '3px solid var(--info)' }}>
                      <div style={{ fontSize: 12 }}>
                        <strong>{a.title}</strong>
                        {a.owner && <span className="mono" style={{ fontSize: 10.5, color: 'var(--text-3)', marginLeft: 8 }}>· {a.owner}</span>}
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            )}
          </div>
        </window.Modal>
      )}
    </div>
  );
}
Object.assign(window, { Dashboard });
