/* global React, Panel, Sigil, Dot, Btn, Icon, Sparkline, BarSpark, AnimNum, RefChip, seedRng, hashStr */
// Studio solaire — cartographic workspace for PV potential studies
// Backend contract: TechVisit.satelliteData = { geocode, rooftop, altitude, pvgis, calpinage }
// Routes: /api/satellite/{geocode,rooftop,pvgis,altitude} + POST /api/visite/:id/satellite

const { useState, useMemo, useEffect, useRef } = React;

// ─── Solar projects — seed fallback (hydratés depuis backend via api.js) ──
const SOLAR_PROJECTS_SEED = [
  {
    ref: 'SOL-2026-014', site: 'Ducretet Industries · Hall B',
    address: 'ZA des Roches, 72000 Le Mans',
    lat: 48.0061, lng: 0.1996,
    surfaceToiture: 2840, surfaceExploitable: 1690,
    orientation: 182, tilt: 8, kwc: 312, production: 358400,
    panels: 780, exclusions: 4, shadingLoss: 6.2,
    autoconsoTaux: 72, co2: 21.4, fost: 'BAR-EN-101',
    ceeCumac: 412000, primeCee: 2680, capex: 398000, roiYears: 6.8,
    status: 'simulation', updated: 'il y a 12 min',
    zone: 'H1c', techVisit: 'VT-2026-028',
  },
  {
    ref: 'SOL-2026-011', site: 'Résidence Mirabeau',
    address: '14 rue Mirabeau, 75016 Paris',
    lat: 48.8539, lng: 2.2762,
    surfaceToiture: 412, surfaceExploitable: 186,
    orientation: 164, tilt: 28, kwc: 34, production: 38200,
    panels: 85, exclusions: 2, shadingLoss: 11.4,
    autoconsoTaux: 88, co2: 2.3, fost: 'BAR-TH-143',
    ceeCumac: 62000, primeCee: 405, capex: 68000, roiYears: 9.2,
    status: 'signée', updated: 'hier · 18h42',
    zone: 'H1a', techVisit: 'VT-2026-031',
  },
  {
    ref: 'SOL-2026-009', site: 'Château d\'Aubec · Orangerie',
    address: 'Domaine Aubec, 49400 Saumur',
    lat: 47.2583, lng: -0.0791,
    surfaceToiture: 680, surfaceExploitable: 520,
    orientation: 197, tilt: 22, kwc: 98, production: 119000,
    panels: 245, exclusions: 1, shadingLoss: 3.1,
    autoconsoTaux: 64, co2: 7.1, fost: 'BAR-EN-101',
    ceeCumac: 148000, primeCee: 966, capex: 128000, roiYears: 7.1,
    status: 'draft', updated: 'il y a 2 j',
    zone: 'H2a', techVisit: null,
  },
  {
    ref: 'SOL-2026-007', site: 'Groupe Vélan · Entrepôt Nord',
    address: 'Route de Rennes, 44300 Nantes',
    lat: 47.2938, lng: -1.5597,
    surfaceToiture: 8400, surfaceExploitable: 6200,
    orientation: 176, tilt: 5, kwc: 1240, production: 1420000,
    panels: 3100, exclusions: 12, shadingLoss: 4.7,
    autoconsoTaux: 45, co2: 85.2, fost: 'BAR-EN-101',
    ceeCumac: 1680000, primeCee: 10940, capex: 1450000, roiYears: 5.4,
    status: 'étude', updated: 'il y a 4 h',
    zone: 'H2a', techVisit: 'VT-2026-035',
  },
  {
    ref: 'SOL-2026-004', site: 'Manufacture Pelletier',
    address: 'Quai des Minimes, 37000 Tours',
    lat: 47.3900, lng: 0.6889,
    surfaceToiture: 3100, surfaceExploitable: 2040,
    orientation: 168, tilt: 12, kwc: 408, production: 467000,
    panels: 1020, exclusions: 6, shadingLoss: 5.8,
    autoconsoTaux: 81, co2: 28.0, fost: 'BAR-EN-101',
    ceeCumac: 528000, primeCee: 3432, capex: 496000, roiYears: 6.2,
    status: 'simulation', updated: 'il y a 3 h',
    zone: 'H2a', techVisit: 'VT-2026-029',
  },
];
// Expose seed for api.js and fallback
window.SOLAR_PROJECTS_SEED = SOLAR_PROJECTS_SEED;
if (!window.SOLAR_PROJECTS) window.SOLAR_PROJECTS = SOLAR_PROJECTS_SEED;
const SOLAR_PROJECTS = SOLAR_PROJECTS_SEED; // kept as backward-compat alias

// Monthly production (kWh) for Le Mans, 312 kWc, S-oriented, 8° tilt — PVGIS-ish
const MONTHLY_PRODUCTION = [
  { m: 'Jan', v: 12400, irr: 51 },
  { m: 'Fév', v: 18600, irr: 74 },
  { m: 'Mar', v: 29200, irr: 118 },
  { m: 'Avr', v: 36400, irr: 149 },
  { m: 'Mai', v: 41800, irr: 168 },
  { m: 'Juin', v: 44900, irr: 180 },
  { m: 'Juil', v: 46200, irr: 186 },
  { m: 'Août', v: 42100, irr: 172 },
  { m: 'Sep', v: 34700, irr: 141 },
  { m: 'Oct', v: 25600, irr: 102 },
  { m: 'Nov', v: 14800, irr: 58 },
  { m: 'Déc', v: 11700, irr: 47 },
];

// Hourly irradiance (typical sunny June day)
const HOURLY_IRRADIANCE = [
  0, 0, 0, 0, 0, 8, 42, 120, 245, 410, 580, 720, 810, 820, 770, 650, 480, 300, 150, 60, 18, 2, 0, 0,
];
// Expose pour les exports PDF (ExportPane lit window.MONTHLY_PRODUCTION)
window.MONTHLY_PRODUCTION = MONTHLY_PRODUCTION;
window.HOURLY_IRRADIANCE = HOURLY_IRRADIANCE;

// Scenarios
const SCENARIOS = [
  { id: 'A', name: 'Base — Sud, 8°', kwc: 312, prod: 358400, ratio: 1.00, capex: 398000 },
  { id: 'B', name: 'Sud-Ouest, 15°', kwc: 312, prod: 366800, ratio: 1.02, capex: 398000 },
  { id: 'C', name: '+12 panneaux zone nord', kwc: 340, prod: 374100, ratio: 1.04, capex: 431000 },
  { id: 'D', name: 'Bifacial, 20°', kwc: 312, prod: 384200, ratio: 1.07, capex: 448000 },
];

// ─── Main screen ─────────────────────────────────────────────
function SolarStudio() {
  // Hydrated list of solar projects from backend (fallback on seed)
  const [projects, setProjects] = useState(() => window.SOLAR_PROJECTS || SOLAR_PROJECTS_SEED);
  useEffect(() => {
    const refresh = () => setProjects([...(window.SOLAR_PROJECTS || SOLAR_PROJECTS_SEED)]);
    refresh();
    window.addEventListener('ae:hydrated', refresh);
    window.addEventListener('ae:solar-updated', refresh);
    return () => {
      window.removeEventListener('ae:hydrated', refresh);
      window.removeEventListener('ae:solar-updated', refresh);
    };
  }, []);

  const [projectIdx, setProjectIdx] = useState(0);
  const project = projects[projectIdx] || projects[0];
  const [tool, setTool] = useState('select');
  const [scenarioId, setScenarioId] = useState('A');
  const [showShading, setShowShading] = useState(true);
  const [showIrradiance, setShowIrradiance] = useState(true);
  const [mapLayer, setMapLayer] = useState('satellite');
  const [view, setView] = useState('2d'); // 2d | 3d
  const [newProjectOpen, setNewProjectOpen] = useState(false);
  const [editProjectOpen, setEditProjectOpen] = useState(false);
  // Calpinage live : roof/panels/exclusions édités dans le canvas (lift-up pour save)
  const [calpinage, setCalpinage] = useState(null);
  const [saveState, setSaveState] = useState({ loading: false, message: null });

  // ─── Actions projet (Règle n°5 CRUD — P2) ─────────────────────
  const handleDuplicate = async () => {
    const src = project;
    const copy = {
      ...src,
      ref: 'SOL-' + new Date().getFullYear() + '-' + String(Date.now()).slice(-4),
      site: src.site + ' (copie)',
      _visitId: null,
      _projectId: null, // remis à null pour forcer un nouveau POST au prochain save
    };
    // Si le source est persisté, dupliquer aussi côté backend
    if (src._projectId) {
      const API = (window.AE_API && window.AE_API.BASE) || '';
      try {
        const r = await fetch(`${API}/api/solar/projects/${src._projectId}/duplicate`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}),
        }).then(x => x.json());
        if (r?.ok) copy._projectId = r.project.id;
      } catch (_) { /* duplication backend non bloquante */ }
    }
    const next = [copy, ...projects];
    setProjects(next);
    window.SOLAR_PROJECTS = next;
    setProjectIdx(0);
  };

  const handleDelete = async () => {
    if (!window.confirm(`Supprimer le projet ${project.ref} · ${project.site} ?`)) return;
    const API = (window.AE_API && window.AE_API.BASE) || '';
    if (project._visitId) {
      await fetch(`${API}/api/visite/visits/${project._visitId}`, { method: 'DELETE' }).catch(() => {});
    }
    if (project._projectId) {
      await fetch(`${API}/api/solar/projects/${project._projectId}`, { method: 'DELETE' }).catch(() => {});
    }
    const next = projects.filter((_, i) => i !== projectIdx);
    const fallback = next.length ? next : SOLAR_PROJECTS_SEED;
    setProjects(fallback);
    window.SOLAR_PROJECTS = fallback;
    setProjectIdx(0);
  };

  // ─── Inputs dimensionnement (sections A→E portées depuis ancienne V) ─
  // Initialisés depuis le projet courant, mémorisés par ref
  const defaultInputs = (p) => ({
    // A — Contact client
    clientNom: p?._contact?.nom || '',
    clientPrenom: p?._contact?.prenom || '',
    clientSociete: p?._contact?.societe || p?.site || '',
    clientEmail: p?._contact?.email || '',
    clientTel: p?._contact?.tel || '',
    clientAdresse: p?._contact?.adresse || p?.address || '',
    // B — Type
    type: p?._type || 'toiture_ter',
    zone: p?.zone && p.zone.startsWith('H') ? p.zone.slice(0, 2) : 'H2',
    orient: p?._orient || 'S',
    inclin: String(p?.tilt || 30),
    // C — Panneaux
    panneauKey: p?._panneauKey || 'longi_himo6_440',
    wc: p?._wc || 440,
    nb: p?.panels || 50,
    pertes: p?.shadingLoss || 14,
    // D — Conso & tarifs
    conso: p?._conso || 85000,
    tachat: p?._tachat || 0.2276,
    trevente: p?._trevente || 0.1307,
    cout: p?.capex || 31200,
    // E — Batterie (MyLight ou autre constructeur)
    myLight: !!p?._myLight,
    battMarque: p?._battMarque || 'mylight_virtuelle',
    battKwh: p?._battKwh || 150,
  });
  const [inputs, setInputs] = useState(() => defaultInputs(project));
  useEffect(() => { setInputs(defaultInputs(project)); }, [project && project.ref]);
  const setInput = (key, val) => setInputs((x) => ({ ...x, [key]: val }));

  // Résultats live via calcSolaire (moteur porté 1:1)
  const results = React.useMemo(() => {
    if (!window.SOLAR_DATA) return null;
    return window.SOLAR_DATA.calcSolaire(inputs);
  }, [inputs]);

  if (!project) {
    return (
      <div style={{ display: 'grid', gridTemplateColumns: '264px 1fr', height: 'calc(100vh - 52px)', background: 'var(--paper)' }}>
        <SolarProjectList projects={projects} activeIdx={projectIdx} onSelect={setProjectIdx} onNew={() => setNewProjectOpen(true)} />
        <div style={{ display: 'grid', placeItems: 'center', color: 'var(--ink-4)' }}>Sélectionne un projet ou crée-en un nouveau</div>
        {newProjectOpen && <NewSolarProjectModal onClose={() => setNewProjectOpen(false)} onCreated={(p) => { setNewProjectOpen(false); setProjectIdx(0); }} />}
      </div>
    );
  }

  if (view === '3d') {
    return (
      <div style={{ position: 'relative', height: 'calc(100vh - 52px)', background: '#0e1010', overflow: 'hidden' }}>
        <Solar3DStudio project={project} onBack={() => setView('2d')} />
      </div>
    );
  }

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '264px 1fr 340px', height: 'calc(100vh - 52px)', background: 'var(--paper)', overflow: 'hidden' }}>
      <SolarProjectList projects={projects} activeIdx={projectIdx} onSelect={setProjectIdx} onNew={() => setNewProjectOpen(true)} />
      {newProjectOpen && <NewSolarProjectModal onClose={() => setNewProjectOpen(false)} onCreated={(p) => { setNewProjectOpen(false); setProjectIdx(0); }} />}

      <div style={{ display: 'flex', flexDirection: 'column', minWidth: 0, borderLeft: '1px solid var(--line)', borderRight: '1px solid var(--line)' }}>
        <SolarHeader
          project={project}
          onOpen3D={() => setView('3d')}
          calpinage={calpinage}
          saveState={saveState}
          onEdit={() => setEditProjectOpen(true)}
          onDuplicate={handleDuplicate}
          onDelete={handleDelete}
          onSaveCalpinage={async () => {
            if (!calpinage) return;
            setSaveState({ loading: true, message: null });
            const API = (window.AE_API && window.AE_API.BASE) || '';
            try {
              let visitId = project._visitId;
              if (!visitId) {
                const created = await fetch(`${API}/api/visite/`, {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({
                    clientAddress: project.address,
                    lat: project.lat, lon: project.lng,
                    type: 'mixed', technicianName: 'Studio solaire', status: 'draft',
                  }),
                }).then(r => r.json()).catch(() => null);
                visitId = created?.visit?.id || created?.id;
                project._visitId = visitId;
              }
              if (!visitId) throw new Error('Impossible de créer/associer une visite');
              const body = {
                calpinage: {
                  panels: calpinage.panels,
                  exclusions: calpinage.exclusions,
                  roof: calpinage.roof,
                  totalKwc: project.kwc,
                  estimatedProduction: project.production,
                  savedAt: new Date().toISOString(),
                },
              };
              const res = await fetch(`${API}/api/visite/${visitId}`, {
                method: 'PATCH',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(body),
              }).then(r => r.json()).catch(() => null);
              const ok = res?.ok !== false && !res?.error;

              // Persiste également le projet dans /api/solar/projects (CRUD natif)
              let projectId = project._projectId || null;
              try {
                const projBody = {
                  name: project.site || project.ref || 'Projet solaire',
                  status: project.status || 'etude',
                  contactId: project._contact?.contactId || null,
                  organizationId: project._contact?.organizationId || null,
                  opportunityId: project._opportunityId || null,
                  clientName: project._contact ? `${project._contact.prenom || ''} ${project._contact.nom || ''}`.trim() || project._contact.societe : null,
                  address: project.address || null, city: project.city || null, zip: project.zip || null,
                  lat: project.lat || null, lng: project.lng || null,
                  panneauModele: inputs.panneauKey, panneauWc: inputs.wc,
                  nbPanneaux: inputs.nb, kWc: project.kwc || (inputs.nb * inputs.wc / 1000),
                  inclinaison: Number(inputs.inclin) || 30,
                  batterieMarque: inputs.battMarque, batterieKwh: inputs.battKwh,
                  myLight150: !!inputs.myLight,
                  consoAnnuelle: inputs.conso, tarifAchat: inputs.tachat, tarifRevente: inputs.trevente,
                  productionAn: results?.productionAn || project.production,
                  autoconsoTaux: results?.autoconsoTaux, autosuffTaux: results?.autosuffTaux,
                  economieAn: results?.economieAn, roiAn: results?.roiAn,
                  projectData: { calpinage: body.calpinage, inputs, results },
                };
                const url = projectId ? `${API}/api/solar/projects/${projectId}` : `${API}/api/solar/projects`;
                const method = projectId ? 'PATCH' : 'POST';
                const r = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projBody) }).then(x => x.json());
                if (r?.ok) projectId = r.project?.id || projectId;
              } catch (_) { /* persist non bloquant */ }

              setSaveState({ loading: false, message: ok ? 'sauvé' : 'erreur' });
              if (ok) {
                // Persiste le calpinage dans le projet local + window pour clear le "dirty"
                const next = projects.map((p, i) => i === projectIdx
                  ? { ...p, _calpinageSaved: body.calpinage, _visitId: visitId, _projectId: projectId, _calpinageSavedAt: body.calpinage.savedAt }
                  : p);
                setProjects(next);
                window.SOLAR_PROJECTS = next;
                window.dispatchEvent(new CustomEvent('ae:solar-updated'));
                if (window.AE_API?.hydrate) window.AE_API.hydrate();
              }
              setTimeout(() => setSaveState({ loading: false, message: null }), 3000);
            } catch (e) {
              setSaveState({ loading: false, message: 'erreur' });
              console.error('[solar save]', e);
            }
          }}
        />
        <SolarToolbar tool={tool} setTool={setTool} mapLayer={mapLayer} setMapLayer={setMapLayer} showShading={showShading} setShowShading={setShowShading} showIrradiance={showIrradiance} setShowIrradiance={setShowIrradiance} onOpen3D={() => setView('3d')} />
        <div style={{ flex: 1, position: 'relative', overflow: 'hidden', minHeight: 0 }}>
          <SatelliteCanvas project={project} tool={tool} mapLayer={mapLayer} showShading={showShading} showIrradiance={showIrradiance} onCalpinageChange={setCalpinage} />
          <MiniCompass orientation={project.orientation} />
          <MapAttribution />
          <AltitudeBadge project={project} />
          <LiveSunPath />
          <ClaudeHint />
        </div>
        <ProductionDock project={project} results={results} />
      </div>

      <SolarInspector project={project} scenarioId={scenarioId} setScenarioId={setScenarioId} inputs={inputs} setInput={setInput} results={results} />

      {editProjectOpen && (
        <SolarEditModal
          project={project}
          onClose={() => setEditProjectOpen(false)}
          onUpdated={(patch) => {
            const next = projects.map((p, i) => i === projectIdx ? { ...p, ...patch } : p);
            setProjects(next); window.SOLAR_PROJECTS = next;
            setEditProjectOpen(false);
          }}
        />
      )}
    </div>
  );
}

