/* Wisp calculator UI — v2 elevated layout.
 *
 * Structurally identical to the original: same hooks, same data flow, same
 * localStorage keys. The visual layer is redone to match the Codex page:
 *
 *   · .page-hero      — eyebrow + serif title + tags + hero aside card
 *   · .metric-strip   — 4 hero metric cells
 *   · .modebar-v2     — labelled toggle stacks
 *   · .framed-section — bracketed panels for table + overrides
 *   · .chain-flow     — visual chain diagram inside expanded chain breakdowns
 */

const { useState: useStateWU, useMemo: useMemoWU, useEffect: useEffectW } = React;

const WISP_OVERRIDES_KEY = 'craft.wispPriceOverrides.v1';
function loadWispOverrides() {
  try { return JSON.parse(localStorage.getItem(WISP_OVERRIDES_KEY)) || {}; }
  catch (e) { return {}; }
}

const WISP_PROFITABLE_ONLY_KEY = 'craft.wispProfitableOnly.v1';
function loadProfitableOnly() {
  try { return localStorage.getItem(WISP_PROFITABLE_ONLY_KEY) === '1'; }
  catch (e) { return false; }
}

// Glyph used in each chain node — picks the right icon for the step kind.
function ChainStepGlyph({ kind }) {
  if (kind === 'wisp-craft') return '✦';
  if (kind === 'reroll')     return '⚄';
  if (kind === 'upgrade')    return '▲';
  if (kind === 'salvage')    return '⚒';
  return '·';
}

// Arrow between chain nodes
function ChainArrow() {
  return (
    <div className="chain-arrow" aria-hidden>
      <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" strokeWidth="1.5">
        <line x1="2" y1="6" x2="20" y2="6" />
        <polyline points="16,2 20,6 16,10" />
      </svg>
    </div>
  );
}

