// gp-engine.jsx — generic spec-driven estimator
// Renders ANY module from its spec (gp-registry): pulls linked plan
// measures, allows manual rows, applies recipes, computes quantities.
// ───────────────────────────────────────────────────────────────
const {
  Icon: EIcon, ModuleHeader: EHead, Steps: ESteps, SummaryAside: EAside,
  recipesForModule: eRcpFor, bomForRecipe: eBom, fmt: eFmt, moduleById: eModById,
  systemsForModule: eSystems
} = window;

const KIND_LABEL = { surface: 'surface', ligne: 'linéaire', ouvert: 'ouverture' };
const DRIVER_UNIT = { area: 'pi²', perim: 'pi lin.', units: 'u' };

// moduleConfig : { recipe, brand, excl, buffer, fields, rows }
// onPatchConfig : (changes) => void — écrit dans byProject[pid].moduleConfig[mid]
function EstimatorModule({ mod, spec, unit, onMesure, measures = [], moduleConfig = {}, onPatchConfig,
  recipeChoices, onChooseRecipe, onToggleExclude, onApplySystem, systems: systemsProp }) {

  const [step, setStep] = React.useState(1);
  const hasBuffer = !spec.isCalc && !spec.noBuffer;
  const recipes = eRcpFor(spec.id);

  // ── État persisté : lu depuis moduleConfig, écrit via onPatchConfig ──
  const rows   = moduleConfig.rows   ?? [];
  const buffer = moduleConfig.buffer ?? spec.buffer ?? 8;
  const fields = moduleConfig.fields ?? {};   // { [key]: value }

  const setRows   = (updater) => onPatchConfig && onPatchConfig({ rows:   typeof updater === 'function' ? updater(rows)   : updater });
  const setBuffer = (val)     => onPatchConfig && onPatchConfig({ buffer: val });
  const setFieldVal = (key, val) => onPatchConfig && onPatchConfig({ fields: { ...fields, [key]: val } });

  // recette active : vient du store (via recipeChoices dérivé de moduleConfig)
  const choice   = recipeChoices && recipeChoices[spec.id];
  const recipeId = choice?.recipe || recipes[0]?.id || null;
  const brand    = choice?.brand  || recipes[0]?.brand || null;

  const chooseRecipe = (rid, br) => onChooseRecipe && onChooseRecipe(spec.id, rid, br);

  const systems    = eSystems ? eSystems(spec.id, systemsProp) : [];
  const activeSys  = systems.find((s) => s.picks[spec.id] && s.picks[spec.id].recipe === recipeId && s.picks[spec.id].brand === brand) || null;
  const applySystem = (s) => { const p = s.picks[spec.id]; if (p) chooseRecipe(p.recipe, p.brand); setSysOpen(false); };

  const systemCells = () => systems.map((s) => {
    const on = activeSys && activeSys.id === s.id;
    const nMods = Object.keys(s.picks).length;
    return (
      <button key={s.id} className={'syscell' + (on ? ' on' : '')} style={{ '--sys': s.accent }} onClick={() => applySystem(s)}>
        <span className="sysmono">{s.mono}</span>
        <span className="systx">
          <span className="sysname">{s.name}</span>
          <span className="systag">{s.tagline}</span>
        </span>
        {on ? <span className="sysok"><EIcon n="check" style={{ width: 13, height: 13 }} /></span> :
          nMods > 1 ? <span className="sysmulti">{nMods} modules</span> : null}
      </button>
    );
  });

  const [sysOpen, setSysOpen] = React.useState(false);
  const resetWidget = () => {
    onPatchConfig && onPatchConfig({ rows: [], buffer: spec.buffer ?? 8, fields: {} });
    setSysOpen(true);
    setStep(1);
  };

  const linked = measures.filter((m) => m.target === spec.id);
  const drivers = window.computeDrivers(linked, rows, spec.rowMode);
  const recipe = recipes.find((r) => r.id === recipeId) || null;

  const excl   = (choice && choice.excl) || {};
  const bomRaw = recipe ? eBom(recipe, drivers) : [];
  const bomAll = bomRaw.map((b) => ({ ...b, qty: Math.ceil(b.qty * (1 + (hasBuffer ? buffer : 0) / 100)), off: !!excl[b.sku] }));
  const bom    = bomAll.filter((b) => !b.off);
  const toggleLine = (sku) => onToggleExclude && onToggleExclude(spec.id, sku);

  const heroQty     = bom[0]?.qty ?? null;
  const heroUnitLbl = bom[0]?.unit ? bom[0].unit + 's' : spec.unitOut;
  const primaryVal  = drivers[spec.primaryDriver] || 0;
  const primaryUnit = DRIVER_UNIT[spec.primaryDriver];

  const setRow = (id, patch) => setRows((rs) => rs.map((r) => r.id === id ? { ...r, ...patch } : r));
  const delRow = (id) => setRows((rs) => rs.filter((r) => r.id !== id));
  const addRow = () => setRows((rs) => [...rs, { id: 'r' + Date.now(), ident: 'Nouvelle entrée', qte: 1, l: 0, h: 0 }]);

  const steps = spec.isCalc ?
    ['Paramètres', 'Bilan thermique'] :
    ['Paramètres', 'Relevé & plan', 'Recettes', 'Quantités'];

  // ── aside ──
  const aside = spec.isCalc ?
    <EAside
      heroLabel="Charge de design" heroVal={eFmt(Math.round((Number(String(spec.fields[0].val).replace(/\s/g, '')) || 20000) * 1.6))} heroUnit="BTU/h"
      heroRows={[{ k: 'Chauffage', v: eFmt(28800) + ' BTU/h' }, { k: 'Climatisation', v: eFmt(18000) + ' BTU/h' }]}
      title="Hypothèses" rows={[
        { n: 'Pertes enveloppe', s: 'transmission', v: '19,4', u: 'kW' },
        { n: 'Infiltration', s: 'ACH₅₀ 0.6', v: '2,1', u: 'kW' },
        { n: 'Ventilation', s: 'VRC 82%', v: '1,3', u: 'kW' }]}
      buffer={null}
      accentNote={<>Calcul simplifié type <b>CSA F280</b> — sert au dimensionnement de la thermopompe.</>} /> :

    <EAside
      heroLabel={`${recipe ? recipe.name : mod.mat || 'Matériau'} requis`}
      heroVal={heroQty != null ? eFmt(heroQty) : '—'} heroUnit={heroQty != null ? heroUnitLbl : spec.unitOut}
      heroRows={[
        { k: spec.primaryDriver === 'perim' ? 'Longueur' : spec.primaryDriver === 'units' ? 'Unités' : 'Surface',
          v: primaryVal ? `${eFmt(primaryVal, primaryUnit === 'pi lin.' ? 1 : 0)} ${primaryUnit}` : '—' },
        { k: 'Formes du plan', v: String(linked.length) }]}
      title="Composants estimés"
      rows={bom.map((b) => ({ n: b.item, s: b.note, v: String(eFmt(b.qty)), u: b.unit }))}
      buffer={hasBuffer ? buffer : null} onBuffer={setBuffer}
      accentNote={recipe ? <>Recette active : <b>{recipe.name}</b> ({brand}). Modifiable dans <b>Recettes & ratios</b>.</> :
        <>Aucune recette pour ce module. Définissez-en une dans <b>Recettes & ratios</b>.</>} />;

  return (
    <div className="fade-in">
      <EHead mod={mod} status={mod.status}>
        {!spec.isCalc && activeSys &&
          <span className="syschip" style={{ '--sys': activeSys.accent }} title={`Système actif : ${activeSys.name}`}>
            <span className="syschip-mono">{activeSys.mono}</span> {activeSys.name}
          </span>}
        {onMesure && <button className="btn" onClick={onMesure}><EIcon n="ruler" /> Mesure sur plan</button>}
        {!spec.isCalc && systems.length > 0 &&
          <button className={'btn' + (sysOpen ? ' accent' : '')} onClick={() => sysOpen ? setSysOpen(false) : resetWidget()} title="Réinitialiser le widget et choisir un système">
            <EIcon n="sync" /> Réinitialiser
          </button>}
        <button className="btn"><EIcon n="copy" /> Dupliquer config</button>
      </EHead>
      <ESteps steps={steps} active={step} onChange={setStep} />

      {!spec.isCalc && systems.length > 0 && sysOpen &&
        <div className="syscard">
          <div className="syscard-h">
            <div>
              <div className="syscard-t"><EIcon n="box" style={{ width: 15, height: 15 }} /> Choisir un système de produits</div>
              <div className="syscard-s">Appliquez une approche fabricant complète en un clic, puis raffinez dans <b>Recettes</b>.</div>
            </div>
            <button className="syscard-x" onClick={() => setSysOpen(false)} title="Fermer">×</button>
          </div>
          <div className="sysgrid">
            {systemCells()}
            <button className="syscell syscustom" onClick={() => setStep(2)}>
              <span className="sysmono ghost"><EIcon n="beaker" style={{ width: 15, height: 15 }} /></span>
              <span className="systx">
                <span className="sysname">Hybride / personnalisé</span>
                <span className="systag">Composer dans Recettes &amp; ratios</span>
              </span>
            </button>
          </div>
          {activeSys && Object.keys(activeSys.picks).length > 1 && onApplySystem &&
            <button className="sysall" onClick={() => onApplySystem(activeSys)}>
              <EIcon n="layers" style={{ width: 14, height: 14 }} /> Appliquer « {activeSys.name} » aux {Object.keys(activeSys.picks).length} modules concernés
            </button>}
        </div>}

      <div className="grid-2">
        <div className="col">

          {/* STEP 0 — paramètres */}
          {step === 0 &&
            <div className="card">
              <div className="card-h"><div className="card-h-l"><span className="badge-n">1</span><span className="card-t">Paramètres par défaut</span></div>
                <span className="card-hint">repris de la Configuration globale</span></div>
              <div className="fgrid">
                {spec.fields.map((f) =>
                  <div className="fcell" key={f.key}>
                    <label className="lbl">{f.label}</label>
                    {f.type === 'select' ?
                      <select className="field"
                        value={fields[f.key] !== undefined ? fields[f.key] : (f.opts[0] || '')}
                        onChange={(e) => setFieldVal(f.key, e.target.value)}>
                        {f.opts.map((o) => <option key={o}>{o}</option>)}
                      </select> :
                      <input className={'field' + (f.type === 'mono' ? ' mono' : '')}
                        value={fields[f.key] !== undefined ? fields[f.key] : (f.val || '')}
                        onChange={(e) => setFieldVal(f.key, e.target.value)} />}
                  </div>
                )}
                {hasBuffer &&
                  <div className="fcell full"><label className="lbl">Surplus / pertes ({buffer}%)</label>
                    <input type="range" className="ctl-slider" min="0" max="25" value={buffer} onChange={(e) => setBuffer(Number(e.target.value))} /></div>
                }
              </div>
              <div className="note" style={{ marginTop: 14 }}>
                <b>Module piloté par recette.</b> Ce module consomme {spec.consumes.length ? spec.consumes.map((k) => KIND_LABEL[k]).join(' + ') + ' du plan' : 'des paramètres saisis'} ; les quantités proviennent des recettes associées.
              </div>
            </div>
          }

          {/* STEP 1 — relevé & plan */}
          {step === 1 && !spec.isCalc &&
            <div className="card">
              <div className="card-h"><div className="card-h-l"><span className="badge-n">2</span><span className="card-t">Relevé & mesures du plan</span></div>
                {linked.length > 0 && <span className="tag plan"><EIcon n="link" />{linked.length} liées</span>}</div>

              {linked.length > 0 || rows.length > 0 ?
                <div className="tbl-wrap">
                  <table>
                    <thead><tr>
                      <th>Entrée</th><th style={{ width: 90 }}>Source</th>
                      {spec.rowMode === 'surface' && <th style={{ width: 64 }} className="num">Qté</th>}
                      {spec.rowMode === 'surface' && <th>L × H <span className="u">pi</span></th>}
                      {spec.rowMode === 'linear' && <th style={{ width: 64 }} className="num">Qté</th>}
                      {spec.rowMode === 'linear' && <th>Long. <span className="u">pi</span></th>}
                      {spec.rowMode === 'count' && <th style={{ width: 64 }} className="num">Qté</th>}
                      <th className="num">Valeur</th><th style={{ width: 36 }}></th>
                    </tr></thead>
                    <tbody>
                      {linked.map((m) =>
                        <tr key={m.id}>
                          <td style={{ fontWeight: 500 }}>{m.name} <span className="tag plan"><EIcon n="ruler" />plan</span></td>
                          <td className="muted">p. {m.page}</td>
                          {spec.rowMode === 'surface' && <><td className="cell-ro" style={{ textAlign: 'left' }}>—</td><td className="cell-ro" style={{ textAlign: 'left' }}>relevé</td></>}
                          {spec.rowMode === 'linear' && <><td className="cell-ro" style={{ textAlign: 'left' }}>—</td><td className="cell-ro" style={{ textAlign: 'left' }}>relevé</td></>}
                          {spec.rowMode === 'count' && <td className="cell-ro" style={{ textAlign: 'left' }}>—</td>}
                          <td className="cell-ro">{eFmt(m.val, m.kind === 'ligne' ? 1 : 0)} {m.unit}</td>
                          <td></td>
                        </tr>
                      )}
                      {rows.map((r) =>
                        <tr key={r.id}>
                          <td><input className="cell-in" value={r.ident} onChange={(e) => setRow(r.id, { ident: e.target.value })} style={{ fontStyle: 'italic', minWidth: 108 }} /></td>
                          <td className="muted">manuel</td>
                          {(spec.rowMode === 'surface' || spec.rowMode === 'linear' || spec.rowMode === 'count') &&
                            <td className="num"><input className="cell-in num" value={r.qte} onChange={(e) => setRow(r.id, { qte: Number(e.target.value) || 0 })} /></td>}
                          {spec.rowMode === 'surface' && <td><span className="dim">
                            <input className="cell-in num" value={r.l} onChange={(e) => setRow(r.id, { l: Number(e.target.value) || 0 })} style={{ width: 48 }} /><span className="su">×</span>
                            <input className="cell-in num" value={r.h} onChange={(e) => setRow(r.id, { h: Number(e.target.value) || 0 })} style={{ width: 48 }} /></span></td>}
                          {spec.rowMode === 'linear' && <td><input className="cell-in num" value={r.l} onChange={(e) => setRow(r.id, { l: Number(e.target.value) || 0 })} style={{ width: 60 }} /></td>}
                          <td className="cell-ro">{spec.rowMode === 'surface' ? eFmt((Number(r.qte) || 0) * (Number(r.l) || 0) * (Number(r.h) || 0)) + ' pi²' :
                            spec.rowMode === 'linear' ? eFmt((Number(r.qte) || 0) * (Number(r.l) || 0)) + ' pi' :
                            eFmt(Number(r.qte) || 0) + ' u'}</td>
                          <td><button className="row-x" onClick={() => delRow(r.id)}><EIcon n="trash" /></button></td>
                        </tr>
                      )}
                    </tbody>
                    <tfoot><tr className="tfoot">
                      <td colSpan={spec.rowMode === 'count' ? 3 : 4}>Total — {spec.primaryDriver === 'perim' ? 'longueur' : spec.primaryDriver === 'units' ? 'unités' : 'surface'}</td>
                      <td className="cell-ro">{eFmt(primaryVal, primaryUnit === 'pi lin.' ? 1 : 0)} {primaryUnit}</td><td></td>
                    </tr></tfoot>
                  </table>
                </div> :

                <div className="empty tickbox">
                  <EIcon n={mod.icon} />
                  <div className="empty-t">Aucune mesure pour l'instant</div>
                  <div className="empty-s">{spec.hint}</div>
                  {onMesure && <button className="btn accent" style={{ margin: '14px auto 0' }} onClick={onMesure}><EIcon n="ruler" /> Ouvrir Mesure sur plan</button>}
                </div>
              }
              {spec.rowMode !== 'none' && <button className="addrow" onClick={addRow}><EIcon n="plus" /> Ajouter une ligne manuelle</button>}
            </div>
          }

          {/* STEP 2 — recettes */}
          {step === 2 && !spec.isCalc &&
            <div className="card">
              <div className="card-h"><div className="card-h-l"><span className="badge-n">3</span><span className="card-t">Recette appliquée</span></div>
                <span className="card-hint">{recipes.length} disponible{recipes.length > 1 ? 's' : ''}</span></div>

              {systems.length > 0 &&
                <div className="sysinline">
                  <div className="sysinline-h">
                    <span className="card-t" style={{ fontSize: 13 }}><EIcon n="box" style={{ width: 14, height: 14 }} /> Système de produits</span>
                    {activeSys ?
                      <span className="sysbadge" style={{ '--sys': activeSys.accent }}><span className="d" /> {activeSys.name} actif</span> :
                      <span className="sysbadge custom"><span className="d" /> Personnalisé (hybride)</span>}
                  </div>
                  <div className="sysgrid">{systemCells()}</div>
                  {activeSys && Object.keys(activeSys.picks).length > 1 && onApplySystem &&
                    <button className="sysall" onClick={() => onApplySystem(activeSys)}>
                      <EIcon n="layers" style={{ width: 14, height: 14 }} /> Appliquer « {activeSys.name} » aux {Object.keys(activeSys.picks).length} modules concernés
                    </button>}
                </div>}

              {recipes.length ?
                <>
                  <div className="wrap-flex" style={{ marginBottom: 6 }}>
                    <div className="fcell"><label className="lbl">Composition</label>
                      <select className="field" value={recipeId} style={{ minWidth: 220 }}
                        onChange={(e) => { const rc = recipes.find((r) => r.id === e.target.value); chooseRecipe(e.target.value, rc.brand); }}>
                        {recipes.map((r) => <option key={r.id} value={r.id}>{r.name}</option>)}
                      </select></div>
                    <div className="fcell"><label className="lbl">Marque</label>
                      <select className="field" value={brand} style={{ minWidth: 160 }} onChange={(e) => chooseRecipe(recipeId, e.target.value)}>
                        {(recipe ? recipe.brands : []).map((b) => <option key={b}>{b}</option>)}
                      </select></div>
                  </div>
                  <div className="rcp-compo-h">Composants — décochez ceux à exclure de cette soumission</div>
                  <div className="tbl-wrap" style={{ marginTop: 8 }}>
                    <table>
                      <thead><tr><th style={{ width: 34 }}></th><th>Composant</th><th style={{ width: 90 }}>Code</th><th>Ratio</th><th className="num">Qté</th><th style={{ width: 80 }}>Unité</th></tr></thead>
                      <tbody>
                        {bomAll.map((b, i) =>
                          <tr key={i} className={b.off ? 'row-off' : ''}>
                            <td><button className={'line-ck' + (b.off ? '' : ' on')} onClick={() => toggleLine(b.sku)} title={b.off ? 'Inclure' : 'Exclure'}>{!b.off && <EIcon n="check" style={{ width: 11, height: 11 }} />}</button></td>
                            <td style={{ fontWeight: 500 }}>{b.item}</td>
                            <td className="cell-ro" style={{ textAlign: 'left' }}>{b.sku}</td>
                            <td className="muted" style={{ fontSize: 12 }}>{b.note}</td>
                            <td className="cell-ro">{b.off ? '—' : eFmt(b.qty)}</td><td className="muted">{b.unit}</td>
                          </tr>
                        )}
                      </tbody>
                    </table>
                  </div>
                </> :

                <div className="empty tickbox"><EIcon n="beaker" />
                  <div className="empty-t">Aucune recette définie</div>
                  <div className="empty-s">Créez une recette avec <b>module : {spec.id}</b> dans l'outil <b>Recettes & ratios</b> ; ses ratios alimenteront automatiquement ce module et la soumission.</div>
                </div>
              }
            </div>
          }

          {/* STEP 3 — quantités */}
          {step === 3 && !spec.isCalc &&
            <div className="card">
              <div className="card-h"><div className="card-h-l"><span className="badge-n">4</span><span className="card-t">Quantités & surplus</span></div></div>
              <div className="fgrid" style={{ gridTemplateColumns: 'repeat(auto-fill,minmax(150px,1fr))' }}>
                {bom.map((b, i) =>
                  <div className="card" key={i} style={{ boxShadow: 'none', background: 'var(--surface-2)' }}>
                    <div className="lbl">{b.item}</div>
                    <div className="kpi-v" style={{ fontSize: 24 }}>{eFmt(b.qty)}</div>
                    <div className="kpi-s">{b.unit}{hasBuffer ? ` · surplus ${buffer}%` : ''}</div>
                  </div>
                )}
                {bom.length === 0 && <div className="muted">Définissez une recette pour obtenir des quantités.</div>}
              </div>
              {bom.length > 0 && <div className="note" style={{ marginTop: 14 }}>Calculé sur <b>{eFmt(primaryVal, primaryUnit === 'pi lin.' ? 1 : 0)} {primaryUnit}</b> ({linked.length} forme{linked.length > 1 ? 's' : ''} du plan + {rows.length} ligne{rows.length > 1 ? 's' : ''} manuelle{rows.length > 1 ? 's' : ''}){hasBuffer ? <>, surplus de <b>{buffer}%</b> inclus</> : ''}. Ces quantités alimentent la <b>Soumission</b>.</div>}
            </div>
          }

          {/* CALC modules (charge) */}
          {step === 1 && spec.isCalc &&
            <div className="card">
              <div className="card-h"><div className="card-h-l"><span className="badge-n">2</span><span className="card-t">Bilan thermique</span></div></div>
              <div className="tbl-wrap">
                <table>
                  <thead><tr><th>Poste de déperdition</th><th className="num">U / valeur</th><th className="num">Charge</th></tr></thead>
                  <tbody>
                    {[['Murs hors sol', 'R-40', '4 850 BTU/h'], ['Toiture', 'R-60', '2 410 BTU/h'], ['Sous-dalle', 'R-20', '1 980 BTU/h'],
                      ['Fenestration', 'U 1.10', '6 240 BTU/h'], ['Infiltration', '0.6 ACH₅₀', '3 100 BTU/h'], ['Ventilation', 'VRC 82%', '1 980 BTU/h']].map((r, i) =>
                      <tr key={i}><td style={{ fontWeight: 500 }}>{r[0]}</td><td className="cell-ro">{r[1]}</td><td className="cell-ro">{r[2]}</td></tr>
                    )}
                  </tbody>
                  <tfoot><tr className="tfoot"><td colSpan="2">Charge de chauffage de design</td><td className="cell-ro">28 800 BTU/h</td></tr></tfoot>
                </table>
              </div>
              <div className="note" style={{ marginTop: 14 }}>Méthode simplifiée (type <b>CSA F280-12</b>). Sert à dimensionner la thermopompe et la ventilation — paramètres repris de la <b>Configuration</b>.</div>
            </div>
          }

        </div>
        <aside className="aside">{aside}</aside>
      </div>
    </div>
  );
}

Object.assign(window, { EstimatorModule });