// ─── Modal édition projet solaire (P2 — Règle n°5 CRUD) ─────────────────────
function SolarEditModal({ project, onClose, onUpdated }) {
  const API = (window.AE_API && window.AE_API.BASE) || '';
  const [draft, setDraft] = useState({ site: project.site || '', notes: project.notes || '' });
  const [saving, setSaving] = useState(false);
  const [msg, setMsg] = useState(null);

  const save = async () => {
    setSaving(true); setMsg(null);
    if (project._visitId) {
      const r = await fetch(`${API}/api/visite/visits/${project._visitId}`, {
        method: 'PATCH', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ notes: draft.notes, summary: draft.site }),
      }).then(r => r.json()).catch(() => null);
      if (r?.status !== 'ok') { setSaving(false); setMsg(`⚠ ${r?.message || 'Erreur API'}`); return; }
    }
    setSaving(false);
    onUpdated(draft);
  };

  const inpS = { padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box', color: 'var(--ink)' };
  const lblS = { fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 };

  return (
    <div onClick={e => { if (e.target === e.currentTarget) onClose(); }}
      style={{ position: 'fixed', inset: 0, zIndex: 200, background: 'rgba(14,16,16,0.6)', display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)', padding: 16 }}>
      <div style={{ width: 'min(420px, 100%)', background: 'var(--paper)', borderRadius: 12, boxShadow: 'var(--shadow-3)', border: '1px solid var(--line-2)' }}>
        <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>✏️ Éditer le projet solaire</div>
            <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>{project.ref}</div>
          </div>
          <span style={{ flex: 1 }} />
          <button onClick={onClose} style={{ color: 'var(--ink-4)', fontSize: 16 }}>✕</button>
        </div>
        <div style={{ padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 10 }}>
          {msg && <div style={{ padding: '6px 10px', fontSize: 11, background: msg.startsWith('✓') ? 'var(--signal-tint)' : 'var(--rouge-tint)', border: '1px solid ' + (msg.startsWith('✓') ? 'var(--signal-soft)' : 'var(--rouge)'), borderRadius: 5, color: msg.startsWith('✓') ? 'var(--signal-deep)' : 'var(--rouge)' }}>{msg}</div>}
          <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
            <span style={lblS}>Nom du site</span>
            <input value={draft.site} onChange={e => setDraft(d => ({ ...d, site: e.target.value }))} style={inpS} />
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
            <span style={lblS}>Notes internes</span>
            <textarea value={draft.notes} onChange={e => setDraft(d => ({ ...d, notes: e.target.value }))} rows={3} style={{ ...inpS, resize: 'vertical' }} />
          </label>
          {!project._visitId && <div style={{ fontSize: 10, color: 'var(--ink-4)', padding: '5px 8px', background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 4 }}>ℹ️ Projet local — nom mis à jour dans la session.</div>}
          <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end', marginTop: 4 }}>
            <button onClick={onClose} disabled={saving} style={{ padding: '7px 12px', fontSize: 11, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 5 }}>Annuler</button>
            <button onClick={save} disabled={saving} style={{ padding: '7px 14px', fontSize: 11, fontWeight: 600, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 5 }}>
              {saving ? '…' : '✓ Enregistrer'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Project list ────────────────────────────────────────────
function SolarProjectList({ projects, activeIdx, onSelect, onNew }) {
  const [q, setQ] = useState('');
  const filtered = projects.filter(p => !q || p.site.toLowerCase().includes(q.toLowerCase()) || p.ref.toLowerCase().includes(q.toLowerCase()));
  const totalKwc = projects.reduce((s, p) => s + (p.kwc || 0), 0);
  const totalProd = projects.reduce((s, p) => s + (p.production || 0), 0);

  return (
    <aside style={{ background: 'var(--paper)', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
      <div style={{ padding: '14px 14px 10px', borderBottom: '1px solid var(--hairline)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
          <div style={{ fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, color: 'var(--ink-4)', fontWeight: 600 }}>Portefeuille solaire</div>
          <div style={{ flex: 1 }} />
          <button onClick={onNew} title="Nouveau projet depuis adresse" style={{ padding: 3, color: 'var(--signal-deep)' }}><Icon.plus /></button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 10 }}>
          <MiniStat label="Pipeline" value={`${Math.round(totalKwc)} kWc`} />
          <MiniStat label="Prod. estimée" value={`${(totalProd / 1000000).toFixed(2)} GWh`} tone="signal" />
        </div>
        <div style={{ position: 'relative' }}>
          <input
            value={q} onChange={e => setQ(e.target.value)}
            placeholder="Chercher site…"
            style={{
              width: '100%', padding: '6px 10px 6px 28px', fontSize: 12,
              background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 5,
              color: 'var(--ink)', fontFamily: 'var(--font-sans)',
            }}
          />
          <span style={{ position: 'absolute', left: 8, top: 6, color: 'var(--ink-4)' }}><Icon.search /></span>
        </div>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: '6px 8px 20px' }}>
        {filtered.length === 0 && window.EmptyState && (
          <window.EmptyState
            compact
            icon="☀️"
            title={q ? 'Aucun projet trouvé' : 'Aucun projet solaire'}
            sub={q ? `Aucun projet pour "${q}"` : 'Créez votre premier projet en saisissant une adresse — le calpinage, PVGIS et le rapport seront générés automatiquement.'}
            cta={!q ? { label: '+ Nouveau projet', onClick: onNew } : null}
          />
        )}
        {filtered.map((p, i) => {
          const realIdx = projects.indexOf(p);
          const active = realIdx === activeIdx;
          return (
            <button
              key={p.ref}
              onClick={() => onSelect(realIdx)}
              style={{
                width: '100%', padding: '10px 10px',
                borderRadius: 6, marginBottom: 2,
                background: active ? 'var(--paper-2)' : 'transparent',
                borderLeft: active ? '2px solid var(--signal)' : '2px solid transparent',
                paddingLeft: 8, textAlign: 'left',
                display: 'flex', flexDirection: 'column', gap: 4,
                transition: 'background 120ms',
              }}
              onMouseEnter={e => !active && (e.currentTarget.style.background = 'var(--paper-2)')}
              onMouseLeave={e => !active && (e.currentTarget.style.background = 'transparent')}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{p.ref}</span>
                <span style={{ flex: 1 }} />
                <StatusChip status={p.status} />
              </div>
              <div style={{ fontSize: 13, fontWeight: 500, letterSpacing: '-0.01em', color: active ? 'var(--ink)' : 'var(--ink-2)' }}>
                {p.site}
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 11, color: 'var(--ink-4)' }}>
                <span className="mono">{p.kwc} kWc</span>
                <span>·</span>
                <span className="mono">{Math.round(p.production / 1000)} MWh/an</span>
              </div>
              <div style={{ fontSize: 10, color: 'var(--ink-5)' }}>{p.updated}</div>
            </button>
          );
        })}
      </div>
    </aside>
  );
}

function MiniStat({ label, value, tone }) {
  return (
    <div style={{ padding: '6px 8px', background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 5 }}>
      <div style={{ fontSize: 9, textTransform: 'uppercase', letterSpacing: 0.6, color: 'var(--ink-4)', fontWeight: 600 }}>{label}</div>
      <div className="mono" style={{ fontSize: 13, fontWeight: 600, color: tone === 'signal' ? 'var(--signal-deep)' : 'var(--ink)', marginTop: 1 }}>{value}</div>
    </div>
  );
}

function StatusChip({ status }) {
  const map = {
    draft:      { label: 'draft',      color: 'var(--ink-4)',      bg: 'var(--paper-3)' },
    simulation: { label: 'simulation', color: 'var(--plasma)',     bg: 'var(--plasma-tint)' },
    'étude':    { label: 'étude',      color: 'var(--amber)',      bg: 'rgba(232,179,65,0.15)' },
    signée:     { label: 'signée',     color: 'var(--signal-deep)', bg: 'var(--signal-tint)' },
  };
  const s = map[status] || map.draft;
  return (
    <span className="mono" style={{
      fontSize: 9, padding: '1px 5px', borderRadius: 3,
      background: s.bg, color: s.color, fontWeight: 600,
      textTransform: 'uppercase', letterSpacing: 0.4,
    }}>{s.label}</span>
  );
}

// ─── Header ──────────────────────────────────────────────────
function SolarHeader({ project, onOpen3D, onSaveCalpinage, saveState, calpinage, onEdit, onDuplicate, onDelete }) {
  const dirty = !!calpinage;
  return (
    <header style={{
      display: 'flex', alignItems: 'center', gap: 14,
      padding: '12px 20px', borderBottom: '1px solid var(--line)',
      background: 'var(--paper)', minHeight: 54,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
        <Sigil seed={project.ref} size={28} />
        <div style={{ minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <RefChip r={project.ref} />
            <span style={{ fontSize: 13, fontWeight: 600, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{project.site}</span>
          </div>
          <div style={{ fontSize: 11, color: 'var(--ink-4)', display: 'flex', alignItems: 'center', gap: 8, marginTop: 2 }}>
            <span>{project.address}</span>
            <span style={{ color: 'var(--ink-5)' }}>·</span>
            <span className="mono">{project.lat.toFixed(4)}°N, {project.lng.toFixed(4)}°E</span>
            <span style={{ color: 'var(--ink-5)' }}>·</span>
            <span className="mono">zone {project.zone}</span>
          </div>
        </div>
      </div>

      <div style={{ flex: 1 }} />

      {/* Sync + linked visit */}
      {project.techVisit && (
        <div style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '4px 8px', background: 'var(--paper-2)',
          border: '1px solid var(--line)', borderRadius: 5,
        }}>
          <Dot tone="signal" size={6} />
          <span style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, fontWeight: 600 }}>Visite liée</span>
          <RefChip r={project.techVisit} />
        </div>
      )}

      <div style={{
        display: 'flex', alignItems: 'center', gap: 6,
        padding: '4px 10px', background: 'var(--paper-2)',
        border: '1px solid var(--line)', borderRadius: 5,
      }}>
        <span style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, fontWeight: 600 }}>Source</span>
        <span className="mono" style={{ fontSize: 10, color: 'var(--ink-2)' }}>IGN · PVGIS v5.3</span>
      </div>

      {onSaveCalpinage && (
        <button
          onClick={onSaveCalpinage}
          disabled={saveState?.loading}
          title={dirty ? 'Sauvegarder roof + panneaux + exclusions' : 'Aucun changement'}
          style={{
            padding: '6px 12px', fontSize: 12, fontWeight: 600,
            background: saveState?.loading ? 'var(--paper-3)' : dirty ? 'var(--signal)' : 'var(--paper-2)',
            color: saveState?.loading ? 'var(--ink-4)' : dirty ? 'var(--ink)' : 'var(--ink-3)',
            border: '1px solid ' + (dirty ? 'var(--signal-deep)' : 'var(--line)'),
            borderRadius: 5, cursor: saveState?.loading ? 'wait' : 'pointer',
          }}
        >
          {saveState?.loading ? 'Enregistrement…' : saveState?.message ? '✓ ' + saveState.message : 'Enregistrer calpinage'}
        </button>
      )}

      {/* Actions projet P2 */}
      {onEdit && <Btn variant="outline" size="sm" onClick={onEdit}>✏️ Éditer</Btn>}
      {onDuplicate && <Btn variant="outline" size="sm" onClick={onDuplicate}>⧉ Dupliquer</Btn>}
      {onDelete && <Btn variant="outline" size="sm" onClick={onDelete} style={{ color: 'var(--rouge)' }}>🗑</Btn>}

      <Btn variant="outline" size="sm" icon={<Icon.download />}>Exporter</Btn>
      <Btn variant="solid" size="sm" icon={<Icon.bolt />}>Générer dossier CEE</Btn>
      <SolarSettingsButton />
    </header>
  );
}

// ─── SolarSettingsButton — Personnalisation Solar (Règle n°6) ───────────────
function SolarSettingsButton() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)} title="Personnalisation Studio solaire" style={{
        padding: '6px 8px', fontSize: 12, background: 'var(--paper)',
        border: '1px solid var(--line-2)', borderRadius: 5, cursor: 'pointer', color: 'var(--ink-3)',
      }}>⚙️</button>
      {open && <SolarSettingsModal onClose={() => setOpen(false)} />}
    </>
  );
}