function WispPanel({ ctx, laborValue, currency }) {
  const [mode, setMode]   = useStateWU('armor');
  const [armorType,  setArmorType]  = useStateWU('Cloth');
  const [weaponType, setWeaponType] = useStateWU('Wooden');
  const [expanded,   setExpanded]   = useStateWU(null);
  const [overrides, setOverrides]   = useStateWU(loadWispOverrides);
  const [profitableOnly, setProfitableOnly] = useStateWU(loadProfitableOnly);
  const [overridesOpen,  setOverridesOpen]  = useStateWU(false);
  useEffectW(() => { try { localStorage.setItem(WISP_OVERRIDES_KEY, JSON.stringify(overrides)); } catch (e) {} }, [overrides]);
  useEffectW(() => { try { localStorage.setItem(WISP_PROFITABLE_ONLY_KEY, profitableOnly ? '1' : '0'); } catch (e) {} }, [profitableOnly]);

  const sealingIndex   = useMemoWU(() => window.buildSealingIndex(ctx.recipes), [ctx.recipes]);
  const upgradeRecipes = useMemoWU(() => window.findUpgradeRecipes(ctx.recipes), [ctx.recipes]);
  const manaSeals      = useMemoWU(() => window.findManaSeals(ctx.recipes), [ctx.recipes]);
  const key = mode === 'armor' ? armorType : weaponType;
  const laborMult = ctx.laborMult ?? 1;

  // Merge scenario overrides on top of live prices. Wisp-page only.
  const effectivePrices = useMemoWU(() => ({ ...ctx.prices, ...overrides }), [ctx.prices, overrides]);

  const { rows: allRows, wispPrice } = useMemoWU(
    () => window.listWispSources({
      mode, key, sealingIndex, upgradeRecipes, manaSeals,
      prices: effectivePrices, laborGold: laborValue, laborMult,
    }),
    [mode, key, sealingIndex, upgradeRecipes, manaSeals, effectivePrices, laborValue, laborMult]
  );

  const rows = useMemoWU(() => {
    if (!profitableOnly) return allRows;
    return allRows.filter(r => {
      if (r.kind === 'ah-wisp') return true;
      if (!r.hasPrice) return true;
      return wispPrice != null && r.costPerWisp != null && r.costPerWisp < wispPrice;
    });
  }, [allRows, profitableOnly, wispPrice]);

  const overrideMaterials = useMemoWU(() => {
    const seen = new Map();
    function note(id, name, isBest) {
      if (!id || String(id) === '500') return;
      const k = String(id);
      if (!seen.has(k)) seen.set(k, { id: k, name, count: 0, usedInBest: false });
      const v = seen.get(k); v.count++; if (isBest) v.usedInBest = true;
    }
    const bestPick = allRows.find(r => r.hasPrice);
    for (const r of allRows) {
      const isBest = r === bestPick;
      if (r.kind === 'craft' && r.breakdown) {
        for (const m of r.breakdown) if (m.itemType) note(m.itemType, m.name, isBest);
      } else if (r.kind === 'chain' && r.steps) {
        for (const step of r.steps) {
          if (step.breakdown) for (const m of step.breakdown) if (m.itemType) note(m.itemType, m.name, isBest);
        }
      }
    }
    const wispId = mode === 'armor' ? window.WISP_DATA.armor.wisp[key] : window.WISP_DATA.weapons.wisp[key];
    const wispName = `${key} Mana Wisp`;
    const list = [{ id: wispId, name: wispName, count: Infinity, usedInBest: true }];
    for (const v of [...seen.values()].sort((a, b) => (b.usedInBest ? 1 : 0) - (a.usedInBest ? 1 : 0) || b.count - a.count)) {
      if (v.id !== wispId) list.push(v);
    }
    return list;
  }, [allRows, mode, key]);

  const best = rows.find(r => r.hasPrice);
  const priceCount = useMemoWU(() => Object.keys(ctx.prices).length, [ctx.prices]);

  // Hero metric values
  const pricedCount   = rows.filter(r => r.hasPrice).length;
  const cheapestVsAh  = (best && wispPrice != null && best.kind !== 'ah-wisp' && best.costPerWisp != null)
    ? wispPrice - best.costPerWisp : null;
  const profitableCount = wispPrice != null
    ? allRows.filter(r => r.hasPrice && r.kind !== 'ah-wisp' && r.costPerWisp != null && r.costPerWisp < wispPrice).length
    : 0;
  const chainsCount = allRows.filter(r => r.kind === 'chain').length;

  const armorGlyph = { Cloth: '✚', Leather: '◆', Plate: '⛨' };
  const weaponGlyph = { 'Wooden': '⚝', '2H Metal': '⚔', '1H Metal': '⚒' };

  return (
    <main className="detail wisp-view v2">
      <div className="fade-in">

        {/* ── Page hero ─────────────────────────────────────────────── */}
        <div className="page-hero">
          <div className="page-hero-crest" aria-hidden>✦</div>
          <div className="page-hero-meta">
            <div className="page-hero-eyebrow">Wisp Production</div>
            <h1 className="page-hero-title">
              <em>{key}</em> · Cost per Wisp
            </h1>
            <div className="page-hero-tags">
              <span><strong style={{color:'var(--parch)'}}>{pricedCount}</strong>&nbsp;priced source{pricedCount === 1 ? '' : 's'}</span>
              <span className="dot" />
              <span>{chainsCount} multi-grade chains</span>
              <span className="dot" />
              <span style={{color:'var(--labor)'}}>{laborValue}{currency}/labor</span>
              <span className="dot" />
              <span style={{color:'var(--muted)'}}>{priceCount.toLocaleString()} live prices loaded</span>
              {Object.keys(overrides).length > 0 && (
                <>
                  <span className="dot" />
                  <span className="pill">{Object.keys(overrides).length} override{Object.keys(overrides).length === 1 ? '' : 's'} active</span>
                </>
              )}
            </div>
          </div>
          <div className="page-hero-aside">
            <div className="hero-aside-card">
              <div className="hero-aside-label">Auction Wisp Price</div>
              <div className="hero-aside-value">
                {wispPrice != null
                  ? <><span>{fmtG(wispPrice)}</span><span className="unit">{currency}/wisp</span></>
                  : <span style={{color:'var(--garnet)', fontSize: 16, fontStyle: 'italic'}}>not listed</span>}
              </div>
              {best && wispPrice != null && best.kind !== 'ah-wisp' && best.costPerWisp != null && (
                <div className={`hero-aside-foot ${best.costPerWisp < wispPrice ? 'pos' : 'neg'}`}>
                  {best.costPerWisp < wispPrice
                    ? `Best craft beats AH by ${fmtG(wispPrice - best.costPerWisp)}${currency}`
                    : `Best craft is ${fmtG(best.costPerWisp - wispPrice)}${currency} over AH`}
                </div>
              )}
            </div>
          </div>
        </div>

        {/* ── Hero metric strip ────────────────────────────────────── */}
        <div className="metric-strip">
          <div className="metric-cell hero accent">
            <div className="ms-label">Cheapest source</div>
            <div className="ms-value">
              {best && best.costPerWisp != null
                ? <><span>{fmtG(best.costPerWisp)}</span><span className="unit">{currency}/wisp</span></>
                : <span style={{fontSize: 16, color: 'var(--muted)', fontStyle: 'italic'}}>—</span>}
            </div>
            <div className="ms-sub">
              {best ? `${best.method}${best.grade && !best.startGrade ? ' · ' + best.grade : ''}${best.slot ? ' · ' + best.slot : ''}` : 'no priced sources'}
            </div>
          </div>
          <div className={`metric-cell ${cheapestVsAh != null && cheapestVsAh > 0 ? 'pos' : cheapestVsAh != null ? 'neg' : ''}`}>
            <div className="ms-label">Savings vs AH</div>
            <div className="ms-value">
              {cheapestVsAh != null
                ? <><span>{cheapestVsAh >= 0 ? '+' : '−'}{fmtG(Math.abs(cheapestVsAh))}</span><span className="unit">{currency}</span></>
                : <span style={{fontSize: 16, color:'var(--muted)', fontStyle:'italic'}}>—</span>}
            </div>
            <div className="ms-sub">{cheapestVsAh != null && cheapestVsAh > 0 ? 'per wisp produced' : cheapestVsAh != null ? 'AH is cheaper' : 'awaiting AH price'}</div>
          </div>
          <div className="metric-cell">
            <div className="ms-label">Profitable</div>
            <div className="ms-value">
              <span>{profitableCount}</span>
              <span className="unit">/&thinsp;{allRows.filter(r => r.hasPrice && r.kind !== 'ah-wisp').length}</span>
            </div>
            <div className="ms-sub">beat AH wisp price</div>
          </div>
          <div className="metric-cell">
            <div className="ms-label">Chains computed</div>
            <div className="ms-value">
              <span>{chainsCount}</span>
              <span className="unit">paths</span>
            </div>
            <div className="ms-sub">Mag&thinsp;→&thinsp;Ayanad upgrades</div>
          </div>
        </div>

        {/* ── Mode bar (Armor/Weapons + type) ──────────────────────── */}
        <div className="modebar-v2">
          <div className="toggle-stack">
            <div className="toggle-stack-label">Class</div>
            <div className="toggle-group">
              <button className={mode === 'armor' ? 'on' : ''} onClick={() => { setMode('armor'); setExpanded(null); }}>
                <span className="gl">⛨</span> Armor
              </button>
              <button className={mode === 'weapons' ? 'on' : ''} onClick={() => { setMode('weapons'); setExpanded(null); }}>
                <span className="gl">⚔</span> Weapons
              </button>
            </div>
          </div>
          <div className="toggle-stack">
            <div className="toggle-stack-label">{mode === 'armor' ? 'Material' : 'Weapon family'}</div>
            <div className="toggle-group subtle">
              {(mode === 'armor' ? window.WISP_DATA.armor.types : window.WISP_DATA.weapons.types).map(t => (
                <button
                  key={t}
                  className={key === t ? 'on' : ''}
                  onClick={() => { mode === 'armor' ? setArmorType(t) : setWeaponType(t); setExpanded(null); }}
                >
                  <span className="gl">{mode === 'armor' ? armorGlyph[t] : weaponGlyph[t]}</span> {t}
                </button>
              ))}
            </div>
          </div>
          <div style={{flex: 1}} />
          <div className="toggle-stack">
            <div className="toggle-stack-label">View</div>
            <div className="chip-row">
              <button
                className={`chip ${profitableOnly ? 'on' : ''}`}
                onClick={() => setProfitableOnly(v => !v)}
                title="Hide rows that don't beat the AH wisp price"
              >
                {profitableOnly ? '◆' : '◇'} Profitable only
              </button>
              <button
                className={`chip ${overridesOpen ? 'on' : ''}`}
                onClick={() => setOverridesOpen(v => !v)}
              >
                ⚖ Price overrides
                {Object.keys(overrides).length > 0 && <span className="chip-count">· {Object.keys(overrides).length}</span>}
              </button>
            </div>
          </div>
        </div>

        {/* ── Overrides panel (collapsible) ────────────────────────── */}
        {overridesOpen && (
          <div className="framed-section" style={{marginBottom: 22}}>
            <div className="framed-section-head">
              <span className="fs-eyebrow">Scenario</span>
              <span className="fs-title">Price Overrides</span>
              <span className="fs-rule" />
              <span className="fs-aside">
                {Object.keys(overrides).length === 0
                  ? 'no overrides yet — edit any value below'
                  : `${Object.keys(overrides).length} active`}
              </span>
            </div>
            <div className="framed-section-body">
              <WispOverridesPanel
                materials={overrideMaterials}
                overrides={overrides}
                setOverrides={setOverrides}
                livePrices={ctx.prices}
                currency={currency}
              />
            </div>
          </div>
        )}

        {/* ── Ranked sources table ─────────────────────────────────── */}
        <div className="framed-section">
          <div className="framed-section-head">
            <span className="fs-eyebrow">Ledger</span>
            <span className="fs-title">Ranked sources</span>
            <span className="fs-rule" />
            <span className="fs-aside">{rows.length} of {allRows.length} rows</span>
          </div>

          <div className="framed-section-body">
            <div className="wisp-table">
              <div className={`wisp-row wisp-head ${mode}`}>
                <div className="num">#</div>
                <div>Source</div>
                <div>Grade</div>
                {mode === 'armor' && <div>Slot</div>}
                <div className="num">Wisps</div>
                <div className="num" title="Net wisps per cycle">Net</div>
                <div className="num">Capital</div>
                <div className="num">Cost/wisp</div>
                <div className="num">EV g/lp</div>
                <div className="num">vs AH</div>
              </div>
              {rows.map((r, i) => {
                const isOpen = expanded === i;
                const isBest = r === best;
                const isClickable = r.kind === 'craft' || r.kind === 'chain';
                const cellSummary = r.kind === 'ah-wisp' ? 'Direct AH wisp purchase'
                  : r.kind === 'chain' ? `${r.steps.length} steps · net +${fmtInt(r.netWisps)} wisps`
                  : (r.productName || '—');
                const profitVsAh = (wispPrice != null && r.costPerWisp != null && r.kind !== 'ah-wisp')
                  ? wispPrice - r.costPerWisp : null;
                const isGood = profitVsAh != null && profitVsAh > 0;
                const isBad  = profitVsAh != null && profitVsAh < 0;
                const gradeCellContent = r.kind === 'chain'
                  ? (
                    <span className="chain-grade" style={{display:'inline-flex', alignItems:'center', gap:6}}>
                      <span className={`gp ${r.startGrade.toLowerCase()}`}>{r.startGrade}</span>
                      <span className="chain-arrow" style={{padding:0, opacity:0.6}}>→</span>
                      <span className={`gp ${r.endGrade.toLowerCase()}`}>{r.endGrade}</span>
                    </span>
                  )
                  : r.grade
                    ? <span className={`gp ${r.grade.toLowerCase()}`}>{r.grade}</span>
                    : <span style={{color:'var(--muted)'}}>—</span>;
                return (
                  <React.Fragment key={i}>
                    <div
                      className={`wisp-row ${mode} ${isOpen ? 'open' : ''} ${!r.hasPrice ? 'missing' : ''} ${isBest ? 'best' : ''} ${r.kind === 'chain' ? 'chain-row' : ''}`}
                      onClick={() => isClickable && setExpanded(isOpen ? null : i)}
                      style={{cursor: isClickable ? 'pointer' : 'default'}}
                    >
                      <div className="num mono">{i + 1}</div>
                      <div>
                        <div className="wb-method" style={{display:'flex', alignItems:'center', gap:8}}>
                          <span>{r.method}</span>
                          {isBest && <span className="best-ribbon">Best</span>}
                        </div>
                        {cellSummary && <div className="wb-method-sub">{cellSummary}</div>}
                      </div>
                      <div>{gradeCellContent}</div>
                      {mode === 'armor' && <div style={{fontFamily:'var(--serif)', fontSize:14, color:'var(--parch-2)'}}>{r.slot || '—'}</div>}
                      <div className="num mono" style={{color:'var(--parch-3)'}}>{r.wispsPerCraft != null ? r.wispsPerCraft : '—'}</div>
                      <div className={`num mono ${r.netWisps != null && r.netWisps > 0 ? 'pos' : r.netWisps != null && r.netWisps < 0 ? 'neg' : ''}`}>
                        {r.netWisps != null ? `${r.netWisps >= 0 ? '+' : '−'}${Math.abs(r.netWisps).toLocaleString()}` : '—'}
                      </div>
                      <div className="num mono">{r.totalCost != null ? `${fmtG(r.totalCost)}${currency}` : <span className="missing-tag">missing</span>}</div>
                      <div className={`num mono best-profit ${isBest ? 'pos' : ''}`}>{r.costPerWisp != null ? `${fmtG(r.costPerWisp)}${currency}` : '—'}</div>
                      <div className={`num mono ${r.evGPerLp != null && r.evGPerLp >= 0 ? 'pos' : r.evGPerLp != null ? 'neg' : ''}`} title="EV gold per labor">
                        {r.evGPerLp != null ? `${r.evGPerLp >= 0 ? '+' : '−'}${fmtG(Math.abs(r.evGPerLp))}${currency}/lp` : '—'}
                      </div>
                      <div className={`num mono ${isGood ? 'pos' : isBad ? 'neg' : ''}`} style={{color: r.kind === 'ah-wisp' ? 'var(--muted)' : undefined}}>
                        {r.kind === 'ah-wisp' ? '—' : profitVsAh != null
                          ? `${profitVsAh >= 0 ? '+' : '−'}${fmtG(Math.abs(profitVsAh))}${currency}`
                          : '—'}
                      </div>
                    </div>
                    {isOpen && r.kind === 'craft' && (
                      <WispRecipeBreakdown row={r} mode={mode} currency={currency} laborValue={laborValue} wispPrice={wispPrice} />
                    )}
                    {isOpen && r.kind === 'chain' && (
                      <WispChainBreakdown row={r} mode={mode} currency={currency} laborValue={laborValue} wispPrice={wispPrice} />
                    )}
                  </React.Fragment>
                );
              })}
              {rows.length === 0 && (
                <div style={{padding: '40px 20px', textAlign: 'center', color: 'var(--muted)'}}>
                  No sealing recipes found for {key}.
                </div>
              )}
            </div>
          </div>
        </div>

        {/* ── Notes ────────────────────────────────────────────────── */}
        <div className="notes-panel">
          <div className="notes-label">How rankings work</div>
          Each row is a way to produce one wisp of the selected type. <strong>Chains</strong> transmute a low-grade wisp-craft up through upgrade recipes, with mana-seal rerolls at each Sealed intermediate, then salvage the top piece for wisps. Chains starting at <strong>Magnificent</strong> or higher are computed; Illustrious starts are skipped because the Magnificent reroll has no in-data mana seal and is dominated by starting at Magnificent. Click any craft or chain row for a full breakdown.
        </div>
      </div>
    </main>
  );
}