function SolarSettingsModal({ onClose }) {
  const KEY = 'ae_solar_settings';
  const defaults = () => ({
    tarifAchatEdf: 0.2516,        // €/kWh achat élec EDF
    tarifReventeBase: 0.13,       // €/kWh revente surplus
    fostDefault: 'BAR-EN-101',
    panneauPreferred: 'longi',    // marque préférée par défaut
    onduleurPreferred: 'huawei',
    autoConsommationCible: 70,    // %
    margeCommerciale: 25,         // %
    inclinaisonDefaut: 30,        // ° toiture
    azimutDefaut: 180,            // sud
    pertesSysteme: 14,            // % (câblage, onduleur, soiling)
    showProductionMonthly: true,
    showShadingMap: true,
    pdfTemplate: 'audits-energies-classique',
  });
  const [s, setS] = useState(() => {
    try { return { ...defaults(), ...(JSON.parse(localStorage.getItem(KEY)) || {}) }; }
    catch { return defaults(); }
  });
  const [saved, setSaved] = useState(false);
  const upd = (k, v) => setS(p => ({ ...p, [k]: v }));
  const save = () => { localStorage.setItem(KEY, JSON.stringify(s)); setSaved(true); setTimeout(() => setSaved(false), 2000); };
  const reset = () => { if (confirm('Réinitialiser tous les paramètres ?')) setS(defaults()); };
  const inp = { width: '100%', padding: '6px 10px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', color: 'var(--ink)', boxSizing: 'border-box' };
  const lbl = { fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.04em', fontWeight: 600, marginBottom: 4 };

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div onClick={e => e.stopPropagation()} style={{ background: 'var(--paper)', borderRadius: 10, maxWidth: 700, width: '100%', maxHeight: '90vh', overflowY: 'auto', boxShadow: '0 8px 40px rgba(0,0,0,.3)' }}>
        <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ fontSize: 16, fontWeight: 700, flex: 1 }}>⚙️ Personnalisation Studio solaire</div>
          <button onClick={onClose} style={{ padding: 4, fontSize: 16 }}>×</button>
        </div>
        <div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div>
            <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Tarifs énergie</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <div><div style={lbl}>Tarif achat EDF (€/kWh)</div><input type="number" step="0.0001" value={s.tarifAchatEdf} onChange={e => upd('tarifAchatEdf', parseFloat(e.target.value) || 0)} style={inp}/></div>
              <div><div style={lbl}>Tarif revente surplus (€/kWh)</div><input type="number" step="0.001" value={s.tarifReventeBase} onChange={e => upd('tarifReventeBase', parseFloat(e.target.value) || 0)} style={inp}/></div>
              <div><div style={lbl}>Marge commerciale (%)</div><input type="number" value={s.margeCommerciale} onChange={e => upd('margeCommerciale', parseFloat(e.target.value) || 0)} style={inp}/></div>
              <div><div style={lbl}>Autoconso. cible (%)</div><input type="number" value={s.autoConsommationCible} onChange={e => upd('autoConsommationCible', parseFloat(e.target.value) || 0)} style={inp}/></div>
            </div>
          </div>
          <div>
            <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Catalogue préféré</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <div><div style={lbl}>Marque panneau préférée</div>
                <select value={s.panneauPreferred} onChange={e => upd('panneauPreferred', e.target.value)} style={inp}>
                  <option value="longi">LONGi Solar</option><option value="ja">JA Solar</option><option value="trina">Trina Solar</option><option value="canadian">Canadian Solar</option><option value="dualsun">DualSun</option>
                </select>
              </div>
              <div><div style={lbl}>Marque onduleur préférée</div>
                <select value={s.onduleurPreferred} onChange={e => upd('onduleurPreferred', e.target.value)} style={inp}>
                  <option value="huawei">Huawei</option><option value="solaredge">SolarEdge</option><option value="enphase">Enphase</option><option value="fronius">Fronius</option><option value="sma">SMA</option>
                </select>
              </div>
              <div><div style={lbl}>FOST par défaut</div>
                <select value={s.fostDefault} onChange={e => upd('fostDefault', e.target.value)} style={inp}>
                  <option value="BAR-EN-101">BAR-EN-101 (combles)</option><option value="BAR-TH-104">BAR-TH-104 (PAC air/eau)</option><option value="BAR-TH-129">BAR-TH-129 (PAC air/air)</option>
                </select>
              </div>
              <div><div style={lbl}>Template PDF</div>
                <select value={s.pdfTemplate} onChange={e => upd('pdfTemplate', e.target.value)} style={inp}>
                  <option value="audits-energies-classique">Audits Énergies — classique</option><option value="audits-energies-pro">Audits Énergies — pro (couleurs)</option>
                </select>
              </div>
            </div>
          </div>
          <div>
            <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Paramètres techniques par défaut</div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10 }}>
              <div><div style={lbl}>Inclinaison (°)</div><input type="number" value={s.inclinaisonDefaut} onChange={e => upd('inclinaisonDefaut', parseFloat(e.target.value) || 0)} style={inp}/></div>
              <div><div style={lbl}>Azimut (° / 180=sud)</div><input type="number" value={s.azimutDefaut} onChange={e => upd('azimutDefaut', parseFloat(e.target.value) || 180)} style={inp}/></div>
              <div><div style={lbl}>Pertes système (%)</div><input type="number" value={s.pertesSysteme} onChange={e => upd('pertesSysteme', parseFloat(e.target.value) || 14)} style={inp}/></div>
            </div>
          </div>
          <div>
            <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Affichage</div>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, marginBottom: 6 }}>
              <input type="checkbox" checked={s.showProductionMonthly} onChange={e => upd('showProductionMonthly', e.target.checked)} />
              Afficher production mensuelle détaillée
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }}>
              <input type="checkbox" checked={s.showShadingMap} onChange={e => upd('showShadingMap', e.target.checked)} />
              Afficher carte ombrage par défaut
            </label>
          </div>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', paddingTop: 12, borderTop: '1px solid var(--hairline)' }}>
            <button onClick={reset} style={{ padding: '8px 14px', fontSize: 12, color: 'var(--ink-3)', background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5, cursor: 'pointer' }}>↺ Réinitialiser</button>
            <button onClick={save} style={{ padding: '8px 18px', fontSize: 12, fontWeight: 600, color: 'var(--paper)', background: saved ? 'var(--signal-deep)' : 'var(--ink)', border: 'none', borderRadius: 5, cursor: 'pointer' }}>
              {saved ? '✓ Enregistré' : 'Enregistrer'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Toolbar ─────────────────────────────────────────────────
function SolarToolbar({ tool, setTool, mapLayer, setMapLayer, showShading, setShowShading, showIrradiance, setShowIrradiance, onOpen3D }) {
  const tools = [
    { id: 'select', label: 'Sélection', icon: '⊹' },
    { id: 'trace',  label: 'Tracer toiture', icon: '▱' },
    { id: 'panels', label: 'Poser panneaux', icon: '⊞' },
    { id: 'exclude',label: 'Zone exclue', icon: '⊘' },
    { id: 'measure',label: 'Mesurer', icon: '⟼' },
  ];
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8,
      padding: '8px 16px', borderBottom: '1px solid var(--line)',
      background: 'var(--paper)',
    }}>
      {/* Tool group */}
      <div style={{ display: 'flex', gap: 1, padding: 2, background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 5 }}>
        {tools.map(t => (
          <button
            key={t.id}
            onClick={() => setTool(t.id)}
            title={t.label}
            style={{
              padding: '4px 10px', fontSize: 12, borderRadius: 3,
              background: tool === t.id ? 'var(--paper)' : 'transparent',
              color: tool === t.id ? 'var(--ink)' : 'var(--ink-3)',
              fontWeight: tool === t.id ? 600 : 500,
              border: tool === t.id ? '1px solid var(--line)' : '1px solid transparent',
              display: 'flex', alignItems: 'center', gap: 6,
            }}
          >
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 14 }}>{t.icon}</span>
            <span>{t.label}</span>
          </button>
        ))}
      </div>

      <div style={{ width: 1, height: 20, background: 'var(--line)', margin: '0 4px' }} />

      {/* Layers */}
      <div style={{ display: 'flex', gap: 1, padding: 2, background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 5 }}>
        {['satellite', 'hybrid', 'cadastre'].map(l => (
          <button
            key={l} onClick={() => setMapLayer(l)}
            style={{
              padding: '4px 10px', fontSize: 11, borderRadius: 3,
              background: mapLayer === l ? 'var(--paper)' : 'transparent',
              color: mapLayer === l ? 'var(--ink)' : 'var(--ink-4)',
              border: mapLayer === l ? '1px solid var(--line)' : '1px solid transparent',
              textTransform: 'capitalize', fontFamily: 'var(--font-mono)',
            }}
          >{l}</button>
        ))}
      </div>

      <div style={{ width: 1, height: 20, background: 'var(--line)', margin: '0 4px' }} />

      {/* Toggles */}
      <ToggleChip on={showShading} onClick={() => setShowShading(v => !v)} label="Ombrage" dot="var(--ink-3)" />
      <ToggleChip on={showIrradiance} onClick={() => setShowIrradiance(v => !v)} label="Irradiance" dot="var(--amber)" />

      <div style={{ flex: 1 }} />

      <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>
        <Dot tone="signal" size={6} pulse /> <span style={{ marginLeft: 4 }}>PVGIS synchro · 12:47:03</span>
      </span>

      <button onClick={onOpen3D} style={{
        padding: '4px 12px', fontSize: 11, fontWeight: 600,
        background: 'var(--ink)', color: 'var(--signal)',
        border: '1px solid var(--ink)', borderRadius: 5,
        display: 'flex', alignItems: 'center', gap: 6,
        letterSpacing: 0.3,
      }}>
        <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M8 1 1 5l7 4 7-4zM1 11l7 4 7-4M1 8l7 4 7-4"/></svg>
        Vue 3D · calpinage
      </button>
    </div>
  );
}

function ToggleChip({ on, onClick, label, dot }) {
  return (
    <button onClick={onClick} style={{
      display: 'flex', alignItems: 'center', gap: 6,
      padding: '4px 10px', fontSize: 11,
      background: on ? 'var(--paper-2)' : 'transparent',
      border: '1px solid var(--line)', borderRadius: 4,
      color: on ? 'var(--ink-2)' : 'var(--ink-4)',
    }}>
      <span style={{
        width: 8, height: 8, borderRadius: 2,
        background: on ? dot : 'transparent',
        border: `1px solid ${on ? dot : 'var(--line-2)'}`,
      }} />
      <span>{label}</span>
    </button>
  );
}

// ─── Satellite canvas ────────────────────────────────────────
// SCALE: 3 pixels = 1 metre (used by measure tool + surface calc)
const PX_PER_M = 3;

function polygonArea(verts) {
  // Shoelace formula (pixels²)
  let s = 0;
  for (let i = 0; i < verts.length; i++) {
    const a = verts[i], b = verts[(i + 1) % verts.length];
    s += a.x * b.y - b.x * a.y;
  }
  return Math.abs(s) / 2;
}