// ─── Recipe breakdown panel (on row click) ──────────────────────────────
function WispRecipeBreakdown({ row, mode, currency, laborValue, wispPrice }) {
  const profitPerWisp = (wispPrice != null && row.costPerWisp != null)
    ? wispPrice - row.costPerWisp : null;
  const profitPerCraft = (wispPrice != null && row.totalCost != null)
    ? wispPrice * row.wispsPerCraft - row.totalCost : null;
  return (
    <div className="wisp-breakdown one-col">
      <div className="wb-panel">
        <div className="wb-title">Recipe — {row.productName}</div>
        <div className="wb-recipe-grid">
          <div className="wb-recipe-head">
            <div>Material</div>
            <div className="num">Amount</div>
            <div className="num">Unit ({currency})</div>
            <div className="num">Subtotal</div>
          </div>
          {row.breakdown.map((m, i) => (
            <div key={i} className={`wb-recipe-row ${m.unit == null ? 'missing' : ''}`}>
              <div style={{display:'flex', alignItems:'center', gap:8}}>
                <window.ItemIcon itemType={m.itemType} name={m.name} size="sm" />
                <span>{m.name}</span>
              </div>
              <div className="num mono">×{fmtInt(m.amount)}</div>
              <div className="num mono">{m.unit != null ? fmtG(m.unit) : <span className="missing-tag">missing</span>}</div>
              <div className="num mono" style={{color: m.unit != null ? 'var(--parch)' : 'var(--garnet)'}}>
                {m.sub != null ? `${fmtG(m.sub)}${currency}` : '—'}
              </div>
            </div>
          ))}
        </div>
        <div className="wb-totals">
          <div className="wb-totals-row">
            <span>Material cost</span>
            <span className="mono">{row.matCost != null ? `${fmtG(row.matCost)}${currency}` : '—'}</span>
          </div>
          <div className="wb-totals-row">
            <span style={{color:'var(--labor)'}}>Labor</span>
            <span className="mono" style={{color:'var(--labor)'}}>{Math.round(row.laborPerCraft)} lp</span>
          </div>
          <div className="wb-totals-row total">
            <span>Total per craft</span>
            <span className="mono">{row.totalCost != null ? `${fmtG(row.totalCost)}${currency}` : '—'}</span>
          </div>
          <div className="wb-totals-row">
            <span>Salvage yield</span>
            <span className="mono" style={{color:'var(--sapphire)'}}>{row.wispsPerCraft} wisp{row.wispsPerCraft === 1 ? '' : 's'}</span>
          </div>
          <div className="wb-totals-row" style={{borderTop:'1px solid var(--line)', paddingTop:8, marginTop:4}}>
            <span style={{color:'var(--gold)', fontWeight:600}}>Cost per wisp</span>
            <span className="mono" style={{color:'var(--gold)', fontWeight:600}}>{row.costPerWisp != null ? `${fmtG(row.costPerWisp)}${currency}` : '—'}</span>
          </div>
          {wispPrice != null && row.costPerWisp != null && (
            <>
              <div className="wb-totals-row">
                <span>Profit per wisp vs AH ({fmtG(wispPrice)}{currency})</span>
                <span className={`mono ${profitPerWisp >= 0 ? 'pos' : 'neg'}`} style={{fontWeight:600}}>
                  {profitPerWisp >= 0 ? '+' : '−'}{fmtG(Math.abs(profitPerWisp))}{currency}
                </span>
              </div>
              <div className="wb-totals-row">
                <span>Profit per full craft cycle</span>
                <span className={`mono ${profitPerCraft >= 0 ? 'pos' : 'neg'}`} style={{fontWeight:600}}>
                  {profitPerCraft >= 0 ? '+' : '−'}{fmtG(Math.abs(profitPerCraft))}{currency}
                </span>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Chain breakdown — flow diagram + step list ─────────────────────────
function WispChainBreakdown({ row, mode, currency, laborValue, wispPrice }) {
  // Build the visual chain nodes. Insert a final "Salvage" node for the
  // grade-to-wisp conversion so the flow ends with the wisp payout.
  const flowNodes = [];
  row.steps.forEach((s, i) => flowNodes.push({ ...s, _idx: i }));
  flowNodes.push({
    kind: 'salvage',
    _idx: flowNodes.length,
    grade: row.endGrade,
    wispsPerCraft: row.wispsPerCraft,
  });

  return (
    <div className="wisp-breakdown one-col">
      <div className="wb-panel" style={{padding: 18}}>
        <div className="wb-title" style={{margin: 0}}>
          Chain: <span className={`gp ${row.startGrade.toLowerCase()}`} style={{margin:'0 6px'}}>{row.startGrade}</span>
          →
          <span className={`gp ${row.endGrade.toLowerCase()}`} style={{margin:'0 6px'}}>{row.endGrade}</span>
          {row.slot ? ` · ${row.slot}` : ''}
        </div>

        {/* Visual chain flow */}
        <div className="chain-flow-wrap" style={{marginTop: 14}}>
          <div className="chain-flow">
            {flowNodes.map((node, idx) => (
              <React.Fragment key={idx}>
                <div className={`chain-node kind-${node.kind}`}>
                  <div className="chain-node-head">
                    <span className="chain-node-num">STEP {idx + 1}</span>
                    <span className="chain-node-glyph"><ChainStepGlyph kind={node.kind} /></span>
                  </div>
                  <div className="chain-node-label">
                    {node.kind === 'wisp-craft' ? 'Wisp-craft' :
                     node.kind === 'reroll'     ? 'Reroll' :
                     node.kind === 'upgrade'    ? 'Upgrade' :
                                                  'Salvage'}
                  </div>
                  <div className="chain-node-title">
                    {node.kind === 'wisp-craft' && <><span className={`gp ${node.grade.toLowerCase()}`} style={{marginBottom:4}}>{node.grade}</span></>}
                    {node.kind === 'reroll'     && <>{node.grade} <span className="gp" style={{color:'var(--amethyst)', borderColor:'var(--amethyst)', marginLeft:4}}>seal</span></>}
                    {node.kind === 'upgrade'    && <><span className={`gp ${node.fromGrade.toLowerCase()}`}>{node.fromGrade}</span> → <span className={`gp ${node.toGrade.toLowerCase()}`}>{node.toGrade}</span></>}
                    {node.kind === 'salvage'    && <span style={{color:'var(--emerald)'}}>+{node.wispsPerCraft} wisps</span>}
                  </div>
                  {node.kind === 'reroll' && (
                    <div className="chain-node-detail">
                      {node.expectedAttempts != null
                        ? <>~{node.expectedAttempts.toFixed(1)}× @ {(node.pSuccess*100).toFixed(0)}%</>
                        : 'attempts —'}
                    </div>
                  )}
                  {(node.kind === 'wisp-craft' || node.kind === 'upgrade') && node.effLp != null && (
                    <div className="chain-node-detail">{Math.round(node.effLp)} lp labor</div>
                  )}
                  {node.kind === 'salvage' && (
                    <div className="chain-node-detail">final salvage yield</div>
                  )}
                  <div className={`chain-node-cost ${node.kind === 'salvage' ? 'muted' : ''}`}>
                    {node.kind === 'salvage'
                      ? (wispPrice != null ? `${fmtG(wispPrice * node.wispsPerCraft)}${currency} value` : '—')
                      : (node.totalCost != null ? `${fmtG(node.totalCost)}${currency}` : '—')}
                  </div>
                </div>
                {idx < flowNodes.length - 1 && <ChainArrow />}
              </React.Fragment>
            ))}
          </div>
        </div>

        {/* Summary numbers */}
        <div className="wb-chain-summary" style={{marginTop: 14}}>
          <div className="wcs-line">
            <span>Total cost per cycle</span>
            <span className="mono" style={{color:'var(--parch)'}}>{row.totalCost != null ? `${fmtG(row.totalCost)}${currency}` : '—'}</span>
          </div>
          <div className="wcs-line">
            <span>Wisps consumed (initial wisp-craft)</span>
            <span className="mono" style={{color:'var(--garnet)'}}>−{row.wispsConsumed}</span>
          </div>
          <div className="wcs-line">
            <span>Wisps produced (final salvage)</span>
            <span className="mono" style={{color:'var(--emerald)'}}>+{row.wispsPerCraft}</span>
          </div>
          <div className="wcs-line wcs-total">
            <span>Net wisp gain per cycle</span>
            <span className="mono" style={{color:'var(--gold)', fontWeight:600}}>+{row.netWisps}</span>
          </div>
          {row.expectedWispReturn != null && row.sealWispsBurned > 0 && (
            <div className="wcs-line">
              <span title="Net gain minus expected wisps burned crafting mana seals across all reroll attempts.">
                Expected wisp return
              </span>
              <span className={`mono ${row.expectedWispReturn >= 0 ? 'pos' : 'neg'}`} style={{fontWeight:600}}>
                {row.expectedWispReturn >= 0 ? '+' : '−'}{Math.abs(row.expectedWispReturn).toFixed(1)}
                <span style={{color:'var(--muted)', fontWeight:400, marginLeft:4}}>
                  (−{row.sealWispsBurned.toFixed(1)} burned on seals)
                </span>
              </span>
            </div>
          )}
          <div className="wcs-line">
            <span>Cost per wisp produced</span>
            <span className="mono" style={{color:'var(--gold)', fontWeight:600}}>{row.costPerWisp != null ? `${fmtG(row.costPerWisp)}${currency}` : '—'}</span>
          </div>
          {row.evGPerLp != null && (
            <div className="wcs-line">
              <span>EV gold per labor</span>
              <span className={`mono ${row.evGPerLp >= 0 ? 'pos' : 'neg'}`} style={{fontWeight:600}}>
                {row.evGPerLp >= 0 ? '+' : '−'}{fmtG(Math.abs(row.evGPerLp))}{currency}/lp
              </span>
            </div>
          )}
          {wispPrice != null && row.costPerWisp != null && (
            <div className="wcs-line">
              <span>vs AH wisp ({fmtG(wispPrice)}{currency})</span>
              <span className={`mono ${row.costPerWisp < wispPrice ? 'pos' : 'neg'}`} style={{fontWeight:600}}>
                {row.costPerWisp < wispPrice ? '−' : '+'}{fmtG(Math.abs(wispPrice - row.costPerWisp))}{currency}
              </span>
            </div>
          )}
        </div>

        <div className="wb-title" style={{marginTop:18}}>Step-by-step</div>
        <div className="wb-chain-steps">
          {row.steps.map((s, i) => (
            <WispChainStep key={i} idx={i} step={s} currency={currency} laborValue={laborValue} />
          ))}
        </div>

        {row.missing && row.missing.length > 0 && (
          <div className="wb-chain-missing">
            <strong>Missing prices / data:</strong> {row.missing.join(' · ')}
          </div>
        )}
      </div>
    </div>
  );
}

function WispChainStep({ idx, step, currency, laborValue }) {
  const stepLabel = step.kind === 'wisp-craft' ? `Wisp-craft ${step.grade}`
    : step.kind === 'upgrade' ? `Upgrade ${step.fromGrade} → Sealed ${step.toGrade}`
    : step.kind === 'reroll' ? `Reroll ${step.grade} (Sealed → Typhoon)`
    : 'Step';
  return (
    <div className={`wcs-step wcs-${step.kind}`}>
      <div className="wcs-step-head">
        <span className="wcs-step-num">{idx + 1}</span>
        <span className="wcs-step-label">{stepLabel}</span>
        <span className="wcs-step-cost mono">{step.totalCost != null ? `${fmtG(step.totalCost)}${currency}` : '—'}</span>
      </div>
      {step.kind === 'reroll' && (
        <div className="wcs-step-detail">
          {step.sealPrice != null ? (
            <>
              Seal cost {fmtG(step.sealPrice)}{currency}
              <span style={{color:'var(--muted)'}}>
                {step.sealWispCost != null && <> ({step.sealWispCost} wisps × AH</>}
                {step.sealLaborLp > 0 && <> + {Math.round(step.sealLaborLp)} lp labor</>}
                {step.sealWispCost != null && <>)</>}
              </span>
              <br/>
              <span style={{color:'var(--muted)'}}>× <strong style={{color:'var(--parch-3)'}}>{step.expectedAttempts.toFixed(2)}</strong> attempts ({(step.pSuccess * 100).toFixed(1)}% per try)</span>
            </>
          ) : <span style={{color:'var(--garnet)'}}>Mana seal cost not available</span>}
        </div>
      )}
      {(step.kind === 'wisp-craft' || step.kind === 'upgrade') && (
        <div className="wcs-step-detail">
          <span style={{color:'var(--muted)'}}>{step.productName}</span>
          {' · '}
          <span className="mono">{fmtG(step.matCost)}{currency} mats</span>
          {' + '}
          <span className="mono" style={{color:'var(--labor)'}}>{Math.round(step.effLp || (step.recipe.laborCost || 0))} lp labor</span>
          {step.missing && step.missing.length > 0 && (
            <div style={{color:'var(--garnet)', fontSize:10, marginTop:4}}>missing: {step.missing.slice(0,3).join(', ')}</div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Price overrides panel ────────────────────────────────────────────
function WispOverridesPanel({ materials, overrides, setOverrides, livePrices, currency }) {
  function setOverride(id, val) {
    setOverrides(prev => {
      const next = { ...prev };
      if (val === '' || val == null || !Number.isFinite(Number(val))) delete next[id];
      else next[id] = Number(val);
      return next;
    });
  }
  function clearAll() {
    if (confirm('Clear all wisp-page price overrides?')) setOverrides({});
  }
  return (
    <div className="wisp-overrides-panel" style={{borderTop: 0, paddingTop: 12}}>
      <div className="wop-head">
        <span><strong>Price overrides</strong> — scoped to this page. Edit any material to model "what if" scenarios; the calculator re-ranks instantly.</span>
        {Object.keys(overrides).length > 0 && (
          <button className="wop-clear" onClick={clearAll}>Clear all</button>
        )}
      </div>
      <div className="wop-grid">
        {materials.map(m => {
          const live = livePrices[m.id];
          const override = overrides[m.id];
          const isOverridden = override != null;
          return (
            <label key={m.id} className={`wop-row ${isOverridden ? 'overridden' : ''} ${m.usedInBest ? 'in-best' : ''}`}>
              <span className="wop-icon">
                <window.ItemIcon itemType={m.id} name={m.name} size="sm" />
              </span>
              <span className="wop-name">
                <span className="wop-name-text">{m.name}</span>
                <span className="wop-name-meta">{live != null ? `AH ${fmtG(live)}${currency}` : 'no AH price'}{m.usedInBest && <span className="wop-best-mark"> · in best</span>}</span>
              </span>
              <input
                type="number" step="0.0001" min="0"
                className="wop-input"
                value={override ?? ''}
                placeholder={live != null ? fmtG(live) : '—'}
                onChange={(e) => setOverride(m.id, e.target.value)}
              />
              <span className="wop-unit">{currency}</span>
              {isOverridden && (
                <button className="wop-reset" onClick={(e) => { e.preventDefault(); setOverride(m.id, ''); }} title="Reset to AH price">↺</button>
              )}
            </label>
          );
        })}
        {materials.length === 0 && (
          <div style={{color:'var(--muted)', padding:'12px', textAlign:'center', gridColumn:'1 / -1'}}>
            No materials in view. Switch to a different type/mode to see overridable inputs.
          </div>
        )}
      </div>
    </div>
  );
}

window.WispPanel = WispPanel;