function SatelliteCanvas({ project, tool, mapLayer, showShading, showIrradiance, onCalpinageChange }) {
  // Interactive canvas with tools: select | trace | panels | exclude | measure
  const [hover, setHover] = useState(null);
  const [view, setView] = useState({ x: 0, y: 0, scale: 1 }); // pan + zoom
  const [panDrag, setPanDrag] = useState(null); // { startX, startY, startViewX, startViewY }

  // Seeded site baseline
  const seeded = useMemo(() => genSite(project), [project.ref]);

  // Editable state — ré-initialisé quand on change de projet
  const [roof, setRoof] = useState(seeded.roof);
  const [panels, setPanels] = useState(seeded.panels);
  const [exclusions, setExclusions] = useState(seeded.exclusions);
  useEffect(() => {
    setRoof(seeded.roof);
    setPanels(seeded.panels);
    setExclusions(seeded.exclusions);
  }, [project.ref]);

  // Push calpinage changes to parent (for future save backend)
  useEffect(() => {
    if (onCalpinageChange) {
      onCalpinageChange({ roof, panels, exclusions });
    }
  }, [roof, panels, exclusions]);

  const { buildings, trees } = seeded;

  // Tool states
  const [traceInProgress, setTraceInProgress] = useState([]); // [{x,y}]
  const [measureLine, setMeasureLine] = useState(null); // { a:{x,y}, b:{x,y} }
  const [measurePending, setMeasurePending] = useState(null); // {x,y} first click
  const [excludeDrag, setExcludeDrag] = useState(null); // { startX, startY, x, y } current

  const svgRef = useRef(null);
  // Coord écran → coord monde (inversion du pan/zoom pour que les clics tombent au bon endroit)
  const getCoord = (e) => {
    const svg = svgRef.current;
    if (!svg) return { x: 0, y: 0 };
    const rect = svg.getBoundingClientRect();
    const sx = e.clientX - rect.left;
    const sy = e.clientY - rect.top;
    return { x: (sx - view.x) / view.scale, y: (sy - view.y) / view.scale };
  };
  // Coord brute écran (pour pan)
  const getScreenCoord = (e) => {
    const svg = svgRef.current;
    if (!svg) return { x: 0, y: 0 };
    const rect = svg.getBoundingClientRect();
    return { x: e.clientX - rect.left, y: e.clientY - rect.top };
  };

  const onSvgClick = (e) => {
    const p = getCoord(e);
    if (tool === 'trace') {
      setTraceInProgress((pts) => [...pts, p]);
    } else if (tool === 'panels') {
      // Place a new panel at cursor
      const pw = 24, ph = 14;
      setPanels((list) => [...list, { x: p.x - pw / 2, y: p.y - ph / 2, w: pw, h: ph, shaded: false }]);
    } else if (tool === 'measure') {
      if (!measurePending) {
        setMeasurePending(p);
        setMeasureLine(null);
      } else {
        setMeasureLine({ a: measurePending, b: p });
        setMeasurePending(null);
      }
    }
  };

  const onSvgDoubleClick = () => {
    if (tool === 'trace' && traceInProgress.length >= 3) {
      const vertices = traceInProgress.slice();
      const points = vertices.map(v => `${Math.round(v.x)},${Math.round(v.y)}`).join(' ');
      const shadow = vertices.map(v => `${Math.round(v.x - 22)},${Math.round(v.y + 18)}`).join(' ');
      setRoof({ vertices, points, shadow });
      setTraceInProgress([]);
    }
  };

  const onSvgMouseDown = (e) => {
    if (tool === 'exclude') {
      const p = getCoord(e);
      setExcludeDrag({ startX: p.x, startY: p.y, x: p.x, y: p.y });
    } else if (tool === 'select') {
      // Pan de la vue
      const s = getScreenCoord(e);
      setPanDrag({ startX: s.x, startY: s.y, startViewX: view.x, startViewY: view.y });
    }
  };
  const onSvgMouseMove = (e) => {
    if (tool === 'exclude' && excludeDrag) {
      const p = getCoord(e);
      setExcludeDrag({ ...excludeDrag, x: p.x, y: p.y });
    }
    if (panDrag) {
      const s = getScreenCoord(e);
      setView((v) => ({ ...v, x: panDrag.startViewX + (s.x - panDrag.startX), y: panDrag.startViewY + (s.y - panDrag.startY) }));
    }
  };
  const onSvgMouseUp = () => {
    if (tool === 'exclude' && excludeDrag) {
      const x = Math.min(excludeDrag.startX, excludeDrag.x);
      const y = Math.min(excludeDrag.startY, excludeDrag.y);
      const w = Math.abs(excludeDrag.x - excludeDrag.startX);
      const h = Math.abs(excludeDrag.y - excludeDrag.startY);
      if (w > 5 && h > 5) {
        setExclusions((list) => [...list, { x, y, w, h, label: 'zone ' + (list.length + 1) }]);
      }
      setExcludeDrag(null);
    }
    if (panDrag) setPanDrag(null);
  };
  const onSvgWheel = (e) => {
    e.preventDefault();
    const s = getScreenCoord(e);
    // Zoom autour du curseur : maintient le point sous le curseur fixe
    const delta = e.deltaY < 0 ? 1.12 : 1 / 1.12;
    setView((v) => {
      const newScale = Math.max(0.3, Math.min(4, v.scale * delta));
      const actualDelta = newScale / v.scale;
      return {
        scale: newScale,
        x: s.x - (s.x - v.x) * actualDelta,
        y: s.y - (s.y - v.y) * actualDelta,
      };
    });
  };
  const zoomBy = (factor) => {
    setView((v) => {
      const svg = svgRef.current;
      const rect = svg ? svg.getBoundingClientRect() : { width: 800, height: 600 };
      const cx = rect.width / 2, cy = rect.height / 2;
      const newScale = Math.max(0.3, Math.min(4, v.scale * factor));
      const actualDelta = newScale / v.scale;
      return {
        scale: newScale,
        x: cx - (cx - v.x) * actualDelta,
        y: cy - (cy - v.y) * actualDelta,
      };
    });
  };
  const resetView = () => setView({ x: 0, y: 0, scale: 1 });

  // Keyboard: Enter = close polygon, Escape = cancel trace
  useEffect(() => {
    const onKey = (e) => {
      if (tool === 'trace' && traceInProgress.length >= 3 && e.key === 'Enter') {
        onSvgDoubleClick();
      }
      if (e.key === 'Escape') {
        setTraceInProgress([]);
        setExcludeDrag(null);
        setMeasurePending(null);
        setMeasureLine(null);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [tool, traceInProgress]);

  const cursorByTool = {
    select: panDrag ? 'grabbing' : 'grab', trace: 'crosshair', panels: 'copy', exclude: 'crosshair', measure: 'crosshair',
  };

  // Measure distance text
  const measureDistM = measureLine
    ? (Math.hypot(measureLine.b.x - measureLine.a.x, measureLine.b.y - measureLine.a.y) / PX_PER_M).toFixed(1)
    : null;

  return (
    <div
      style={{
        position: 'absolute', inset: 0,
        background: mapLayer === 'cadastre'
          ? 'var(--paper-2)'
          : 'radial-gradient(circle at 30% 20%, #3a4a3a 0%, #2a3829 40%, #1e2a1e 100%)',
        cursor: cursorByTool[tool] || 'default',
        overflow: 'hidden',
      }}
    >
      {/* Terrain layer */}
      <TerrainLayer layer={mapLayer} />

      {/* Cadastre grid for 'cadastre' & 'hybrid' */}
      {(mapLayer === 'cadastre' || mapLayer === 'hybrid') && <CadastreLayer />}

      {/* Other buildings (context) */}
      <svg
        ref={svgRef}
        style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}
        onClick={onSvgClick}
        onDoubleClick={onSvgDoubleClick}
        onMouseDown={onSvgMouseDown}
        onMouseMove={onSvgMouseMove}
        onMouseUp={onSvgMouseUp}
        onMouseLeave={onSvgMouseUp}
        onWheel={onSvgWheel}
      >
      <g transform={`translate(${view.x}, ${view.y}) scale(${view.scale})`}>
        {buildings.map((b, i) => (
          <polygon
            key={i} points={b.points}
            fill={mapLayer === 'cadastre' ? 'var(--paper)' : 'rgba(90,85,75,0.75)'}
            stroke={mapLayer === 'cadastre' ? 'var(--line-2)' : 'rgba(40,40,35,0.6)'}
            strokeWidth="1"
          />
        ))}
        {mapLayer !== 'cadastre' && trees.map((t, i) => (
          <circle key={i} cx={t.x} cy={t.y} r={t.r} fill={`rgba(50,${60 + (i%3)*10},38,0.85)`} />
        ))}

        {/* Irradiance heatmap on main roof */}
        {showIrradiance && (
          <g>
            <defs>
              <radialGradient id="irr" cx="30%" cy="20%" r="80%">
                <stop offset="0%" stopColor="rgba(255,220,90,0.55)" />
                <stop offset="60%" stopColor="rgba(255,160,60,0.28)" />
                <stop offset="100%" stopColor="rgba(30,40,80,0.15)" />
              </radialGradient>
            </defs>
            <polygon points={roof.points} fill="url(#irr)" />
          </g>
        )}

        {/* Roof polygon — the traced rooftop */}
        <polygon
          points={roof.points}
          fill={showIrradiance ? 'rgba(0,0,0,0)' : 'rgba(80,70,55,0.4)'}
          stroke="var(--signal)"
          strokeWidth="1.5"
          strokeDasharray={tool === 'trace' ? '4 3' : 'none'}
        />
        {/* Roof vertices */}
        {roof.vertices.map((v, i) => (
          <g key={i}>
            <rect x={v.x - 3} y={v.y - 3} width="6" height="6" fill="var(--paper)" stroke="var(--signal-deep)" strokeWidth="1.2" />
            <text x={v.x + 7} y={v.y - 4} fontSize="9" fill="var(--signal)" fontFamily="var(--font-mono)">v{i + 1}</text>
          </g>
        ))}

        {/* Exclusion zones (velux, cheminées) */}
        {exclusions.map((e, i) => (
          <g key={i}>
            <rect x={e.x} y={e.y} width={e.w} height={e.h}
              fill="rgba(217,70,70,0.22)" stroke="var(--rouge)" strokeWidth="1" strokeDasharray="3 2" />
            <text x={e.x + 3} y={e.y + 11} fontSize="9" fill="var(--rouge)" fontFamily="var(--font-mono)">{e.label}</text>
          </g>
        ))}

        {/* Shadow projection */}
        {showShading && (
          <polygon
            points={roof.shadow}
            fill="rgba(20,30,50,0.32)"
            style={{ mixBlendMode: 'multiply' }}
          />
        )}

        {/* PV panels — grid */}
        <g>
          {panels.map((p, i) => (
            <rect
              key={i}
              x={p.x} y={p.y} width={p.w} height={p.h}
              fill={p.shaded ? 'rgba(50,80,120,0.8)' : 'rgba(30,55,95,0.92)'}
              stroke="rgba(160,190,220,0.35)"
              strokeWidth="0.4"
              onMouseEnter={() => setHover({ idx: i, p })}
              onMouseLeave={() => setHover(null)}
              style={{ cursor: 'pointer', transition: 'fill 120ms' }}
            />
          ))}
          {/* Panel highlight tinted overlay for cell lines */}
          {panels.map((p, i) => (
            <g key={'g' + i} pointerEvents="none">
              <line x1={p.x + p.w/3} y1={p.y} x2={p.x + p.w/3} y2={p.y + p.h} stroke="rgba(160,190,220,0.12)" strokeWidth="0.3" />
              <line x1={p.x + 2*p.w/3} y1={p.y} x2={p.x + 2*p.w/3} y2={p.y + p.h} stroke="rgba(160,190,220,0.12)" strokeWidth="0.3" />
            </g>
          ))}
        </g>

        {/* Trace in progress — ghost polyline while user adds vertices */}
        {tool === 'trace' && traceInProgress.length > 0 && (
          <g>
            <polyline
              points={traceInProgress.map(v => `${v.x},${v.y}`).join(' ')}
              fill="none" stroke="var(--signal)" strokeWidth="1.5" strokeDasharray="5 3"
            />
            {traceInProgress.map((v, i) => (
              <g key={i}>
                <circle cx={v.x} cy={v.y} r="4" fill="var(--signal)" stroke="var(--paper)" strokeWidth="1.5" />
                <text x={v.x + 8} y={v.y - 6} fontSize="9" fill="var(--signal)" fontFamily="var(--font-mono)">v{i + 1}</text>
              </g>
            ))}
            <text x={traceInProgress[0].x} y={traceInProgress[0].y + 22} fontSize="10" fill="var(--signal)" fontFamily="var(--font-mono)">
              {traceInProgress.length} pts · double-clic ou Enter pour fermer
            </text>
          </g>
        )}

        {/* Exclude drag preview */}
        {excludeDrag && (
          <rect
            x={Math.min(excludeDrag.startX, excludeDrag.x)}
            y={Math.min(excludeDrag.startY, excludeDrag.y)}
            width={Math.abs(excludeDrag.x - excludeDrag.startX)}
            height={Math.abs(excludeDrag.y - excludeDrag.startY)}
            fill="rgba(217,70,70,0.22)" stroke="var(--rouge)" strokeWidth="1.2" strokeDasharray="4 3"
          />
        )}

        {/* Measure line (interactive) */}
        {measureLine && (
          <g stroke="var(--signal)" strokeWidth="1.3" fill="none">
            <line x1={measureLine.a.x} y1={measureLine.a.y} x2={measureLine.b.x} y2={measureLine.b.y} strokeDasharray="3 2" />
            <circle cx={measureLine.a.x} cy={measureLine.a.y} r="4" fill="var(--signal)" />
            <circle cx={measureLine.b.x} cy={measureLine.b.y} r="4" fill="var(--signal)" />
            <rect
              x={(measureLine.a.x + measureLine.b.x) / 2 - 22}
              y={(measureLine.a.y + measureLine.b.y) / 2 - 18}
              width="44" height="14" rx="3"
              fill="var(--paper)" stroke="var(--signal)"
            />
            <text
              x={(measureLine.a.x + measureLine.b.x) / 2}
              y={(measureLine.a.y + measureLine.b.y) / 2 - 7}
              fontSize="10" fill="var(--signal-deep)" fontFamily="var(--font-mono)" textAnchor="middle" fontWeight="600"
            >
              {measureDistM} m
            </text>
          </g>
        )}
        {measurePending && !measureLine && (
          <circle cx={measurePending.x} cy={measurePending.y} r="4" fill="var(--signal)" stroke="var(--paper)" strokeWidth="1.5" />
        )}
      </g>
      </svg>

      {/* Hover tooltip */}
      {hover && (
        <div style={{
          position: 'absolute', left: hover.p.x + hover.p.w + 20, top: hover.p.y - 8,
          background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5,
          padding: '6px 8px', fontSize: 10, boxShadow: 'var(--shadow-2)', pointerEvents: 'none',
          fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap',
        }}>
          <div style={{ color: 'var(--ink-4)' }}>panneau #{hover.idx + 1}</div>
          <div style={{ color: 'var(--ink)' }}>400 Wc · Trina Vertex S+</div>
          <div style={{ color: hover.p.shaded ? 'var(--rouge)' : 'var(--signal-deep)' }}>
            {hover.p.shaded ? 'ombré 16%' : 'plein soleil'}
          </div>
        </div>
      )}

      {/* Zoom + reset view controls */}
      <div style={{
        position: 'absolute', left: 16, top: 16, display: 'flex', flexDirection: 'column',
        background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5,
        overflow: 'hidden', boxShadow: 'var(--shadow-1)',
      }}>
        <button onClick={() => zoomBy(1.2)} title="Zoom +" style={{ padding: '6px 8px', borderBottom: '1px solid var(--hairline)', fontSize: 14, color: 'var(--ink-2)' }}>+</button>
        <button onClick={() => zoomBy(1 / 1.2)} title="Zoom −" style={{ padding: '6px 8px', borderBottom: '1px solid var(--hairline)', fontSize: 14, color: 'var(--ink-2)' }}>−</button>
        <button onClick={resetView} title="Recentrer" style={{ padding: '6px 8px', fontSize: 10, color: 'var(--ink-3)', fontFamily: 'var(--font-mono)' }}>⌂</button>
      </div>
      {/* Zoom level indicator */}
      <div style={{
        position: 'absolute', left: 16, top: 120,
        background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5,
        padding: '3px 8px', fontSize: 10, color: 'var(--ink-3)', fontFamily: 'var(--font-mono)',
      }}>
        {Math.round(view.scale * 100)}%
      </div>

      {/* Scale bar */}
      <div style={{ position: 'absolute', left: 16, bottom: 16, display: 'flex', alignItems: 'center', gap: 6, color: 'var(--paper)', textShadow: '0 0 4px rgba(0,0,0,0.8)' }}>
        <div style={{ width: 60, height: 4, background: 'linear-gradient(90deg, #fff 50%, transparent 50%)', border: '1px solid rgba(0,0,0,0.5)' }} />
        <span className="mono" style={{ fontSize: 10 }}>10 m</span>
      </div>
    </div>
  );
}

// Terrain — dense procedural texture for satellite feel
function TerrainLayer({ layer }) {
  if (layer === 'cadastre') return null;
  return (
    <>
      {/* field patches */}
      <div style={{
        position: 'absolute', inset: 0,
        backgroundImage: `
          radial-gradient(ellipse 80% 60% at 70% 70%, rgba(90,105,70,0.55) 0%, transparent 60%),
          radial-gradient(ellipse 60% 40% at 20% 80%, rgba(110,130,85,0.4) 0%, transparent 60%),
          radial-gradient(ellipse 40% 30% at 80% 30%, rgba(130,110,80,0.35) 0%, transparent 60%),
          repeating-linear-gradient(22deg, rgba(75,90,65,0.12) 0 3px, transparent 3px 6px),
          repeating-linear-gradient(112deg, rgba(50,65,45,0.1) 0 5px, transparent 5px 10px)
        `,
      }} />
      {/* road */}
      <div style={{
        position: 'absolute', left: 0, right: 0, top: '30%',
        height: 18, background: 'rgba(130,125,115,0.6)',
        transform: 'rotate(-3deg)',
      }}>
        <div style={{ position: 'absolute', top: 8, left: 0, right: 0, height: 1, background: 'repeating-linear-gradient(90deg, rgba(255,255,230,0.6) 0 18px, transparent 18px 32px)' }} />
      </div>
      <div style={{
        position: 'absolute', left: '40%', top: 0, bottom: 0,
        width: 12, background: 'rgba(115,112,102,0.5)',
        transform: 'rotate(6deg)',
      }} />
    </>
  );
}

function CadastreLayer() {
  return (
    <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
      <defs>
        <pattern id="cadgrid" width="60" height="60" patternUnits="userSpaceOnUse">
          <path d="M60 0 H0 V60" fill="none" stroke="var(--line-2)" strokeWidth="0.4" strokeDasharray="1 2" />
        </pattern>
      </defs>
      <rect width="100%" height="100%" fill="url(#cadgrid)" />
      {/* Parcel outlines */}
      <polygon points="120,80 320,60 380,220 240,300 80,260" fill="none" stroke="var(--copper)" strokeWidth="1" strokeDasharray="4 2" />
      <text x="200" y="170" fontSize="9" fill="var(--copper)" fontFamily="var(--font-mono)">AK 0127</text>
      <polygon points="420,180 620,160 680,340 540,420 380,380" fill="none" stroke="var(--copper)" strokeWidth="1" strokeDasharray="4 2" />
      <text x="530" y="290" fontSize="9" fill="var(--copper)" fontFamily="var(--font-mono)">AK 0128 · 2 840 m²</text>
    </svg>
  );
}

// Seeded rooftop + panels + obstacles
function genSite(project) {
  const rng = seedRng(project.ref);
  // Main rooftop polygon
  const cx = 560, cy = 360;
  const vertices = [
    { x: cx - 200, y: cy - 100 },
    { x: cx + 180, y: cy - 120 },
    { x: cx + 220, y: cy + 30 },
    { x: cx + 150, y: cy + 160 },
    { x: cx - 180, y: cy + 140 },
    { x: cx - 230, y: cy + 10 },
  ];
  const points = vertices.map(v => `${v.x},${v.y}`).join(' ');
  const shadow = vertices.map(v => `${v.x - 22},${v.y + 18}`).join(' ');

  // Panel grid — rows x cols inside bounding box
  const panels = [];
  const cols = 14, rows = 8;
  const pw = 24, ph = 14;
  const startX = cx - 160, startY = cy - 70;
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const x = startX + c * (pw + 2);
      const y = startY + r * (ph + 2);
      // skip some for exclusions visually
      if ((r === 2 && c >= 5 && c <= 7) || (r === 5 && c >= 10 && c <= 12)) continue;
      // corners trimmed
      if (r === 0 && c >= 12) continue;
      if (r === rows - 1 && c === 0) continue;
      const shaded = (r > 5 && c < 3) || (rng() < 0.04);
      panels.push({ x, y, w: pw, h: ph, shaded });
    }
  }

  const exclusions = [
    { x: cx - 40, y: cy - 48, w: 74, h: 18, label: 'velux ×3' },
    { x: cx + 88, y: cy + 46, w: 56, h: 22, label: 'cheminée' },
    { x: cx - 170, y: cy + 78, w: 42, h: 30, label: 'VMC' },
    { x: cx + 120, y: cy - 90, w: 36, h: 20, label: 'antenne' },
  ];

  // Neighbor buildings
  const buildings = [
    { points: '40,120 180,100 200,200 60,220' },
    { points: '780,220 920,200 940,340 800,360' },
    { points: '160,460 300,440 320,560 180,580' },
    { points: '720,80 820,70 830,150 730,160' },
  ];

  // Trees
  const trees = [];
  for (let i = 0; i < 18; i++) {
    trees.push({ x: 40 + rng() * 1000, y: 40 + rng() * 560, r: 7 + rng() * 6 });
  }

  return { roof: { vertices, points, shadow }, panels, exclusions, buildings, trees };
}

// Mini compass
function MiniCompass({ orientation }) {
  const rot = orientation - 180;
  return (
    <div style={{
      position: 'absolute', right: 16, top: 16,
      width: 64, height: 64, borderRadius: '50%',
      background: 'rgba(14,16,16,0.82)', border: '1px solid rgba(255,255,255,0.12)',
      display: 'grid', placeItems: 'center', color: '#fff',
      boxShadow: 'var(--shadow-2)', backdropFilter: 'blur(4px)',
    }}>
      <svg width="64" height="64" viewBox="-32 -32 64 64">
        <circle r="28" fill="none" stroke="rgba(255,255,255,0.12)" />
        <circle r="22" fill="none" stroke="rgba(255,255,255,0.08)" />
        <g transform={`rotate(${rot})`}>
          <path d="M0 -22 L4 0 L0 4 L-4 0 Z" fill="var(--signal)" />
          <path d="M0 22 L4 0 L0 -4 L-4 0 Z" fill="rgba(255,255,255,0.25)" />
        </g>
        <text textAnchor="middle" y="-15" fontSize="8" fill="rgba(255,255,255,0.8)" fontFamily="var(--font-mono)">N</text>
        <text textAnchor="middle" y="24" fontSize="8" fill="rgba(255,255,255,0.5)" fontFamily="var(--font-mono)">S</text>
      </svg>
      <div style={{
        position: 'absolute', bottom: -20, left: '50%', transform: 'translateX(-50%)',
        fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--ink-3)',
        background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3,
        padding: '1px 5px', whiteSpace: 'nowrap',
      }}>
        {orientation}° · azimut sud
      </div>
    </div>
  );
}

function MapAttribution() {
  return (
    <div style={{
      position: 'absolute', right: 12, bottom: 12,
      fontSize: 9, color: 'rgba(255,255,255,0.7)',
      background: 'rgba(0,0,0,0.4)', padding: '2px 6px',
      borderRadius: 2, fontFamily: 'var(--font-mono)',
    }}>
      IGN ORTHOPHOTO · Cadastre · Bâti v2026.03
    </div>
  );
}

function AltitudeBadge({ project }) {
  return (
    <div style={{
      position: 'absolute', left: 16, top: 96,
      padding: '6px 10px', background: 'rgba(14,16,16,0.82)',
      border: '1px solid rgba(255,255,255,0.12)', borderRadius: 5,
      color: '#fff', fontFamily: 'var(--font-mono)', fontSize: 11,
      boxShadow: 'var(--shadow-2)', backdropFilter: 'blur(4px)',
      display: 'flex', flexDirection: 'column', gap: 2, minWidth: 110,
    }}>
      <div style={{ fontSize: 9, color: 'rgba(255,255,255,0.55)', textTransform: 'uppercase', letterSpacing: 0.6 }}>site</div>
      <div>alt. <span style={{ color: 'var(--signal)' }}>82 m</span></div>
      <div>pente toiture <span style={{ color: 'var(--signal)' }}>{project.tilt}°</span></div>
      <div>orient. <span style={{ color: 'var(--signal)' }}>{project.orientation}°</span></div>
      <div>masque <span style={{ color: project.shadingLoss > 8 ? 'var(--copper)' : 'var(--signal)' }}>{project.shadingLoss}%</span></div>
    </div>
  );
}

// Sun path overlay
function LiveSunPath() {
  const [t, setT] = useState(12);
  useEffect(() => {
    const id = setInterval(() => setT(x => (x + 0.25) % 24), 400);
    return () => clearInterval(id);
  }, []);
  const sunX = 80 + ((t - 6) / 12) * 880;
  const sunY = 340 - Math.sin(((t - 6) / 12) * Math.PI) * 260;
  if (t < 6 || t > 20) return null;
  return (
    <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
      <path d="M 80 340 Q 520 20 960 340" fill="none" stroke="rgba(255,220,120,0.25)" strokeWidth="1" strokeDasharray="4 4" />
      <circle cx={sunX} cy={sunY} r="8" fill="#FFD95E" filter="url(#glow)" style={{ transition: 'all 200ms' }} />
      <circle cx={sunX} cy={sunY} r="14" fill="none" stroke="rgba(255,217,94,0.4)" />
      <defs>
        <filter id="glow"><feGaussianBlur stdDeviation="3" /></filter>
      </defs>
    </svg>
  );
}

// Claude hint — contextual AI suggestion
function ClaudeHint() {
  const [dismissed, setDismissed] = useState(false);
  if (dismissed) return null;
  return (
    <div style={{
      position: 'absolute', right: 16, bottom: 16,
      maxWidth: 360, padding: '12px 14px',
      background: 'rgba(14,16,16,0.92)', border: '1px solid rgba(107,76,255,0.4)',
      borderRadius: 6, color: '#fff', boxShadow: '0 10px 30px rgba(0,0,0,0.4)',
      backdropFilter: 'blur(8px)',
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <div style={{
          width: 20, height: 20, borderRadius: 3, background: 'var(--plasma)', flexShrink: 0,
          display: 'grid', placeItems: 'center', marginTop: 1,
        }}>
          <svg width="10" height="10" viewBox="0 0 10 10" fill="#fff"><path d="M5 0 L6 4 L10 5 L6 6 L5 10 L4 6 L0 5 L4 4 Z" /></svg>
        </div>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 10, color: 'rgba(255,255,255,0.55)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 3 }}>
            Co-pilote · claude-haiku-4-5
          </div>
          <div style={{ fontSize: 12.5, lineHeight: 1.45, color: '#fff' }}>
            Masque solaire de <strong style={{ color: 'var(--amber)' }}>22 %</strong> détecté au nord-est (cheminée + arbre 14 m).
            Bascule 6 panneaux côté sud-ouest → <strong style={{ color: 'var(--signal)' }}>+840 kWh/an</strong>, ROI raccourci de 4 mois.
          </div>
          <div style={{ display: 'flex', gap: 6, marginTop: 10 }}>
            <button style={{
              padding: '4px 10px', fontSize: 11, borderRadius: 4,
              background: 'var(--signal)', color: '#0B1F12', fontWeight: 600,
            }}>Appliquer</button>
            <button onClick={() => setDismissed(true)} style={{
              padding: '4px 10px', fontSize: 11, borderRadius: 4,
              background: 'transparent', border: '1px solid rgba(255,255,255,0.2)', color: 'rgba(255,255,255,0.7)',
            }}>Ignorer</button>
          </div>
        </div>
        <button onClick={() => setDismissed(true)} style={{ color: 'rgba(255,255,255,0.4)', padding: 2, marginTop: -2 }}>
          <Icon.cross />
        </button>
      </div>
    </div>
  );
}

// ─── Production dock (bottom) — table mensuelle détaillée portée de la V ─
function ProductionDock({ project, results }) {
  const [mode, setMode] = useState('monthly'); // monthly | hourly | detail
  // Utilise results.mensuel live si dispo, sinon fallback sur MONTHLY_PRODUCTION seed
  const mensuel = (results && results.mensuel) || MONTHLY_PRODUCTION.map(m => ({ mois: m.m, prod: m.v, intensite: (m.v / 50000) * 100, autocons: Math.round(m.v * 0.7), surplus: Math.round(m.v * 0.3) }));
  const dataByMode = mode === 'hourly'
    ? HOURLY_IRRADIANCE.map((v, i) => ({ m: String(i) + 'h', v }))
    : mensuel.map(m => ({ m: m.mois, v: m.prod }));
  const max = Math.max(...dataByMode.map(d => d.v), 1);
  const total = mensuel.reduce((s, m) => s + (m.prod || 0), 0);
  const peakIdx = mensuel.reduce((best, m, i, arr) => (m.prod > arr[best].prod ? i : best), 0);
  const peak = mensuel[peakIdx];
  const totalAutocons = mensuel.reduce((s, m) => s + (m.autocons || 0), 0);
  const totalSurplus = mensuel.reduce((s, m) => s + (m.surplus || 0), 0);
  const tauxAutocons = total > 0 ? Math.round((totalAutocons / total) * 100) : 0;

  return (
    <div style={{
      borderTop: '1px solid var(--line)',
      background: 'var(--paper)',
      padding: '10px 20px 14px',
      height: mode === 'detail' ? 260 : 156, flexShrink: 0, overflow: 'hidden',
      transition: 'height 200ms',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 10 }}>
        <div style={{ fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.8, color: 'var(--ink-4)', fontWeight: 600 }}>
          Production estimée
        </div>
        <div style={{ display: 'flex', gap: 1, padding: 2, background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 4 }}>
          {[
            { k: 'monthly', l: 'mensuel' },
            { k: 'hourly',  l: 'horaire (21 juin)' },
            { k: 'detail',  l: 'détail autocons.' },
          ].map(t => (
            <button key={t.k} onClick={() => setMode(t.k)} style={{
              padding: '2px 8px', fontSize: 10,
              background: mode === t.k ? 'var(--paper)' : 'transparent',
              color: mode === t.k ? 'var(--ink)' : 'var(--ink-4)',
              borderRadius: 2, fontFamily: 'var(--font-mono)',
              border: mode === t.k ? '1px solid var(--line)' : '1px solid transparent',
            }}>{t.l}</button>
          ))}
        </div>
        <div style={{ flex: 1 }} />
        <DockStat label="Annuel" value={Math.round(total / 1000) + ' MWh'} color="var(--signal-deep)" />
        <DockStat label="Pic mensuel" value={`${Math.round((peak?.prod || 0) / 1000)} MWh (${peak?.mois || '—'})`} />
        <DockStat label="Autoconso" value={tauxAutocons + ' %'} color="var(--signal-deep)" />
        <DockStat label="Surplus revendu" value={Math.round(totalSurplus / 1000) + ' MWh'} />
      </div>

      {mode !== 'detail' && (
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 3, height: 72, paddingLeft: 2 }}>
          {dataByMode.map((d, i) => {
            const h = (d.v / max) * 68;
            const isPeak = d.v === max;
            return (
              <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
                <div style={{
                  width: '100%', height: h,
                  background: isPeak
                    ? 'linear-gradient(to top, var(--signal-deep), var(--signal))'
                    : 'linear-gradient(to top, var(--signal-deep), var(--signal-soft))',
                  borderRadius: '2px 2px 0 0',
                  position: 'relative',
                  opacity: mode === 'monthly' ? 1 : 0.9,
                }}>
                  {isPeak && mode === 'monthly' && (
                    <div style={{
                      position: 'absolute', top: -16, left: '50%', transform: 'translateX(-50%)',
                      fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--signal-deep)', fontWeight: 600,
                      whiteSpace: 'nowrap',
                    }}>{Math.round(d.v / 1000)} MWh</div>
                  )}
                </div>
                <div style={{ fontSize: 9, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>{d.m}</div>
              </div>
            );
          })}
        </div>
      )}

      {mode === 'detail' && (
        <div style={{ overflowX: 'auto', overflowY: 'auto', maxHeight: 210, border: '1px solid var(--hairline)', borderRadius: 5 }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11 }}>
            <thead>
              <tr style={{ borderBottom: '1px solid var(--line)', background: 'var(--paper-2)' }}>
                {['Mois', 'Prod. (kWh)', 'Intensité', 'Autocons. (kWh)', 'Surplus (kWh)'].map(h => (
                  <th key={h} style={{ padding: '6px 10px', textAlign: 'left', fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {mensuel.map((m, i) => (
                <tr key={i} style={{ borderBottom: '1px solid var(--hairline)' }}>
                  <td style={{ padding: '6px 10px', fontWeight: 500 }}>{m.mois}</td>
                  <td style={{ padding: '6px 10px' }} className="mono">{(m.prod || 0).toLocaleString('fr-FR')}</td>
                  <td style={{ padding: '6px 10px', width: 160 }}>
                    <div style={{ height: 5, background: 'var(--paper-2)', borderRadius: 2, overflow: 'hidden' }}>
                      <div style={{ width: (m.intensite || 0) + '%', height: '100%', background: 'var(--signal-deep)' }} />
                    </div>
                  </td>
                  <td style={{ padding: '6px 10px', color: 'var(--signal-deep)' }} className="mono">{(m.autocons || 0).toLocaleString('fr-FR')}</td>
                  <td style={{ padding: '6px 10px', color: 'var(--ink-3)' }} className="mono">{(m.surplus || 0).toLocaleString('fr-FR')}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

function DockStat({ label, value, color }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
      <span style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{label}</span>
      <span className="mono" style={{ fontSize: 12, fontWeight: 600, color: color || 'var(--ink)' }}>{value}</span>
    </div>
  );
}

// ─── Inspector (right panel) ─────────────────────────────────
function SolarInspector({ project, scenarioId, setScenarioId, inputs, setInput, results }) {
  const [tab, setTab] = useState('sim'); // sim | scenarios | cee | export
  const activeScenario = SCENARIOS.find(s => s.id === scenarioId) || SCENARIOS[0];

  return (
    <aside style={{
      background: 'var(--paper)',
      display: 'flex', flexDirection: 'column',
      minHeight: 0,
    }}>
      {/* Tabs */}
      <div style={{ display: 'flex', borderBottom: '1px solid var(--line)', padding: '4px 4px 0' }}>
        {[
          { k: 'sim', l: 'Simulation' },
          { k: 'scenarios', l: 'Scénarios' },
          { k: 'cee', l: 'Valorisation' },
          { k: 'export', l: 'Export' },
          { k: 'chatter', l: '📎 Pièces' },
        ].map(t => (
          <button
            key={t.k} onClick={() => setTab(t.k)}
            style={{
              flex: 1, padding: '8px 4px', fontSize: 11, fontWeight: 500,
              color: tab === t.k ? 'var(--ink)' : 'var(--ink-4)',
              borderBottom: tab === t.k ? '2px solid var(--signal)' : '2px solid transparent',
              marginBottom: -1,
            }}
          >{t.l}</button>
        ))}
      </div>

      <div style={{ flex: 1, overflowY: 'auto' }}>
        {tab === 'sim' && <SimulationPane project={project} inputs={inputs} setInput={setInput} results={results} />}
        {tab === 'scenarios' && <ScenariosPane scenarioId={scenarioId} setScenarioId={setScenarioId} results={results} />}
        {tab === 'cee' && <ValorisationPane project={project} inputs={inputs} setInput={setInput} results={results} />}
        {tab === 'export' && <ExportPane project={project} inputs={inputs} results={results} />}
        {tab === 'chatter' && (
          window.ChatterPanel && project?.id
            ? <div style={{ padding: 12 }}><window.ChatterPanel entityType="solar_project" entityId={project.id} /></div>
            : <div style={{ padding: 20, fontSize: 11, color: 'var(--ink-4)' }}>{project?.id ? 'Module chatter non chargé.' : 'Projet non persisté — enregistre-le d\'abord.'}</div>
        )}
      </div>
    </aside>
  );
}

// Input compact dans un Row (même structure visuelle que les Row d'affichage)
function InputField({ value, onChange, type = 'text', width = 110, step, min, max, placeholder }) {
  return (
    <input
      value={value != null ? value : ''}
      onChange={e => onChange(type === 'number' ? Number(e.target.value) : e.target.value)}
      type={type} step={step} min={min} max={max} placeholder={placeholder}
      style={{
        width, padding: '4px 8px', fontSize: 12,
        border: '1px solid var(--line-2)', borderRadius: 4,
        background: 'var(--paper-2)', color: 'var(--ink)', fontFamily: 'var(--font-sans)',
        textAlign: 'right',
      }}
    />
  );
}
function SelectField({ value, onChange, options, width = 160, groups }) {
  return (
    <select
      value={value}
      onChange={e => onChange(e.target.value)}
      style={{
        width, padding: '4px 8px', fontSize: 12,
        border: '1px solid var(--line-2)', borderRadius: 4,
        background: 'var(--paper-2)', color: 'var(--ink)', fontFamily: 'var(--font-sans)',
      }}
    >
      {groups
        ? groups.map(g => (
            <optgroup key={g.label} label={g.label}>
              {g.keys.map(k => {
                const db = window.SOLAR_DATA && window.SOLAR_DATA.PANNEAUX_DB;
                const p = db && db[k];
                return <option key={k} value={k}>{p ? `${p.b} — ${p.m} (${p.wc} Wc)` : k}</option>;
              })}
            </optgroup>
          ))
        : options.map(o => (<option key={o.v} value={o.v}>{o.label}</option>))}
    </select>
  );
}

// Simulation sub-pane — sections B (Type install) + C (Panneaux) + H (Équipements) + Performance
function SimulationPane({ project, inputs, setInput, results }) {
  if (!inputs || !results) return <div style={{ padding: 14, color: 'var(--ink-4)' }}>Chargement…</div>;
  const SD = window.SOLAR_DATA;
  const panneau = (SD && SD.PANNEAUX_DB[inputs.panneauKey]) || {};
  const onSelectPanneau = (k) => {
    setInput('panneauKey', k);
    if (SD && SD.PANNEAUX_DB[k] && k !== 'custom') setInput('wc', SD.PANNEAUX_DB[k].wc);
  };
  return (
    <div style={{ padding: '14px 16px 24px', display: 'flex', flexDirection: 'column', gap: 14 }}>
      {/* B — Type d'installation */}
      <section>
        <SectionLabel>Type d'installation</SectionLabel>
        <SolarRow k="Type" v={<SelectField value={inputs.type} onChange={v => setInput('type', v)} options={SD.TYPES_INSTALLATION} width={180} />} />
        <SolarRow k="Zone climatique" v={<SelectField value={inputs.zone} onChange={v => setInput('zone', v)} options={SD.ZONES} />} />
        <SolarRow k="Orientation" v={<SelectField value={inputs.orient} onChange={v => setInput('orient', v)} options={SD.ORIENTATIONS} />} />
        <SolarRow k="Inclinaison" v={<SelectField value={String(inputs.inclin)} onChange={v => setInput('inclin', v)} options={SD.INCLINAISONS} />} />
        <SolarRow k="Irradiation (zone × orient.)" v={<span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{(SD.IRRADIATION[inputs.zone] && SD.IRRADIATION[inputs.zone][inputs.orient]) || '—'} kWh/m²·an</span>} />
      </section>

      {/* C — Panneaux */}
      <section>
        <SectionLabel>Panneaux photovoltaïques</SectionLabel>
        <SolarRow k="Modèle (40+)" v={<SelectField value={inputs.panneauKey} onChange={onSelectPanneau} groups={SD.PANNEAUX_GROUPES} width={200} />} />
        <SolarRow k="Puissance unit." v={<InputField type="number" value={inputs.wc} onChange={v => setInput('wc', v)} />} />
        <SolarRow k="Nombre panneaux" v={<InputField type="number" value={inputs.nb} onChange={v => setInput('nb', v)} />} />
        <SolarRow k="Pertes système (%)" v={<InputField type="number" value={inputs.pertes} onChange={v => setInput('pertes', v)} step="0.1" />} />
        <SolarRow k="Rendement panneau" v={<span className="mono" style={{ fontSize: 10 }}>{panneau.rdt || '—'} %</span>} muted />
        <SolarRow k="Garantie" v={<span className="mono" style={{ fontSize: 10 }}>{panneau.gar || '—'} ans</span>} muted />
        <SolarRow k="Prix unitaire" v={<span className="mono" style={{ fontSize: 10 }}>{panneau.prix || '—'} €</span>} muted />
      </section>

      {/* Performance live */}
      <section>
        <SectionLabel>Performance calculée</SectionLabel>
        <SolarRow k="Puissance crête" v={<span className="mono" style={{ color: 'var(--signal-deep)', fontWeight: 600 }}>{results.puissKwc} kWc</span>} tone="signal" />
        <SolarRow k="Production annuelle" v={<span className="mono" style={{ color: 'var(--signal-deep)', fontWeight: 600 }}>{results.prod.toLocaleString('fr-FR')} kWh</span>} tone="signal" />
        <SolarRow k="Productible spéc." v={<span className="mono">{results.puissKwc > 0 ? Math.round(results.prod / results.puissKwc) : 0} kWh/kWc</span>} />
        <SolarRow k="Surface panneaux" v={<span className="mono">{results.surface} m²</span>} />
        <SolarRow k="Pertes ombrage" v={<span className="mono">{inputs.pertes} %</span>} tone={inputs.pertes > 14 ? 'amber' : 'muted'} />
      </section>

      {/* H — Équipements recommandés */}
      <section>
        <SectionLabel>Équipements recommandés</SectionLabel>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          <EquipRow k="Panneaux" v={`${inputs.nb} × ${panneau.b || ''} ${panneau.m || ''} (${inputs.wc} Wc)`} sub={`${panneau.rdt || '—'} % · garantie ${panneau.gar || '—'} ans · ${results.surface} m²`} />
          <EquipRow k="Onduleur" v={`${results.onduleur.marque} ${results.onduleur.modele}`} sub={`${results.onduleur.type} · ${results.onduleur.puiss} · ${results.onduleur.prix}`} />
          <EquipRow k="Fixation" v={results.fixation.nom} sub={`${results.fixation.desc} · ${results.fixation.prix}`} />
          <EquipRow k="Câblage DC" v="Câbles solaires + connecteurs MC4" sub="~18 €/kWc" />
          {results.puissKwc > 6 && <EquipRow k="Gestionnaire autoconso" v="MyLight MSB ou Enphase Envoy" sub="~280–450 € · monitoring + pilotage" />}
          <EquipRow k="Monitoring" v="Supervision temps réel" sub="~350 € · SMA Sunny Portal / Fronius Solar.web" />
          {inputs.myLight && <EquipRow k="MyLight 150" v={`Batterie virtuelle ${inputs.battKwh} kWh`} sub={`35 €/mois · gain net ${results.gainMyLight.toLocaleString('fr-FR')} €/an`} highlight />}
        </div>
      </section>

      <section>
        <SectionLabel>Météo de référence</SectionLabel>
        <SolarRow k="Source" v={<span className="mono" style={{ fontSize: 10 }}>PVGIS-SARAH3</span>} />
        <SolarRow k="Irradiation" v={<span className="mono" style={{ fontSize: 10 }}>{(SD.IRRADIATION[inputs.zone] && SD.IRRADIATION[inputs.zone][inputs.orient]) || '—'} kWh/m²·an</span>} />
        <SolarRow k="Facteur inclinaison" v={<span className="mono" style={{ fontSize: 10 }}>×{SD.INCLIN_FACTOR[String(inputs.inclin)] || 1}</span>} />
      </section>
    </div>
  );
}

// Petite ligne équipement — réutilise le style "Row" mais multi-lignes
function EquipRow({ k, v, sub, highlight }) {
  return (
    <div style={{
      padding: '6px 8px', fontSize: 11,
      background: highlight ? 'var(--signal-tint)' : 'var(--paper-2)',
      border: '1px solid ' + (highlight ? 'var(--signal-soft)' : 'var(--hairline)'),
      borderRadius: 4,
    }}>
      <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{k}</div>
      <div style={{ fontSize: 12, fontWeight: 500, color: 'var(--ink)' }}>{v}</div>
      {sub && <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 1 }}>{sub}</div>}
    </div>
  );
}

function ScenariosPane({ scenarioId, setScenarioId }) {
  const active = SCENARIOS.find(s => s.id === scenarioId);
  return (
    <div style={{ padding: '14px 16px 24px', display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ fontSize: 11, color: 'var(--ink-4)', lineHeight: 1.5 }}>
        Compare jusqu'à 4 configurations — orientation, inclinaison, technologie panneau. Chaque scénario relance PVGIS et met à jour CAPEX/ROI.
      </div>

      {SCENARIOS.map(s => {
        const selected = s.id === scenarioId;
        return (
          <button
            key={s.id}
            onClick={() => setScenarioId(s.id)}
            style={{
              textAlign: 'left', padding: '10px 12px',
              border: selected ? '1.5px solid var(--signal)' : '1px solid var(--line)',
              borderRadius: 6,
              background: selected ? 'var(--signal-tint)' : 'var(--paper)',
              transition: 'all 120ms',
              display: 'flex', flexDirection: 'column', gap: 8,
            }}
          >
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span className="mono" style={{ fontSize: 10, padding: '1px 5px', background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 3, color: 'var(--ink-3)' }}>
                scén.{s.id}
              </span>
              <span style={{ fontSize: 12, fontWeight: 600 }}>{s.name}</span>
              <span style={{ flex: 1 }} />
              {s.ratio !== 1 && (
                <span className="mono" style={{ fontSize: 10, color: s.ratio > 1 ? 'var(--signal-deep)' : 'var(--copper)', fontWeight: 600 }}>
                  {s.ratio > 1 ? '+' : ''}{((s.ratio - 1) * 100).toFixed(1)}%
                </span>
              )}
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
              <TinyMetric label="kWc" value={s.kwc} />
              <TinyMetric label="MWh/an" value={Math.round(s.prod / 1000)} />
              <TinyMetric label="CAPEX" value={'€' + (s.capex / 1000).toFixed(0) + 'k'} />
            </div>
            {/* prod bar */}
            <div style={{ height: 4, background: 'var(--paper-2)', borderRadius: 2, overflow: 'hidden' }}>
              <div style={{ width: (s.ratio / 1.1) * 100 + '%', height: '100%', background: selected ? 'var(--signal)' : 'var(--signal-deep)' }} />
            </div>
          </button>
        );
      })}

      <button style={{
        padding: '8px 12px', fontSize: 12,
        border: '1px dashed var(--line-2)', borderRadius: 6,
        background: 'transparent', color: 'var(--ink-4)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
      }}>
        <Icon.plus /> Nouveau scénario
      </button>
    </div>
  );
}

function TinyMetric({ label, value }) {
  return (
    <div style={{ padding: '4px 6px', background: 'rgba(14,16,16,0.03)', borderRadius: 3 }}>
      <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, fontWeight: 600 }}>{label}</div>
      <div className="mono" style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink)' }}>{value}</div>
    </div>
  );
}

// Valorisation — sections D (Conso/Tarifs) + E (MyLight 150) + F (résultats économie/ROI) + CEE + Impact
function ValorisationPane({ project, inputs, setInput, results }) {
  if (!inputs || !results) return <div style={{ padding: 14, color: 'var(--ink-4)' }}>Chargement…</div>;
  const roiVal = results.ecoAvec > 0 ? (inputs.cout / results.ecoAvec) : 20;
  const roiClamped = Math.min(20, Math.max(0.5, roiVal));
  return (
    <div style={{ padding: '14px 16px 24px', display: 'flex', flexDirection: 'column', gap: 16 }}>
      {/* Hero ROI live */}
      <div style={{ padding: 14, background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 6 }}>
        <div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.8, color: 'var(--ink-4)', fontWeight: 600 }}>Temps de retour</div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginTop: 4 }}>
          <span className="mono" style={{ fontSize: 30, fontWeight: 600, letterSpacing: '-0.03em', color: 'var(--signal-deep)' }}>{results.roi}</span>
          <span style={{ fontSize: 12, color: 'var(--ink-3)' }}>années</span>
        </div>
        <div style={{ fontSize: 10, color: 'var(--ink-4)', marginTop: 2, fontFamily: 'var(--font-mono)' }}>
          autoconso {results.tauxAutocons} % · autosuffisance {results.autosuffisance} %
        </div>
        <div style={{ marginTop: 14, position: 'relative', height: 24 }}>
          <div style={{ position: 'absolute', inset: 0, display: 'flex', gap: 2 }}>
            {Array.from({ length: 20 }).map((_, i) => {
              const crossed = i < roiClamped;
              return <div key={i} style={{ flex: 1, background: crossed ? 'var(--copper-tint)' : 'var(--signal-tint)', borderLeft: i === Math.floor(roiClamped) ? '2px solid var(--signal)' : 'none' }} />;
            })}
          </div>
          <div style={{ position: 'absolute', bottom: -12, left: 0, fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--ink-4)' }}>an 0</div>
          <div style={{ position: 'absolute', bottom: -12, right: 0, fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--ink-4)' }}>an 20</div>
        </div>
      </div>

      {/* D — Consommation & tarifs */}
      <section>
        <SectionLabel>Consommation & tarifs</SectionLabel>
        <SolarRow k="Conso annuelle" v={<><InputField type="number" value={inputs.conso} onChange={v => setInput('conso', v)} /> <span style={{ fontSize: 10, color: 'var(--ink-4)', marginLeft: 4 }}>kWh</span></>} />
        <SolarRow k="Tarif achat réseau" v={<><InputField type="number" step="0.001" value={inputs.tachat} onChange={v => setInput('tachat', v)} width={80} /> <span style={{ fontSize: 10, color: 'var(--ink-4)', marginLeft: 4 }}>€/kWh</span></>} />
        <SolarRow k="Tarif revente" v={<><InputField type="number" step="0.001" value={inputs.trevente} onChange={v => setInput('trevente', v)} width={80} /> <span style={{ fontSize: 10, color: 'var(--ink-4)', marginLeft: 4 }}>€/kWh</span></>} />
        <SolarRow k="Coût installation" v={<><InputField type="number" value={inputs.cout} onChange={v => setInput('cout', v)} /> <span style={{ fontSize: 10, color: 'var(--ink-4)', marginLeft: 4 }}>€</span></>} />
        <SolarRow k="€/kWc calculé" v={<span className="mono" style={{ color: 'var(--ink-3)' }}>{results.coutKwc.toLocaleString('fr-FR')} €/kWc</span>} muted />
      </section>

      {/* E — MyLight 150 */}
      <section>
        <SectionLabel>Batterie virtuelle MyLight 150</SectionLabel>
        <SolarRow k="Activer" v={
          <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
            <input type="checkbox" checked={inputs.myLight} onChange={e => setInput('myLight', e.target.checked)} />
            <span style={{ fontSize: 11 }}>{inputs.myLight ? 'Activée' : 'Non'}</span>
          </label>
        } />
        {inputs.myLight && (
          <>
            <SolarRow k="Constructeur" v={
              <select value={inputs.battMarque || 'mylight_virtuelle'} onChange={e => {
                setInput('battMarque', e.target.value);
                const b = window.SOLAR_DATA?.BATTERIES_DB?.[e.target.value];
                if (b && b.capacites[0]) setInput('battKwh', b.capacites[0].kwh);
              }} style={{ width: 180, padding: '4px 8px', fontSize: 11, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)' }}>
                {window.SOLAR_DATA && Object.entries(window.SOLAR_DATA.BATTERIES_DB).map(([k, b]) => (
                  <option key={k} value={k}>{b.marque} — {b.label.replace(b.marque, '').replace(' — ', '').trim() || b.label}</option>
                ))}
              </select>
            } />
            <SolarRow k="Capacité constructeur" v={
              <select value={inputs.battKwh} onChange={e => setInput('battKwh', Number(e.target.value))} style={{ width: 180, padding: '4px 8px', fontSize: 11, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)' }}>
                {(window.SOLAR_DATA?.BATTERIES_DB?.[inputs.battMarque || 'mylight_virtuelle']?.capacites || []).map(c => (
                  <option key={c.kwh} value={c.kwh}>{c.label}</option>
                ))}
              </select>
            } />
            <SolarRow k="Abonnement" v={<span className="mono" style={{ fontSize: 10 }}>{window.SOLAR_DATA?.BATTERIES_DB?.[inputs.battMarque || 'mylight_virtuelle']?.abonnementMensuel ? (window.SOLAR_DATA.BATTERIES_DB[inputs.battMarque || 'mylight_virtuelle'].abonnementMensuel + ' €/mois') : '—'}</span>} muted />
            <SolarRow k="Rendement" v={<span className="mono" style={{ fontSize: 10 }}>{Math.round((window.SOLAR_DATA?.BATTERIES_DB?.[inputs.battMarque || 'mylight_virtuelle']?.rendement || 0.85) * 100)} %</span>} muted />
            <div style={{ marginTop: 6, padding: 10, background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 5 }}>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Comparatif sans / avec batterie</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 80px 80px', gap: 4, fontSize: 11 }}>
                <span style={{ color: 'var(--ink-4)' }}></span>
                <span style={{ textAlign: 'right', color: 'var(--ink-4)' }}>Sans</span>
                <span style={{ textAlign: 'right', color: 'var(--signal-deep)', fontWeight: 600 }}>Avec</span>
                <span>Autoconso</span>
                <span className="mono" style={{ textAlign: 'right' }}>{results.autoconsSans.toLocaleString('fr-FR')}</span>
                <span className="mono" style={{ textAlign: 'right', color: 'var(--signal-deep)', fontWeight: 600 }}>{results.autoconsAvec.toLocaleString('fr-FR')}</span>
                <span>Économie/an</span>
                <span className="mono" style={{ textAlign: 'right' }}>{results.ecoSans.toLocaleString('fr-FR')} €</span>
                <span className="mono" style={{ textAlign: 'right', color: 'var(--signal-deep)', fontWeight: 600 }}>{results.ecoAvec.toLocaleString('fr-FR')} €</span>
                <span style={{ gridColumn: '1/-1', paddingTop: 4, borderTop: '1px solid var(--signal-soft)', marginTop: 4, color: 'var(--signal-deep)', fontWeight: 600 }}>
                  Gain net : +{results.gainMyLight.toLocaleString('fr-FR')} €/an
                </span>
              </div>
            </div>
          </>
        )}
      </section>

      {/* F — Résultats dimensionnement */}
      <section>
        <SectionLabel>Résultats installation</SectionLabel>
        <SolarRow k="Puissance crête" v={<span className="mono" style={{ color: 'var(--signal-deep)', fontWeight: 600 }}>{results.puissKwc} kWc</span>} tone="signal" />
        <SolarRow k="Production annuelle" v={<span className="mono" style={{ color: 'var(--signal-deep)', fontWeight: 600 }}>{results.prod.toLocaleString('fr-FR')} kWh</span>} tone="signal" />
        <SolarRow k="Taux autoconso" v={<span className="mono">{results.tauxAutocons} %</span>} />
        <SolarRow k="Taux autosuffisance" v={<span className="mono">{results.autosuffisance} %</span>} />
        <SolarRow k="Économies annuelles" v={<span className="mono" style={{ color: 'var(--signal-deep)' }}>+ {results.ecoAvec.toLocaleString('fr-FR')} €/an</span>} />
        <SolarRow k="Revente surplus" v={<span className="mono">+ {(inputs.myLight ? Math.round(results.surplusAvec * inputs.trevente) : results.revSans).toLocaleString('fr-FR')} €/an</span>} />
        <SolarRow k="Investissement" v={<span className="mono">{inputs.cout.toLocaleString('fr-FR')} €</span>} />
        <SolarRow k="ROI" v={<span className="mono" style={{ color: 'var(--signal-deep)', fontWeight: 600 }}>{results.roi} ans</span>} tone="signal" />
      </section>

      {/* CEE (si applicable — FOST BAR-EN-101 par défaut) */}
      <section>
        <SectionLabel>CEE — BAR-EN-101</SectionLabel>
        <SolarRow k="Volume cumac" v={<span className="mono">{project?.ceeCumac ? (project.ceeCumac / 1000).toFixed(0) + ' MWh cumac' : '—'}</span>} />
        <SolarRow k="Prime CEE" v={<span className="mono">{project?.primeCee ? project.primeCee.toLocaleString('fr-FR') + ' €' : '—'}</span>} />
        <SolarRow k="Délégataire" v="Akea · via SFTP" muted />
      </section>

      {/* Impact */}
      <section>
        <SectionLabel>Impact environnemental</SectionLabel>
        <SolarRow k="CO₂ évité" v={<span className="mono">{Math.round(results.prod * 0.06 / 1000)} t/an</span>} />
        <SolarRow k="Équiv. km voiture" v={<span className="mono">{Math.round(results.prod * 0.06 / 1000 * 4800).toLocaleString('fr-FR')} km</span>} muted />
        <SolarRow k="Équiv. foyers (élec)" v={<span className="mono">{Math.round(results.prod / 4500)} foyers</span>} muted />
      </section>
    </div>
  );
}

function ExportPane({ project, inputs, results }) {
  const [busy, setBusy] = useState(null); // current kind in progress
  const [exportResults, setExportResults] = useState({}); // { kind: { url, filename } }
  const API = (window.AE_API && window.AE_API.BASE) || '';
  // Construit un "project" enrichi avec les inputs live pour les PDFs
  const liveProject = {
    ...project,
    kwc: (results && results.puissKwc) || project.kwc,
    production: (results && results.prod) || project.production,
    panels: (inputs && inputs.nb) || project.panels,
    surfaceExploitable: (results && results.surface) || project.surfaceExploitable,
    capex: (inputs && inputs.cout) || project.capex,
    primeCee: project.primeCee,
    ceeCumac: project.ceeCumac,
    autoconsoTaux: (results && results.tauxAutocons) || project.autoconsoTaux,
    co2: (results && Math.round((results.prod || 0) * 0.06 / 1000)) || project.co2,
    roiYears: (results && results.roi) || project.roiYears,
    shadingLoss: (inputs && inputs.pertes) || project.shadingLoss,
    orientation: (inputs && ({ S: 180, SE: 135, SO: 225, E: 90, O: 270 })[inputs.orient]) || project.orientation,
    tilt: (inputs && Number(inputs.inclin)) || project.tilt,
  };
  const download = async (kind) => {
    if (busy) return;
    setBusy(kind);
    try {
      const body = kind === 'fiche-technique'
        ? { project: liveProject, monthly: (results && results.mensuel ? results.mensuel.map(m => ({ m: m.mois, v: m.prod })) : window.MONTHLY_PRODUCTION) || null }
        : { project: liveProject };
      const r = await fetch(`${API}/api/solar/export/${kind}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      }).then(r => r.json()).catch((e) => ({ ok: false, error: e.message }));
      if (r.ok && r.url) {
        setExportResults((x) => ({ ...x, [kind]: r }));
        window.open(API + r.url, '_blank');
      } else {
        setExportResults((x) => ({ ...x, [kind]: { error: r.error || 'Échec' } }));
      }
    } finally {
      setBusy(null);
    }
  };
  const exports = [
    { kind: 'fiche-technique', label: 'Fiche technique PV', desc: 'PDF dimensionnement + PVGIS + synthèse', icon: Icon.doc, primary: true, enabled: true },
    { kind: 'dossier-cee', label: 'Dossier CEE BAR-EN-101', desc: 'AH (Claude) + check-list PNCEE + synthèse', icon: Icon.folder, primary: true, enabled: true },
    { kind: 'plan-dwg', label: 'Plan calpinage DWG', desc: 'Export AutoCAD · toiture + panneaux côtés', icon: Icon.layers, enabled: false },
    { kind: 'devis-installateur', label: 'Devis installateur', desc: 'Matching RGE + bordereau prix 2026', icon: Icon.doc, enabled: false },
    { kind: 'charpente', label: 'Étude de charpente', desc: 'Note de calcul surcharge 15 kg/m²', icon: Icon.shield, enabled: false },
    { kind: 'impact', label: 'Rapport d\'impact', desc: 'CO₂ évité, équivalences, comms RSE', icon: Icon.chart, enabled: false },
  ];
  return (
    <div style={{ padding: '14px 16px 24px', display: 'flex', flexDirection: 'column', gap: 10 }}>
      <div style={{ fontSize: 11, color: 'var(--ink-4)', lineHeight: 1.5, marginBottom: 4 }}>
        Tous les exports sont générés nativement (Claude + pdfkit) — aucun service tiers.
      </div>

      {exports.map((e, i) => {
        const r = exportResults[e.kind];
        const loading = busy === e.kind;
        const disabled = !e.enabled || loading;
        return (
          <button key={i}
            onClick={() => e.enabled && download(e.kind)}
            disabled={disabled}
            style={{
              textAlign: 'left', padding: '10px 12px',
              border: '1px solid ' + (r?.url ? 'var(--signal-soft)' : 'var(--line)'),
              borderRadius: 6,
              background: r?.url ? 'var(--signal-tint)' : e.primary ? 'var(--paper-2)' : 'var(--paper)',
              display: 'flex', alignItems: 'center', gap: 10,
              transition: 'background 120ms',
              cursor: disabled ? (e.enabled ? 'wait' : 'not-allowed') : 'pointer',
              opacity: e.enabled ? 1 : 0.55,
            }}
            onMouseEnter={ev => !disabled && (ev.currentTarget.style.background = 'var(--paper-3)')}
            onMouseLeave={ev => !disabled && (ev.currentTarget.style.background = r?.url ? 'var(--signal-tint)' : e.primary ? 'var(--paper-2)' : 'var(--paper)')}
          >
            <div style={{
              width: 32, height: 32, borderRadius: 5,
              background: e.primary ? 'var(--ink)' : 'var(--paper-3)',
              color: e.primary ? 'var(--signal)' : 'var(--ink-3)',
              display: 'grid', placeItems: 'center', flexShrink: 0,
            }}>
              <e.icon />
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--ink)' }}>{e.label}</div>
              <div style={{ fontSize: 11, color: r?.error ? 'var(--rouge)' : 'var(--ink-4)', marginTop: 1 }}>
                {loading ? 'Génération en cours…' : r?.error ? 'Erreur : ' + r.error : r?.url ? '✓ PDF prêt · ' + (r.ahSource ? 'AH Claude' : '') : e.desc}
              </div>
            </div>
            {e.enabled ? (r?.url ? <span style={{ color: 'var(--signal-deep)', fontSize: 16 }}>↓</span> : <Icon.arrow style={{ color: 'var(--ink-4)' }} />) : <span style={{ fontSize: 9, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: 0.4 }}>soon</span>}
          </button>
        );
      })}

      <div style={{
        marginTop: 10, padding: 12,
        background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 6,
        fontSize: 11, color: 'var(--ink-2)', lineHeight: 1.5,
      }}>
        <strong style={{ color: 'var(--signal-deep)' }}>Dossier PNCEE prêt à 87 %</strong> — pièces manquantes : attestation bénéficiaire signée + étude de charpente.
      </div>
    </div>
  );
}

// ─── Helpers ─────────────────────────────────────────────────
function SectionLabel({ children }) {
  return (
    <div style={{
      fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.8,
      color: 'var(--ink-4)', fontWeight: 600, marginBottom: 6,
      paddingBottom: 4, borderBottom: '1px solid var(--hairline)',
    }}>{children}</div>
  );
}

function SolarRow({ k, v, tone, muted }) {
  const toneColor = tone === 'signal' ? 'var(--signal-deep)' : tone === 'amber' ? 'var(--copper)' : null;
  return (
    <div style={{
      display: 'flex', alignItems: 'baseline',
      padding: '4px 0', fontSize: 12,
      opacity: muted ? 0.65 : 1,
    }}>
      <span style={{ color: 'var(--ink-4)', flex: '0 0 50%' }}>{k}</span>
      <span style={{ color: toneColor || 'var(--ink-2)', fontWeight: tone ? 600 : 500, textAlign: 'right', flex: 1 }}>
        {v}
      </span>
    </div>
  );
}

// ─── Nouveau projet solaire depuis adresse ────────────────────
// Chaîne backend : BAN geocode → IGN altitude/rooftop → PVGIS → création
// TechVisit + sauvegarde satelliteData. Feature Claude-first souveraine.
function NewSolarProjectModal({ onClose, onCreated, prefillClient }) {
  const [address, setAddress] = useState(prefillClient?.street ? [prefillClient.street, prefillClient.zip, prefillClient.city].filter(Boolean).join(' ') : '');
  // Contact (Section A) — ClientPicker unifié (Règle 3.a)
  const [picked, setPicked] = useState(prefillClient || null);
  const [clientNom, setClientNom] = useState(prefillClient?.lastName || '');
  const [clientPrenom, setClientPrenom] = useState(prefillClient?.firstName || '');
  const [clientSociete, setClientSociete] = useState(prefillClient?.isCompany ? prefillClient?.name : '');
  const [clientEmail, setClientEmail] = useState(prefillClient?.email || '');
  const [clientTel, setClientTel] = useState(prefillClient?.phone || '');

  const onPickClient = (c) => {
    setPicked(c);
    if (!c) return;
    if (c.isCompany) {
      setClientSociete(c.name || '');
    } else {
      const parts = (c.name || '').split(' ');
      setClientPrenom(c.firstName || parts[0] || '');
      setClientNom(c.lastName || parts.slice(1).join(' ') || '');
    }
    setClientEmail(c.email || '');
    setClientTel(c.phone || c.mobile || '');
    if (c.street) setAddress([c.street, c.zip, c.city].filter(Boolean).join(' '));
  };
  // Dimensionnement (B)
  const [kwc, setKwc] = useState(100);
  const [tilt, setTilt] = useState(15);
  const [aspect, setAspect] = useState(0); // 0 = Sud
  const [loss, setLoss] = useState(14);
  // Panneaux (C)
  const [panneauKey, setPanneauKey] = useState('longi_himo6_440');
  // Consommation & tarifs (D)
  const [conso, setConso] = useState(85000);
  const [tachat, setTachat] = useState(0.2276);
  const [trevente, setTrevente] = useState(0.1307);
  const [cout, setCout] = useState(120000);
  // MyLight (E)
  const [myLight, setMyLight] = useState(false);
  const [battMarque, setBattMarque] = useState('mylight_virtuelle');
  const [battKwh, setBattKwh] = useState(150);
  const [step, setStep] = useState('form'); // form | processing | error
  const [progress, setProgress] = useState([]);
  const [error, setError] = useState(null);

  const API = (window.AE_API && window.AE_API.BASE) || '';

  const siteLabel = (soc, nom, prenom, addr) => (
    soc || (nom ? `${prenom || ''} ${nom}`.trim() : null) || (addr || '').split(',')[0] || 'Projet solaire'
  );

  const launch = async () => {
    if (!address.trim()) return;
    setStep('processing');
    setProgress([{ k: 'geocode', label: 'Géocodage BAN', status: 'running' }]);
    try {
      // 1. Geocode
      const g = await fetch(`${API}/api/satellite/geocode?address=${encodeURIComponent(address)}`).then(r => r.json());
      if (!g || !g.lat || !g.lon) throw new Error('Adresse introuvable');
      setProgress(p => [
        { k: 'geocode', label: `BAN · ${g.label || address}`, status: 'done' },
        { k: 'rooftop', label: 'Cadastre IGN + rooftop', status: 'running' },
      ]);
      // 2. Rooftop
      const rt = await fetch(`${API}/api/satellite/rooftop?lat=${g.lat}&lon=${g.lon}`).then(r => r.json()).catch(() => null);
      setProgress(p => [
        p[0],
        { k: 'rooftop', label: rt?.estimatedRooftopArea ? `Toiture ≈ ${rt.estimatedRooftopArea} m² (${rt.commune || '—'})` : 'Rooftop estimation indisponible', status: 'done' },
        { k: 'alt', label: 'Altitude IGN', status: 'running' },
      ]);
      // 3. Altitude
      const alt = await fetch(`${API}/api/satellite/altitude?lat=${g.lat}&lon=${g.lon}`).then(r => r.json()).catch(() => null);
      setProgress(p => [
        p[0], p[1],
        { k: 'alt', label: alt?.altitude != null ? `Altitude ${alt.altitude} m` : '—', status: 'done' },
        { k: 'pvgis', label: 'Simulation PVGIS', status: 'running' },
      ]);
      // 4. PVGIS
      const pv = await fetch(`${API}/api/satellite/pvgis?lat=${g.lat}&lon=${g.lon}&peakpower=${kwc}&loss=${loss}&aspect=${aspect}&angle=${tilt}`).then(r => r.json());
      if (!pv || !pv.annualProduction_kWh) throw new Error('PVGIS KO');
      setProgress(p => [
        p[0], p[1], p[2],
        { k: 'pvgis', label: `Production ${Math.round(pv.annualProduction_kWh).toLocaleString('fr-FR')} kWh/an`, status: 'done' },
        { k: 'link', label: 'Upsert Contact + Organization + Opportunity (interconnexion)', status: 'running' },
      ]);
      // 5a. Interconnexion RÈGLE N°3 — upsert Organization (réutilise picked.organizationId si existant)
      let orgId = picked?.organizationId || null;
      const odooId = picked?.odooId || null;
      if (!orgId && clientSociete && clientSociete.trim()) {
        const org = await fetch(`${API}/api/organizations`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ name: clientSociete.trim(), description: 'Créé depuis Studio solaire', odooId }),
        }).then(r => r.json()).catch(() => null);
        orgId = org?.id || null;
      }
      // 5b. Interconnexion — upsert Contact (réutilise picked.contactId si existant, sinon upsert par email)
      let contactId = picked?.contactId || null;
      if (!contactId && (clientNom || clientPrenom || clientEmail)) {
        const contact = await fetch(`${API}/api/contacts`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            firstName: clientPrenom || null,
            lastName: clientNom || null,
            email: clientEmail || null,
            phone: clientTel || null,
            organizationId: orgId, odooId,
            tags: ['solaire', 'studio'],
          }),
        }).then(r => r.json()).catch(() => null);
        contactId = contact?.id || null;
      }
      // 5c. Interconnexion — créer Opportunity (CRM) liée au contact + orga + Odoo externalId
      let opportunityId = null;
      const opp = await fetch(`${API}/api/crm/opportunities`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: `PV ${kwc} kWc · ${siteLabel(clientSociete, clientNom, clientPrenom, g.label || address)}`,
          kind: 'quote',
          source: picked?.source === 'odoo' ? 'odoo' : 'manual',
          discoveryStage: 'etude',
          stage: 'proposal',
          state: 'draft',
          amount: cout,
          partnerName: clientSociete || (clientNom ? `${clientPrenom} ${clientNom}`.trim() : null) || '—',
          partnerEmail: clientEmail || null,
          contactId, organizationId: orgId,
          externalId: odooId ? `odoo:partner:${odooId}` : null,
          probability: 60,
          tags: ['solaire', `${kwc}kWc`],
          description: `Projet solaire ${kwc} kWc · ${panneau.b} ${panneau.m} · adresse ${g.label || address}`,
        }),
      }).then(r => r.json()).catch(() => null);
      opportunityId = opp?.id || opp?.opportunity?.id || null;

      // 5d. Créer TechVisit + sauver satelliteData (lié à opp + contact)
      const created = await fetch(`${API}/api/visite/`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          clientAddress: g.label || address,
          lat: g.lat,
          lon: g.lon,
          type: 'mixed',
          technicianName: 'Studio solaire',
          status: 'draft',
          opportunityId,
          contactId,
        }),
      }).then(r => r.json()).catch(() => null);
      const visitId = created?.visit?.id || created?.id;
      if (visitId) {
        await fetch(`${API}/api/visite/${visitId}/satellite`, { method: 'POST' }).catch(() => null);
      }
      setProgress(p => [
        p[0], p[1], p[2], p[3],
        { k: 'link', label: `Liens : contact #${contactId || '—'} · org #${orgId || '—'} · opp #${opportunityId || '—'} · visite #${visitId || '—'}`, status: 'done' },
        { k: 'save', label: 'Création projet solaire', status: 'running' },
      ]);
      // Mise à jour instantanée de la liste locale (avant le re-fetch complet)
      const site = siteLabel(clientSociete, clientNom, clientPrenom, g.label || address);
      const nbPanneaux = Math.max(1, Math.round((kwc * 1000) / (panneau.wc || 440)));
      const newProject = {
        ref: 'SOL-' + new Date().getFullYear() + '-' + String(Date.now()).slice(-4),
        site,
        address: g.label || address,
        lat: g.lat, lng: g.lon,
        surfaceToiture: rt?.parcelSurface || 0,
        surfaceExploitable: rt?.estimatedRooftopArea || 0,
        orientation: aspect + 180,
        tilt,
        kwc,
        production: Math.round(pv.annualProduction_kWh),
        panels: nbPanneaux,
        exclusions: 0,
        shadingLoss: loss,
        autoconsoTaux: preview ? preview.tauxAutocons : 70,
        co2: Math.round((pv.annualProduction_kWh || 0) * 0.06 / 1000),
        fost: 'BAR-EN-101',
        ceeCumac: 0,
        primeCee: 0,
        capex: cout,
        roiYears: preview ? preview.roi : 7,
        status: 'simulation',
        updated: 'à l\'instant',
        zone: '—',
        techVisit: created?.visit?.ref || null,
        _altitude: alt?.altitude,
        _pvgis: pv,
        _rooftop: rt,
        _visitId: visitId,
        _contact: {
          nom: clientNom, prenom: clientPrenom, societe: clientSociete,
          email: clientEmail, tel: clientTel, adresse: g.label || address,
        },
        _myLight: myLight,
        _battMarque: battMarque,
        _battKwh: battKwh,
        _orient: aspect === 0 ? 'S' : aspect > 0 && aspect < 90 ? 'SO' : aspect < 0 && aspect > -90 ? 'SE' : aspect >= 90 ? 'O' : 'E',
        _type: 'toiture_ter',
        _panneauKey: panneauKey,
        _wc: panneau.wc,
        _conso: conso,
        _tachat: tachat,
        _trevente: trevente,
        // Liens inter-modules (règle n°3 CLAUDE.md)
        _contactId: contactId,
        _organizationId: orgId,
        _opportunityId: opportunityId,
      };
      const list = window.SOLAR_PROJECTS || [];
      window.SOLAR_PROJECTS = [newProject, ...list];
      window.dispatchEvent(new CustomEvent('ae:solar-updated'));
      setProgress(p => [
        p[0], p[1], p[2], p[3], p[4],
        { k: 'save', label: visitId ? `Projet lié à visite ${created.visit?.ref || visitId}` : 'Projet créé en local', status: 'done' },
      ]);
      setTimeout(() => { onCreated && onCreated(newProject); }, 600);
    } catch (e) {
      setError(e.message || String(e));
      setStep('error');
    }
  };

  const inputStyle = { padding: '7px 10px', fontSize: 13, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', color: 'var(--ink)', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box' };
  const labelStyle = { fontSize: 10, color: 'var(--ink-4)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.4 };
  const fieldStyle = { display: 'flex', flexDirection: 'column', gap: 4, minWidth: 0 };

  // ─── Mini preview live — recalcule via calcSolaire à chaque frappe ────
  // Zone climatique par défaut H2 (on l'affine au lancement via BAN→rooftop).
  const SD_modal = window.SOLAR_DATA;
  const panneau = (SD_modal && SD_modal.PANNEAUX_DB[panneauKey]) || { wc: 440, b: '—', m: '—' };
  const preview = React.useMemo(() => {
    if (!SD_modal) return null;
    const orient = aspect === 0 ? 'S' : aspect > 0 && aspect < 90 ? 'SO' : aspect < 0 && aspect > -90 ? 'SE' : aspect >= 90 ? 'O' : 'E';
    const wc = panneau.wc || 440;
    const nb = Math.max(1, Math.round((kwc * 1000) / wc));
    return SD_modal.calcSolaire({
      type: 'toiture_ter',
      zone: 'H2',
      orient,
      inclin: String(tilt),
      panneauKey,
      wc,
      nb,
      pertes: loss,
      conso,
      tachat,
      trevente,
      cout: Math.max(1, cout),
      myLight,
      battKwh,
    });
  }, [kwc, tilt, aspect, loss, panneauKey, conso, tachat, trevente, cout, myLight, battKwh]);
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(14,16,16,0.6)',
      display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)',
      padding: 20,
    }} onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div style={{
        width: 'min(560px, 100%)',
        maxHeight: '90vh', overflowY: 'auto',
        background: 'var(--paper)', borderRadius: 12, boxShadow: 'var(--shadow-3)',
        border: '1px solid var(--line-2)', overflow: 'hidden auto',
        boxSizing: 'border-box',
      }}>
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10, position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 1 }}>
          <Dot tone="signal" size={8} pulse />
          <div style={{ fontSize: 14, fontWeight: 600 }}>Nouveau projet solaire</div>
          <span style={{ flex: 1 }} />
          <button onClick={onClose} style={{ color: 'var(--ink-4)' }}><Icon.cross /></button>
        </div>
        {step === 'form' && (
          <div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 14, boxSizing: 'border-box' }}>
            <div style={{ fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.5 }}>
              La chaîne <b>BAN → cadastre IGN → PVGIS</b> crée la visite technique liée.
            </div>

            {/* A — Client : ClientPicker unifié (Règle 3.a) */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>
              Client <span style={{ color: 'var(--ink-5)', fontWeight: 500 }}>· 101 Odoo + AE contacts</span>
            </div>
            <label style={fieldStyle}>
              <span style={labelStyle}>Rechercher un client existant (Odoo + AE) ou créer</span>
              {window.ClientPicker && <window.ClientPicker value={picked} onChange={onPickClient} placeholder="Taper nom / email / SIRET…" />}
            </label>

            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>Détails {picked && <span style={{ color: 'var(--signal-deep)', fontWeight: 500 }}>· prérempli depuis {picked.source === 'odoo' ? 'Odoo' : picked.source === 'ae_org' ? 'Orga AE' : 'AE'}</span>}</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <label style={fieldStyle}>
                <span style={labelStyle}>Nom</span>
                <input value={clientNom} onChange={e => setClientNom(e.target.value)} placeholder="Dupont" style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Prénom</span>
                <input value={clientPrenom} onChange={e => setClientPrenom(e.target.value)} placeholder="Jean" style={inputStyle} />
              </label>
              <label style={{ ...fieldStyle, gridColumn: '1 / -1' }}>
                <span style={labelStyle}>Société (optionnel)</span>
                <input value={clientSociete} onChange={e => setClientSociete(e.target.value)} placeholder="SARL Exemple" style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Email</span>
                <input type="email" value={clientEmail} onChange={e => setClientEmail(e.target.value)} placeholder="client@exemple.fr" style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Téléphone</span>
                <input value={clientTel} onChange={e => setClientTel(e.target.value)} placeholder="06 12 34 56 78" style={inputStyle} />
              </label>
            </div>

            {/* Adresse chantier */}
            <label style={fieldStyle}>
              <span style={labelStyle}>Adresse chantier</span>
              <input
                value={address}
                onChange={e => setAddress(e.target.value)}
                placeholder="15 avenue des Champs-Élysées, 75008 Paris"
                style={{ ...inputStyle, padding: '9px 12px' }}
                onKeyDown={e => { if (e.key === 'Enter' && address.trim()) launch(); }}
              />
            </label>

            {/* Dimensionnement */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>Dimensionnement initial</div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10 }}>
              <label style={fieldStyle}>
                <span style={labelStyle}>kWc</span>
                <input type="number" value={kwc} onChange={e => setKwc(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Inclin.°</span>
                <input type="number" value={tilt} onChange={e => setTilt(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle} title="Azimut°, 0=Sud">Azimut° (0=S)</span>
                <input type="number" value={aspect} onChange={e => setAspect(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Pertes %</span>
                <input type="number" value={loss} onChange={e => setLoss(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
            </div>

            {/* C — Panneaux */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>Panneaux photovoltaïques</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 10 }}>
              <label style={fieldStyle}>
                <span style={labelStyle}>Modèle (catalogue 45)</span>
                <select value={panneauKey} onChange={e => setPanneauKey(e.target.value)} style={{ ...inputStyle, padding: '7px 8px' }}>
                  {SD_modal && SD_modal.PANNEAUX_GROUPES.map(g => (
                    <optgroup key={g.label} label={g.label}>
                      {g.keys.map(k => {
                        const p = SD_modal.PANNEAUX_DB[k];
                        return <option key={k} value={k}>{p ? `${p.b} — ${p.m} (${p.wc} Wc, ${p.rdt}%)` : k}</option>;
                      })}
                    </optgroup>
                  ))}
                </select>
              </label>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10 }}>
                <div style={fieldStyle}>
                  <span style={labelStyle}>Puissance unit.</span>
                  <div className="mono" style={{ fontSize: 12, padding: '8px 10px', background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5, color: 'var(--ink-2)' }}>{panneau.wc} Wc</div>
                </div>
                <div style={fieldStyle}>
                  <span style={labelStyle}>Rendement</span>
                  <div className="mono" style={{ fontSize: 12, padding: '8px 10px', background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5, color: 'var(--ink-2)' }}>{panneau.rdt} %</div>
                </div>
                <div style={fieldStyle}>
                  <span style={labelStyle}>Prix unitaire</span>
                  <div className="mono" style={{ fontSize: 12, padding: '8px 10px', background: 'var(--paper-2)', border: '1px solid var(--hairline)', borderRadius: 5, color: 'var(--ink-2)' }}>{panneau.prix} €</div>
                </div>
              </div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', fontStyle: 'italic' }}>
                {(() => {
                  const nb = Math.max(1, Math.round((kwc * 1000) / (panneau.wc || 440)));
                  const surf = Math.round(nb * (panneau.wc <= 430 ? 1.7 : 1.9));
                  return `Avec ${kwc} kWc et panneaux de ${panneau.wc} Wc → ${nb} panneaux, ~${surf} m² · garantie ${panneau.gar} ans`;
                })()}
              </div>
            </div>

            {/* D — Consommation & tarifs */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>Consommation & tarifs</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
              <label style={fieldStyle}>
                <span style={labelStyle}>Conso annuelle (kWh)</span>
                <input type="number" value={conso} onChange={e => setConso(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Coût installation (€)</span>
                <input type="number" value={cout} onChange={e => setCout(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Tarif achat (€/kWh)</span>
                <input type="number" step="0.001" value={tachat} onChange={e => setTachat(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
              <label style={fieldStyle}>
                <span style={labelStyle}>Tarif revente (€/kWh)</span>
                <input type="number" step="0.001" value={trevente} onChange={e => setTrevente(Number(e.target.value) || 0)} style={inputStyle} />
              </label>
            </div>

            {/* E — Batterie (MyLight ou autre constructeur) */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 4, borderBottom: '1px solid var(--hairline)', paddingBottom: 4 }}>Batterie (stockage énergétique)</div>
            <label style={{ ...fieldStyle, flexDirection: 'row', alignItems: 'center', gap: 8 }}>
              <input type="checkbox" checked={myLight} onChange={e => setMyLight(e.target.checked)} />
              <span style={{ fontSize: 12 }}>{myLight ? 'Activée' : 'Aucune batterie'}</span>
            </label>
            {myLight && SD_modal && SD_modal.BATTERIES_DB && (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <label style={fieldStyle}>
                  <span style={labelStyle}>Constructeur / modèle</span>
                  <select value={battMarque} onChange={e => {
                    const k = e.target.value;
                    setBattMarque(k);
                    const b = SD_modal.BATTERIES_DB[k];
                    if (b && b.capacites[0]) setBattKwh(b.capacites[0].kwh);
                  }} style={inputStyle}>
                    {Object.entries(SD_modal.BATTERIES_DB).map(([k, b]) => (
                      <option key={k} value={k}>{b.marque} — {b.label.replace(b.marque, '').replace(' — ', '').trim() || b.label}</option>
                    ))}
                  </select>
                </label>
                <label style={fieldStyle}>
                  <span style={labelStyle}>Capacité constructeur</span>
                  <select value={battKwh} onChange={e => setBattKwh(Number(e.target.value))} style={inputStyle}>
                    {(SD_modal.BATTERIES_DB[battMarque]?.capacites || []).map(c => (
                      <option key={c.kwh} value={c.kwh}>{c.label}</option>
                    ))}
                  </select>
                </label>
                <div style={{ gridColumn: '1/-1', fontSize: 10, color: 'var(--ink-4)', fontStyle: 'italic' }}>
                  {SD_modal.BATTERIES_DB[battMarque]?.desc}
                </div>
              </div>
            )}

            {/* Mini preview dynamique — recalcul à chaque frappe */}
            {preview && (
              <div style={{
                padding: 14, borderRadius: 8,
                background: 'linear-gradient(135deg, rgba(63,224,124,0.10), rgba(14,16,16,0.02))',
                border: '1px solid var(--signal-soft)',
                position: 'relative',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
                  <Dot tone="signal" size={6} pulse />
                  <span style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 700 }}>
                    Preview estimative (zone H2 par défaut)
                  </span>
                  <span style={{ flex: 1 }} />
                  <span className="mono" style={{ fontSize: 9, color: 'var(--ink-4)' }}>live</span>
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10 }}>
                  <PreviewKpi label="Puissance" value={preview.puissKwc} unit="kWc" accent />
                  <PreviewKpi label="Production" value={Math.round(preview.prod / 1000)} unit="MWh/an" accent />
                  <PreviewKpi label="Surface" value={preview.surface} unit="m²" />
                  <PreviewKpi label="ROI estimé" value={preview.roi} unit="ans" accent />
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10, marginTop: 10, paddingTop: 10, borderTop: '1px solid var(--signal-soft)' }}>
                  <PreviewKpi label="Panneaux" value={Math.max(1, Math.round((kwc * 1000) / 440))} unit="× 440 Wc" small />
                  <PreviewKpi label="Autoconso" value={preview.tauxAutocons} unit="%" small />
                  <PreviewKpi label="Économie" value={preview.ecoAvec.toLocaleString('fr-FR')} unit="€/an" small accent />
                  {myLight
                    ? <PreviewKpi label="Gain MyLight" value={'+' + preview.gainMyLight.toLocaleString('fr-FR')} unit="€/an" small accent />
                    : <PreviewKpi label="Revente" value={preview.revSans.toLocaleString('fr-FR')} unit="€/an" small />
                  }
                </div>
                <div style={{ fontSize: 9, color: 'var(--ink-4)', fontStyle: 'italic', marginTop: 8, lineHeight: 1.4 }}>
                  Hypothèses : panneau Longi Hi-MO 6 Explorer 440 Wc, tarif EDF 0,2276 €/kWh, conso 85 000 kWh/an.
                  Les valeurs réelles seront recalculées via PVGIS au lancement.
                </div>
              </div>
            )}

            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 6 }}>
              <button onClick={onClose} style={{ padding: '8px 14px', fontSize: 12, color: 'var(--ink-3)' }}>Annuler</button>
              <button
                onClick={launch}
                disabled={!address.trim()}
                style={{
                  padding: '8px 14px', fontSize: 12, fontWeight: 600,
                  background: address.trim() ? 'var(--signal)' : 'var(--paper-3)',
                  color: address.trim() ? 'var(--ink)' : 'var(--ink-4)',
                  border: 'none', borderRadius: 5,
                  cursor: address.trim() ? 'pointer' : 'not-allowed',
                }}
              >Lancer l'analyse</button>
            </div>
          </div>
        )}
        {step === 'processing' && (
          <div style={{ padding: 20 }}>
            <div style={{ fontSize: 12, color: 'var(--ink-4)', marginBottom: 14 }}>Chaîne backend en cours…</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {progress.map((p, i) => (
                <div key={p.k} style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 12 }}>
                  <span style={{ width: 10, height: 10, borderRadius: '50%', background: p.status === 'done' ? 'var(--signal-deep)' : 'var(--amber)' }} />
                  <span style={{ flex: 1, color: p.status === 'done' ? 'var(--ink-2)' : 'var(--ink-3)' }}>{p.label}</span>
                  {p.status === 'done' && <span style={{ color: 'var(--signal-deep)' }}>✓</span>}
                </div>
              ))}
            </div>
          </div>
        )}
        {step === 'error' && (
          <div style={{ padding: 20 }}>
            <div style={{ fontSize: 12, color: 'var(--rouge)', marginBottom: 10, fontWeight: 600 }}>Erreur</div>
            <div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 14 }}>{error}</div>
            <button onClick={() => { setStep('form'); setError(null); setProgress([]); }} style={{ padding: '8px 14px', fontSize: 12, background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 5 }}>Réessayer</button>
          </div>
        )}
      </div>
    </div>
  );
}

// Mini KPI dans la preview du modal
function PreviewKpi({ label, value, unit, accent, small }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0 }}>
      <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 3 }}>
        <span className="mono" style={{
          fontSize: small ? 13 : 17, fontWeight: 600, letterSpacing: '-0.02em',
          color: accent ? 'var(--signal-deep)' : 'var(--ink)',
          transition: 'color 120ms',
        }}>{value}</span>
        <span style={{ fontSize: 9, color: 'var(--ink-4)' }}>{unit}</span>
      </div>
    </div>
  );
}

Object.assign(window, { SolarStudio, NewSolarProjectModal });
