/* ═══════════════════════════════════════════════════════════
   自選股札記 v3 ·「持股帳房」The Ledger House
   ─────────────────────────────────────────────────────────
   單一資料模型：一筆倉位 = { watch, buyLegs, sellLegs, divLegs, sl, tp }
   觀察中(watch:true) → 建倉 → 持有中 → 了結 → 結算績效
   配色：本頁採台股慣例「紅漲綠跌」（漲/獲利 = --bear 紅）
   ═══════════════════════════════════════════════════════════ */
const { useState, useEffect, useMemo, useRef } = React;

// ── 資料層 ────────────────────────────────────────────────
const PF_KEY = 'tw_portfolio_v3';

function loadPortfolio() {
  try {
    var cur = JSON.parse(localStorage.getItem(PF_KEY) || 'null');
    if (Array.isArray(cur)) return cur;
  } catch (_) {}
  // ── 一次性遷移：舊倉位制 + 舊自選清單 ──
  var out = [];
  var seen = {};
  try {
    var oldPos = JSON.parse(localStorage.getItem('tw_trade_pos_v1') || '[]');
    oldPos.forEach(function(p) {
      if (!p.code || seen[p.code]) return;
      seen[p.code] = 1;
      out.push({ id: p.id || 'pf_' + p.code, code: p.code, name: p.name || p.code, watch: false,
        buyLegs: p.buyLegs || [], sellLegs: p.sellLegs || [], divLegs: p.divLegs || [],
        sl: p.sl || 0, tp: p.tp || 0, note: p.note || '' });
    });
  } catch (_) {}
  try {
    var oldWl = JSON.parse(localStorage.getItem('tw_watchlist_v2') || '[]');
    (oldWl || []).forEach(function(g) {
      (g.items || []).forEach(function(it) {
        if (!it.code || seen[it.code]) return;
        seen[it.code] = 1;
        var hasPos = it.cost > 0 && it.qty > 0;
        out.push({ id: 'pf_' + it.code, code: it.code, name: it.name || it.code, watch: !hasPos,
          buyLegs: hasPos ? [{ id: 'mig_' + it.code, date: new Date().toISOString().slice(0,10),
            price: +it.cost, qty: +it.qty, fee: 0, note: '舊自選持倉遷移' }] : [],
          sellLegs: [], divLegs: [], sl: 0, tp: 0, note: it.note || '' });
      });
    });
  } catch (_) {}
  if (out.length) localStorage.setItem(PF_KEY, JSON.stringify(out));
  return out;
}
function savePortfolio(arr) { localStorage.setItem(PF_KEY, JSON.stringify(arr)); }

// ── 元大證券費用標準 ─────────────────────────────────────
// 手續費 0.1425%×折扣 無條件捨去；整股低消 $20、零股(<1000股)低消 $1
// 證交稅(賣出)：股票 0.3% / ETF 0.1% / 現股當沖 0.15%
const TAX_RATES = { stock: 0.003, etf: 0.001, dt: 0.0015 };
function estFee(price, qty, isSell, discount, taxRate) {
  var amount = price * qty;
  var disc   = (discount != null ? discount : 0.6);
  var minFee = qty < 1000 ? 1 : 20;
  var comm   = Math.max(minFee, Math.floor(amount * 0.001425 * disc));
  var tax    = isSell ? Math.floor(amount * (taxRate != null ? taxRate : 0.003)) : 0;
  return comm + tax;
}

function daysBetween(d1, d2) {
  if (!d1 || !d2) return null;
  return Math.round((new Date(d2) - new Date(d1)) / 86400000);
}
function fmtPnl(n) { return (n >= 0 ? '+' : '−') + '$' + Math.abs(Math.round(n)).toLocaleString('en-US'); }
function fmtPct(n) { return (n >= 0 ? '+' : '') + n.toFixed(2) + '%'; }
function fmtMoney(n) { return '$' + Math.round(n).toLocaleString('en-US'); }
function upDn(n) { return n >= 0 ? 'var(--bear)' : 'var(--bull)'; } // 紅漲綠跌

// 倉位衍生數據（含手續費與配息）
function calcPos(p, livePrice) {
  var buyQty    = p.buyLegs.reduce(function(s,l){return s+l.qty;},0);
  var sellQty   = p.sellLegs.reduce(function(s,l){return s+l.qty;},0);
  var openQty   = buyQty - sellQty;
  var totalCost = p.buyLegs.reduce(function(s,l){return s+l.price*l.qty;},0);
  var totalBuyFee = p.buyLegs.reduce(function(s,l){return s+(l.fee||0);},0);
  var adjAvgCost = buyQty > 0 ? (totalCost + totalBuyFee) / buyQty : 0;
  var avgCost    = buyQty > 0 ? totalCost / buyQty : 0;
  var realizedPnl = p.sellLegs.reduce(function(s,l){
    return s + (l.price - adjAvgCost) * l.qty - (l.fee||0);
  }, 0);
  var totalFees = totalBuyFee + p.sellLegs.reduce(function(s,l){return s+(l.fee||0);},0);
  var unrealizedPnl = livePrice && openQty > 0 ? (livePrice - adjAvgCost) * openQty : null;
  var divIncome = (p.divLegs || []).reduce(function(s,l){return s+(l.amount||0);},0);
  var mktValue  = livePrice && openQty > 0 ? livePrice * openQty : 0;
  var firstBuy = p.buyLegs.length ? p.buyLegs[0].date : '';
  var lastSell = p.sellLegs.length ? p.sellLegs[p.sellLegs.length-1].date : '';
  var today    = new Date().toISOString().slice(0,10);
  var holdDays = firstBuy ? daysBetween(firstBuy, openQty > 0 ? today : lastSell) : null;
  return { buyQty, sellQty, openQty, avgCost, adjAvgCost, realizedPnl, unrealizedPnl,
           divIncome, mktValue, totalFees, firstBuy, lastSell, holdDays };
}

// ── 買賣登記表單 ─────────────────────────────────────────
function LegForm({ type, onAdd, openQty, discount, taxRate }) {
  var today = new Date().toISOString().slice(0,10);
  var [f, setF] = useState({ date:today, price:'', qty:'', fee:'', note:'' });
  function set(k,v){ setF(function(p){ return Object.assign({},p,{[k]:v}); }); }
  function autoFee() {
    var p = parseFloat(f.price), q = parseInt(f.qty);
    if (!p || !q) return;
    set('fee', String(estFee(p, q, type==='sell', discount, taxRate)));
  }
  function submit() {
    if (!f.date || !f.price || !f.qty) return;
    onAdd(Object.assign({}, f, { fee: parseFloat(f.fee)||0 }));
    setF({ date:today, price:'', qty:'', fee:'', note:'' });
  }
  var isSell = type === 'sell';
  var accent = isSell ? 'var(--bull)' : 'var(--bear)'; // 買=紅(進場) 賣=綠(出場)
  var estAmt = f.price && f.qty ? estFee(parseFloat(f.price), parseInt(f.qty)||0, isSell, discount, taxRate) : 0;
  return (
    <div style={{ padding:'12px', background:'rgba(255,255,255,.45)',
      marginTop:8, marginBottom:8, border:'1px dashed '+accent }}>
      <div style={{ display:'flex', gap:8, alignItems:'flex-end', flexWrap:'wrap', marginBottom:8 }}>
        {[
          { lbl: isSell ? '賣出日期' : '買入日期', k:'date',  tp:'date',   w:130 },
          { lbl: isSell ? '賣出價'   : '買入價',   k:'price', tp:'number', w:90,  ph:'每股' },
          { lbl: '股數'+(isSell&&openQty?' (持'+openQty.toLocaleString()+')':''), k:'qty', tp:'number', w:100, ph: isSell&&openQty?'≤'+openQty:'' },
        ].map(function(field) {
          return (
            <div key={field.k} style={{ width:field.w }}>
              <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:3 }}>{field.lbl}</div>
              <input type={field.tp} placeholder={field.ph||''} className="wl-edit-input num" style={{ width:'100%' }}
                value={f[field.k]} onChange={function(e){set(field.k,e.target.value);}} onKeyDown={function(e){if(e.key==='Enter')submit();}}/>
            </div>
          );
        })}
        <div style={{ flex:1, minWidth:120 }}>
          <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:3 }}>備註</div>
          <input type="text" className="wl-edit-input" style={{ width:'100%' }} placeholder="策略 / 理由"
            value={f.note} onChange={function(e){set('note',e.target.value);}} onKeyDown={function(e){if(e.key==='Enter')submit();}}/>
        </div>
        <button onClick={submit} className="kai" style={{ padding:'7px 18px', fontSize:12, cursor:'pointer', flexShrink:0,
          background:accent, color:'#fff', border:'none' }}>登記</button>
      </div>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}>
        <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', flexShrink:0 }}>
          手續費{isSell?'＋證交稅':''}：
        </span>
        <input type="number" className="wl-edit-input num" style={{ width:110 }}
          placeholder="0" value={f.fee} onChange={function(e){set('fee',e.target.value);}}
          onKeyDown={function(e){if(e.key==='Enter')submit();}}/>
        <button onClick={autoFee} className="kai"
          style={{ padding:'4px 14px', cursor:'pointer', fontSize:'var(--fs-nano)', flexShrink:0,
            background: estAmt > 0 ? 'var(--gold)' : '#ccc', color:'#fff', border:'none' }}>
          元大標準估算{estAmt > 0 ? '（$'+estAmt.toLocaleString()+'）' : ''}
        </button>
      </div>
    </div>
  );
}

// ── 配息登記表單 ─────────────────────────────────────────
function DivForm({ openQty, onAdd }) {
  var today = new Date().toISOString().slice(0,10);
  var [f, setF] = useState({ date:today, perShare:'', qty: openQty > 0 ? String(openQty) : '', note:'' });
  function set(k,v){ setF(function(p){ return Object.assign({},p,{[k]:v}); }); }
  var amount = (parseFloat(f.perShare)||0) * (parseInt(f.qty)||0);
  function submit() {
    if (!f.date || !amount) return;
    onAdd({ date:f.date, perShare:parseFloat(f.perShare), qty:parseInt(f.qty), amount:Math.round(amount), note:f.note||'' });
    setF({ date:today, perShare:'', qty: openQty > 0 ? String(openQty) : '', note:'' });
  }
  return (
    <div style={{ padding:'12px', background:'rgba(184,146,77,.05)', marginBottom:8,
      border:'1px dashed var(--gold)', display:'flex', gap:8, alignItems:'flex-end', flexWrap:'wrap' }}>
      {[
        { lbl:'除息日',   k:'date',     tp:'date',   w:130 },
        { lbl:'每股股利', k:'perShare', tp:'number', w:90, ph:'如 3.5' },
        { lbl:'參與股數', k:'qty',      tp:'number', w:100 },
      ].map(function(field) {
        return (
          <div key={field.k} style={{ width:field.w }}>
            <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:3 }}>{field.lbl}</div>
            <input type={field.tp} placeholder={field.ph||''} className="wl-edit-input num" style={{ width:'100%' }}
              value={f[field.k]} onChange={function(e){set(field.k,e.target.value);}} onKeyDown={function(e){if(e.key==='Enter')submit();}}/>
          </div>
        );
      })}
      <div style={{ flex:1, minWidth:100 }}>
        <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:3 }}>備註</div>
        <input type="text" className="wl-edit-input" style={{ width:'100%' }} placeholder="現金股利 / 年度"
          value={f.note} onChange={function(e){set('note',e.target.value);}} onKeyDown={function(e){if(e.key==='Enter')submit();}}/>
      </div>
      <div className="num" style={{ fontSize:'var(--fs-nano)', color:'var(--gold-deep, #9a7b35)', fontWeight:700, paddingBottom:7, minWidth:84 }}>
        {amount > 0 ? '+$' + Math.round(amount).toLocaleString() : ''}
      </div>
      <button onClick={submit} className="kai" style={{ padding:'7px 18px', fontSize:12, cursor:'pointer', flexShrink:0,
        background:'var(--gold-deep, #9a7b35)', color:'#fff', border:'none' }}>登記</button>
    </div>
  );
}

// ── 持倉帳冊卡 ───────────────────────────────────────────
function HoldingCard({ pos, live, onUpdate, onDelete, discount, taxRate }) {
  var [open,         setOpen]        = useState(false);
  var [showBuyForm,  setShowBuyForm] = useState(false);
  var [showSellForm, setShowSellForm]= useState(false);
  var [showDivForm,  setShowDivForm] = useState(false);
  var livePrice = live && live.price > 0 ? live.price : null;
  var st = calcPos(pos, livePrice);
  var isClosed = st.openQty <= 0 && st.sellQty > 0;
  var hasLive  = livePrice && st.openQty > 0;
  var sellCostRate = 0.001425 * (discount != null ? discount : 0.6) + (taxRate != null ? taxRate : 0.003);
  var breakeven = st.adjAvgCost > 0 ? st.adjAvgCost / (1 - sellCostRate) : 0;
  var hitSL = hasLive && pos.sl > 0 && livePrice <= pos.sl;
  var hitTP = hasLive && pos.tp > 0 && livePrice >= pos.tp;

  function patch(p) { onUpdate(Object.assign({}, pos, p)); }
  function addLeg(key, leg) {
    var legs = [].concat(pos[key] || [], [Object.assign({ id: Date.now().toString() }, leg)])
      .sort(function(a,b){ return a.date.localeCompare(b.date); });
    patch({ [key]: legs });
  }
  function dropLeg(key, id) {
    patch({ [key]: (pos[key]||[]).filter(function(l){ return l.id !== id; }) });
  }
  function addBuy(f)  { addLeg('buyLegs',  { date:f.date, price:parseFloat(f.price), qty:parseInt(f.qty), fee:parseFloat(f.fee)||0, note:f.note||'' }); }
  function addSell(f) { addLeg('sellLegs', { date:f.date, price:parseFloat(f.price), qty:parseInt(f.qty), fee:parseFloat(f.fee)||0, note:f.note||'' }); }

  // running avg cost per buy leg
  var avgRows = [];
  if (open) {
    var cumQ = 0, cumC = 0;
    avgRows = pos.buyLegs.map(function(l) {
      cumQ += l.qty; cumC += l.price * l.qty + (l.fee||0);
      return (cumC / cumQ).toFixed(2);
    });
  }

  return (
    <div className={'pos-card' + (isClosed ? ' pos-closed' : '')}
      style={{ border:'1px solid var(--paper-line)', marginBottom:10, background:'var(--paper-bright)' }}>
      {/* ── 頭列 ── */}
      <div className="pos-card-head" onClick={function(){setOpen(!open);}}
        style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 16px', cursor:'pointer', userSelect:'none', flexWrap:'wrap' }}>
        <span style={{ width:8, height:8, borderRadius:'50%', flexShrink:0,
          background: isClosed ? 'var(--ink-mute)' : (st.unrealizedPnl != null ? upDn(st.unrealizedPnl) : 'var(--gold)') }}/>
        <span className="num" style={{ fontWeight:700, fontSize:16 }}>
          <a href={'個股技術分析.html?code='+pos.code} onClick={function(e){e.stopPropagation();}}
            style={{ color:'var(--ink)', textDecoration:'none' }}>{pos.code}</a>
        </span>
        <span className="kai" style={{ fontSize:13, color:'var(--ink-soft, var(--ink))', flexShrink:0 }}>{pos.name}</span>
        {hitSL && <span className="kai" style={{ fontSize:'var(--fs-nano)', padding:'1px 8px', flexShrink:0, letterSpacing:'.08em',
          color:'#fff', background:'var(--bull)', animation:'pulse 1.6s infinite' }}>⚠ 觸及停損</span>}
        {hitTP && <span className="kai" style={{ fontSize:'var(--fs-nano)', padding:'1px 8px', flexShrink:0, letterSpacing:'.08em',
          color:'#fff', background:'var(--gold-deep, #9a7b35)' }}>◎ 達停利</span>}
        <span style={{ flex:1 }}/>
        {/* 現價 + 今日 */}
        {live && live.price > 0 && !isClosed && (
          <div style={{ textAlign:'right', minWidth:84 }}>
            <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>now</div>
            <div className="num" style={{ fontSize:14, fontWeight:700 }}>
              {live.price.toLocaleString()}
              <span style={{ fontSize:'var(--fs-nano)', fontWeight:400, marginLeft:4, color: upDn(live.chg||0) }}>
                {fmtPct(live.chg||0)}
              </span>
            </div>
          </div>
        )}
        <div style={{ textAlign:'right', minWidth:74 }}>
          <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>avg cost</div>
          <div className="num" style={{ fontSize:13, fontWeight:600 }}>{st.avgCost > 0 ? '$'+st.avgCost.toFixed(2) : '—'}</div>
        </div>
        {st.openQty > 0 && breakeven > 0 && (
          <div style={{ textAlign:'right', minWidth:74 }}>
            <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>break-even</div>
            <div className="num" style={{ fontSize:13, fontWeight:600,
              color: hasLive ? upDn(livePrice - breakeven) : 'var(--ink)' }}>${breakeven.toFixed(2)}</div>
          </div>
        )}
        <div style={{ textAlign:'right', minWidth:68 }}>
          <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>qty</div>
          <div className="num" style={{ fontSize:13, fontWeight:600, color: isClosed ? 'var(--ink-mute)' : 'var(--ink)' }}>
            {isClosed ? '已結清' : st.openQty.toLocaleString()}
          </div>
        </div>
        {hasLive && (function() {
          // 元大 App 口徑：再扣「現在全賣」的預估費稅（賣費用牌價無折、稅率依設定）
          var mkt = livePrice * st.openQty;
          var sellEst = Math.floor(mkt * 0.001425) + Math.floor(mkt * (taxRate != null ? taxRate : 0.003));
          var net = st.unrealizedPnl - sellEst;
          return (
            <div style={{ textAlign:'right', minWidth:110 }}>
              <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>unrealized</div>
              <div className="num" style={{ fontSize:14, fontWeight:700, color: upDn(st.unrealizedPnl) }}>
                {fmtPnl(st.unrealizedPnl)}
                <span style={{ fontSize:'var(--fs-nano)', fontWeight:400, marginLeft:4 }}>({fmtPct((livePrice/st.adjAvgCost-1)*100)})</span>
              </div>
              <div className="num" style={{ fontSize:'var(--fs-nano)', color: upDn(net), opacity:.85 }} title="市值−含費成本−預估賣出手續費(牌價)−證交稅，與元大App顯示一致">
                元大口徑 {fmtPnl(net)}
              </div>
            </div>
          );
        })()}
        {st.divIncome > 0 && (
          <div style={{ textAlign:'right', minWidth:64 }}>
            <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>dividends</div>
            <div className="num" style={{ fontSize:13, fontWeight:600, color:'var(--gold-deep, #9a7b35)' }}>+${Math.round(st.divIncome).toLocaleString()}</div>
          </div>
        )}
        {st.sellQty > 0 && (
          <div style={{ textAlign:'right', minWidth:90 }}>
            <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>realized</div>
            <div className="num" style={{ fontSize:13, fontWeight:700, color: upDn(st.realizedPnl) }}>{fmtPnl(st.realizedPnl)}</div>
          </div>
        )}
        <span style={{ fontSize:13, color:'var(--ink-mute)', marginLeft:4 }}>{open ? '▲' : '▼'}</span>
      </div>

      {/* ── 展開帳頁 ── */}
      {open && (
        <div className="pos-detail" style={{ borderTop:'1px solid var(--paper-line)', padding:'14px 16px' }}>
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16, marginBottom:14 }}>
            {/* 買入 */}
            <div>
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:6 }}>
                <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', letterSpacing:'.08em' }}>Buy Legs · 買入記錄</span>
                <button onClick={function(){setShowBuyForm(!showBuyForm); setShowSellForm(false);}}
                  style={{ background:'none', border:'1px solid var(--bear)', color:'var(--bear)', cursor:'pointer', fontSize:'var(--fs-nano)', padding:'1px 8px' }}>
                  {showBuyForm ? '收起' : '＋ 買入/加碼'}
                </button>
              </div>
              {showBuyForm && <LegForm type="buy" discount={discount} taxRate={taxRate} onAdd={function(f){ addBuy(f); setShowBuyForm(false); }}/>}
              {pos.buyLegs.length === 0
                ? <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic', padding:'6px 0' }}>尚無買入記錄 — 按「＋買入」登記第一筆</div>
                : <table style={{ width:'100%', borderCollapse:'collapse', fontSize:'var(--fs-nano)' }}>
                    <thead><tr style={{ borderBottom:'1px solid var(--ink)' }}>
                      {['日期','買入價','股數','手續費','均成本(含費)','備註',''].map(function(h){
                        return <th key={h} className="kai" style={{ padding:'3px 6px', textAlign:'left', fontSize:'var(--fs-nano)', fontWeight:400, color:'var(--ink-mute)' }}>{h}</th>;
                      })}
                    </tr></thead>
                    <tbody>
                      {pos.buyLegs.map(function(l,idx) {
                        return (
                          <tr key={l.id} style={{ borderBottom:'1px solid var(--paper-line)' }}>
                            <td style={{ padding:'4px 6px', color:'var(--ink-mute)', whiteSpace:'nowrap' }}>{l.date}</td>
                            <td className="num" style={{ padding:'4px 6px', fontWeight:600 }}>${l.price.toFixed(2)}</td>
                            <td className="num" style={{ padding:'4px 6px' }}>{l.qty.toLocaleString()}</td>
                            <td className="num" style={{ padding:'4px 6px', color:'var(--ink-mute)' }}>{l.fee ? '$'+(l.fee).toLocaleString() : '—'}</td>
                            <td className="num" style={{ padding:'4px 6px', color:'var(--gold-deep, #9a7b35)' }}>${avgRows[idx]}</td>
                            <td className="kai" style={{ padding:'4px 6px', color:'var(--ink-mute)', fontSize:'var(--fs-nano)' }}>{l.note}</td>
                            <td><button onClick={function(){dropLeg('buyLegs',l.id);}}
                              style={{ background:'none', border:'none', cursor:'pointer', color:'var(--ink-mute)', fontSize:12 }}>×</button></td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
              }
            </div>
            {/* 賣出 */}
            <div>
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:6 }}>
                <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', letterSpacing:'.08em' }}>Sell Legs · 了結記錄</span>
                <button onClick={function(){setShowSellForm(!showSellForm); setShowBuyForm(false);}}
                  style={{ background:'none', border:'1px solid var(--bull)', color:'var(--bull)', cursor:'pointer', fontSize:'var(--fs-nano)', padding:'1px 8px' }}>
                  {showSellForm ? '收起' : '＋ 了結'}
                </button>
              </div>
              {showSellForm && <LegForm type="sell" openQty={st.openQty} discount={discount} taxRate={taxRate} onAdd={function(f){ addSell(f); setShowSellForm(false); }}/>}
              {pos.sellLegs.length === 0
                ? <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic', padding:'6px 0' }}>尚未了結 — 浮動損益持續追蹤中</div>
                : <table style={{ width:'100%', borderCollapse:'collapse', fontSize:'var(--fs-nano)' }}>
                    <thead><tr style={{ borderBottom:'1px solid var(--ink)' }}>
                      {['日期','賣出價','股數','費+稅','損益','損益%','備註',''].map(function(h){
                        return <th key={h} className="kai" style={{ padding:'3px 6px', textAlign:'left', fontSize:'var(--fs-nano)', fontWeight:400, color:'var(--ink-mute)' }}>{h}</th>;
                      })}
                    </tr></thead>
                    <tbody>
                      {pos.sellLegs.map(function(l) {
                        var pnl = (l.price - st.adjAvgCost) * l.qty - (l.fee||0);
                        var pct = st.adjAvgCost > 0 ? (l.price/st.adjAvgCost-1)*100 : 0;
                        return (
                          <tr key={l.id} style={{ borderBottom:'1px solid var(--paper-line)' }}>
                            <td style={{ padding:'4px 6px', color:'var(--ink-mute)', whiteSpace:'nowrap' }}>{l.date}</td>
                            <td className="num" style={{ padding:'4px 6px', fontWeight:600 }}>${l.price.toFixed(2)}</td>
                            <td className="num" style={{ padding:'4px 6px' }}>{l.qty.toLocaleString()}</td>
                            <td className="num" style={{ padding:'4px 6px', color:'var(--ink-mute)' }}>{l.fee ? '$'+(l.fee).toLocaleString() : '—'}</td>
                            <td className="num" style={{ padding:'4px 6px', fontWeight:700, color: upDn(pnl) }}>{fmtPnl(pnl)}</td>
                            <td className="num" style={{ padding:'4px 6px', color: upDn(pct) }}>{fmtPct(pct)}</td>
                            <td className="kai" style={{ padding:'4px 6px', color:'var(--ink-mute)', fontSize:'var(--fs-nano)' }}>{l.note}</td>
                            <td><button onClick={function(){dropLeg('sellLegs',l.id);}}
                              style={{ background:'none', border:'none', cursor:'pointer', color:'var(--ink-mute)', fontSize:12 }}>×</button></td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
              }
            </div>
          </div>

          {/* 配息 */}
          <div style={{ marginBottom:14 }}>
            <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:6 }}>
              <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', letterSpacing:'.08em' }}>Dividends · 配息記錄</span>
              <button onClick={function(){setShowDivForm(!showDivForm);}}
                style={{ background:'none', border:'1px solid var(--gold)', color:'var(--gold-deep, #9a7b35)', cursor:'pointer', fontSize:'var(--fs-nano)', padding:'1px 8px' }}>
                {showDivForm ? '收起' : '＋ 配息'}
              </button>
              {st.divIncome > 0 && st.unrealizedPnl != null && (
                <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic', fontFamily:'Times New Roman,serif' }}>
                  含息浮動 <b style={{ color: upDn(st.unrealizedPnl + st.divIncome) }}>{fmtPnl(st.unrealizedPnl + st.divIncome)}</b>
                </span>
              )}
            </div>
            {showDivForm && <DivForm openQty={st.openQty} onAdd={function(f){ addLeg('divLegs', f); setShowDivForm(false); }}/>}
            {(pos.divLegs || []).length > 0 && (
              <table style={{ width:'100%', borderCollapse:'collapse', fontSize:'var(--fs-nano)' }}>
                <thead><tr style={{ borderBottom:'1px solid var(--ink)' }}>
                  {['除息日','每股股利','股數','入帳金額','備註',''].map(function(h){
                    return <th key={h} className="kai" style={{ padding:'3px 6px', textAlign:'left', fontSize:'var(--fs-nano)', fontWeight:400, color:'var(--ink-mute)' }}>{h}</th>;
                  })}
                </tr></thead>
                <tbody>
                  {pos.divLegs.map(function(l) {
                    return (
                      <tr key={l.id} style={{ borderBottom:'1px solid var(--paper-line)' }}>
                        <td style={{ padding:'4px 6px', color:'var(--ink-mute)', whiteSpace:'nowrap' }}>{l.date}</td>
                        <td className="num" style={{ padding:'4px 6px' }}>{l.perShare ? '$'+l.perShare : '—'}</td>
                        <td className="num" style={{ padding:'4px 6px' }}>{(l.qty||0).toLocaleString()}</td>
                        <td className="num" style={{ padding:'4px 6px', fontWeight:700, color:'var(--gold-deep, #9a7b35)' }}>+${Math.round(l.amount).toLocaleString()}</td>
                        <td className="kai" style={{ padding:'4px 6px', color:'var(--ink-mute)', fontSize:'var(--fs-nano)' }}>{l.note}</td>
                        <td><button onClick={function(){dropLeg('divLegs',l.id);}}
                          style={{ background:'none', border:'none', cursor:'pointer', color:'var(--ink-mute)', fontSize:12 }}>×</button></td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            )}
          </div>

          {/* 風險設定 */}
          {st.openQty > 0 && (
            <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:14, flexWrap:'wrap' }}>
              <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', letterSpacing:'.08em' }}>Risk · 停損停利</span>
              <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--bull)' }}>停損價</span>
              <input type="number" className="wl-edit-input num" style={{ width:90 }} placeholder="未設"
                defaultValue={pos.sl || ''} key={'sl'+pos.id+(pos.sl||'')}
                onBlur={function(e){ patch({ sl: parseFloat(e.target.value)||0 }); }}/>
              <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--gold-deep, #9a7b35)' }}>停利價</span>
              <input type="number" className="wl-edit-input num" style={{ width:90 }} placeholder="未設"
                defaultValue={pos.tp || ''} key={'tp'+pos.id+(pos.tp||'')}
                onBlur={function(e){ patch({ tp: parseFloat(e.target.value)||0 }); }}/>
              {hasLive && pos.sl > 0 && livePrice > pos.sl && (
                <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>距停損 {((livePrice - pos.sl) / livePrice * 100).toFixed(1)}%</span>
              )}
              {hasLive && pos.tp > 0 && livePrice < pos.tp && (
                <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>距停利 {((pos.tp - livePrice) / livePrice * 100).toFixed(1)}%</span>
              )}
            </div>
          )}

          {/* 動作列 */}
          <div style={{ display:'flex', gap:10, justifyContent:'flex-end', borderTop:'1px solid var(--paper-line)', paddingTop:10 }}>
            <button onClick={function(){ patch({ watch: true }); }} className="kai"
              style={{ background:'none', border:'1px solid var(--paper-line)', cursor:'pointer',
                fontSize:'var(--fs-nano)', padding:'5px 12px', color:'var(--ink-mute)' }}>
              移回觀察
            </button>
            <button onClick={function(){ if (confirm('確定刪除 ' + pos.code + ' 全部記錄？')) onDelete(); }} className="kai"
              style={{ background:'none', border:'1px solid var(--paper-line)', cursor:'pointer',
                fontSize:'var(--fs-nano)', padding:'5px 12px', color:'var(--ink-mute)' }}>
              刪除倉位
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// ── 持股配置帶（金箔比例條）──────────────────────────────
// ── 個人化每日研判（P3 留存）：大腦 regime/stance × 你的曝險，確定性組句 ──
const PV_LAMP = { good: 'var(--gold-deep, #9a7b35)', warn: 'var(--bear)', watch: '#c98a2b', neutral: 'var(--ink-mute)' };

function buildVerdictText(v) {
  var parts = [];
  if (v.brain && v.brain.regime && v.brain.stance) {
    parts.push('大盤今日' + v.brain.regime.label + '、研判' + v.brain.stance +
      (v.brain.confidence != null ? '（信心 ' + v.brain.confidence + '%）' : '') + '。');
  } else if (v.brain && v.brain.computing) {
    parts.push('大盤研判計算中，稍後更新。');
  } else {
    parts.push('大盤研判暫時載入不到，先看你的帳房狀態。');
  }
  parts.push('你持 ' + v.n + ' 檔、市值約 ' + (v.totalMkt / 10000).toFixed(1) + ' 萬，浮動 ' +
    (v.floatPct >= 0 ? '+' : '') + v.floatPct.toFixed(1) + '%' +
    (v.losers > 0 ? '，其中 ' + v.losers + ' 檔虧損' : '') + '。');
  var rg = v.brain && v.brain.regime ? v.brain.regime.label : null;
  if (v.brain && (v.brain.caution || rg === '空頭')) parts.push('盤勢偏弱、今日不宜加碼，優先控管風險與停損紀律。');
  else if (rg === '盤整') parts.push('盤整格局順勢動能弱，區間操作、別追高。');
  else if (rg === '多頭') parts.push('順勢偏多，但留意個股強弱分化，汰弱留強。');
  return parts.join('');
}

function PersonalVerdict({ holdings, pxMap, industryMap }) {
  var brainState = useState(null), brain = brainState[0], setBrain = brainState[1];
  var loadedState = useState(false), loaded = loadedState[0], setLoaded = loadedState[1];
  var plainState = useState({ status: 'idle', text: '' }), plain = plainState[0], setPlain = plainState[1];

  useEffect(function () {
    var dead = false;
    fetch('/api/stock?type=brain').then(function (r) { return r.json(); })
      .then(function (d) { if (!dead) { setBrain(d && !d.error ? d : null); setLoaded(true); } })
      .catch(function () { if (!dead) setLoaded(true); });
    return function () { dead = true; };
  }, []);

  var rows = useMemo(function () {
    return holdings.map(function (p) {
      var live = pxMap[p.code];
      var px = live && live.price > 0 ? live.price : null;
      var st = calcPos(p, px);
      var todayChg = live && live.chg != null ? live.chg : null;
      var floatPct = px && st.adjAvgCost > 0 ? (px / st.adjAvgCost - 1) * 100 : null;
      var sl = parseFloat(p.sl) || 0, tp = parseFloat(p.tp) || 0;
      var slHit = sl > 0 && px != null && px <= sl;
      var tpHit = tp > 0 && px != null && px >= tp;
      var distSL = sl > 0 && px != null ? (px / sl - 1) * 100 : null; // >0=現價高於停損
      var distTP = tp > 0 && px != null ? (tp / px - 1) * 100 : null; // >0=還有空間到停利
      var bStock = ((brain && brain.stocks) || []).filter(function (s) { return s.code === p.code; })[0];
      var boardVerdict = bStock ? bStock.boardVerdict : null;
      var pick = ((brain && brain.topPicks) || []).some(function (t) { return t.code === p.code; });
      var lamp = 'neutral';
      if (tpHit || pick || boardVerdict === '今日優質選擇') lamp = 'good';
      else if (slHit || boardVerdict === '避開') lamp = 'warn';
      else if (distSL != null && distSL >= 0 && distSL < 5) lamp = 'watch';
      return {
        code: p.code, name: p.name, px: px, todayChg: todayChg, floatPct: floatPct,
        mktValue: st.mktValue, cost: st.adjAvgCost * st.openQty, openQty: st.openQty,
        sl: sl, tp: tp, slHit: slHit, tpHit: tpHit, distSL: distSL, distTP: distTP,
        boardVerdict: boardVerdict, pick: pick, lamp: lamp,
      };
    }).filter(function (r) { return r.openQty > 0; });
  }, [holdings, pxMap, brain]);

  var hasHoldings = rows.length > 0;
  var totalMkt = rows.reduce(function (s, r) { return s + (r.mktValue || 0); }, 0);
  var totalCost = rows.reduce(function (s, r) { return s + (r.cost || 0); }, 0);
  var floatPct = totalCost > 0 ? (totalMkt / totalCost - 1) * 100 : 0;
  var todayPnlPct = totalMkt > 0
    ? rows.reduce(function (s, r) { return s + (r.todayChg || 0) * (r.mktValue || 0); }, 0) / totalMkt : 0;
  var losers = rows.filter(function (r) { return r.floatPct != null && r.floatPct < 0; }).length;

  // 產業集中度（C1 複用）
  var conc = useMemo(function () {
    if (!totalMkt) return null;
    var bySec = {};
    rows.forEach(function (r) { var sec = (industryMap || {})[r.code] || '其他'; bySec[sec] = (bySec[sec] || 0) + (r.mktValue || 0); });
    var top = Object.keys(bySec).map(function (k) { return { sector: k, w: bySec[k] / totalMkt * 100 }; })
      .sort(function (a, b) { return b.w - a.w; })[0];
    return top && top.w >= 40 ? top : null;
  }, [rows, industryMap, totalMkt]);

  // 提醒分點
  var reminders = [];
  var slHitCodes = rows.filter(function (r) { return r.slHit; }).map(function (r) { return r.code; });
  var tpHitCodes = rows.filter(function (r) { return r.tpHit; }).map(function (r) { return r.code; });
  var nearSL = rows.filter(function (r) { return !r.slHit && r.distSL != null && r.distSL >= 0 && r.distSL < 5; });
  var pickCodes = rows.filter(function (r) { return r.pick || r.boardVerdict === '今日優質選擇'; }).map(function (r) { return r.code; });
  var avoidCodes = rows.filter(function (r) { return r.boardVerdict === '避開'; }).map(function (r) { return r.code; });
  if (slHitCodes.length) reminders.push({ tone: 'warn', text: slHitCodes.join('、') + ' 已觸及停損，檢視是否執行紀律' });
  if (tpHitCodes.length) reminders.push({ tone: 'good', text: tpHitCodes.join('、') + ' 已達停利目標' });
  nearSL.forEach(function (r) { reminders.push({ tone: 'watch', text: r.code + ' 逼近停損（距 ' + r.distSL.toFixed(1) + '%）' }); });
  if (pickCodes.length) reminders.push({ tone: 'good', text: pickCodes.join('、') + ' 今日入選董事會精選' });
  if (avoidCodes.length) reminders.push({ tone: 'warn', text: avoidCodes.join('、') + ' 董事會示警「避開」，留意風險' });
  if (conc) reminders.push({ tone: 'watch', text: conc.sector + ' 佔 ' + conc.w.toFixed(0) + '%，單一產業偏集中' });

  var verdict = buildVerdictText({ brain: brain, n: rows.length, totalMkt: totalMkt, floatPct: floatPct, losers: losers });

  function askPlain() {
    setPlain({ status: 'loading', text: '' });
    var summary = {
      regime: brain && brain.regime ? brain.regime.label : null,
      stance: brain ? brain.stance : null,
      持股: rows.map(function (r) { return { code: r.code, name: r.name, 浮動: r.floatPct != null ? +r.floatPct.toFixed(1) : null, 今日: r.todayChg, 董事會: r.boardVerdict, 觸停損: r.slHit }; }),
    };
    window.claudeComplete({ messages: [{ role: 'user', content: '你是溫和的台股投資老師。請用 180 字以內大白話（繁體中文、說故事口吻、不條列），根據以下「大盤研判＋我的持股」講今天我的帳房要注意什麼、心態怎麼擺。務必提醒這只是研究參考、非投資建議。資料：' + JSON.stringify(summary) }] })
      .then(function (r) { var t = typeof r === 'string' ? r : (r && (r.text || r.content)) || JSON.stringify(r); setPlain({ status: 'done', text: t }); })
      .catch(function (e) { setPlain({ status: 'error', text: (e && e.message) || String(e) }); });
  }

  return (
    <div style={{ marginBottom: 26 }}>
      <window.SectionHead kicker="Your Daily Verdict" title="今日研判" titleEn="Your Ledger, Today"
        sub="大盤研判 × 你的持股曝險 — 確定性計算，每日更新，僅供研究參考" />
      {!hasHoldings ? (
        <div className="ph-empty kai">還沒有持股 — 先建倉，明天起這裡就會長出你的個人化每日研判。</div>
      ) : (
        <div style={{ border: '1px solid var(--paper-line)', borderLeft: '3px solid var(--gold)', background: 'var(--paper-bright)', padding: '16px 20px' }}>
          {/* 研判 narrative */}
          <div className="kai" style={{ fontSize: 15, lineHeight: 1.9, color: 'var(--ink)' }}>{verdict}</div>

          {/* 曝險 KPI */}
          <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', margin: '14px 0' }}>
            {[['持股市值', fmtMoney(totalMkt), 'var(--ink)'],
              ['浮動損益', fmtPct(floatPct), upDn(floatPct)],
              ['今日帳房', fmtPct(todayPnlPct), upDn(todayPnlPct)],
              ['持股檔數', rows.length + ' 檔', 'var(--ink)']].map(function (k) {
              return (
                <div key={k[0]} style={{ flex: '1 1 110px', border: '1px solid var(--paper-line)', background: 'var(--paper)', padding: '8px 12px' }}>
                  <div className="kai" style={{ fontSize: 'var(--fs-nano)', color: 'var(--ink-mute)', marginBottom: 3 }}>{k[0]}</div>
                  <div className="num" style={{ fontWeight: 700, fontSize: 17, color: k[2] }}>{k[1]}</div>
                </div>
              );
            })}
          </div>

          {/* 提醒分點 */}
          {reminders.length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 14 }}>
              {reminders.map(function (rm, i) {
                return (
                  <span key={i} className="kai" style={{ fontSize: 12, padding: '3px 10px', borderRadius: 3,
                    border: '1px solid ' + PV_LAMP[rm.tone], color: PV_LAMP[rm.tone],
                    background: rm.tone === 'warn' ? 'rgba(156,43,43,.06)' : 'transparent' }}>
                    {rm.tone === 'warn' ? '⚠ ' : rm.tone === 'good' ? '◎ ' : '· '}{rm.text}
                  </span>
                );
              })}
            </div>
          )}

          {/* 每檔持股研判列 */}
          <table className="wl-table">
            <thead><tr>
              <th>代號 / 名稱</th>
              <th className="r">現價 / 今日</th>
              <th className="r">浮動</th>
              <th className="r">距停損 / 停利</th>
              <th className="r">研判</th>
            </tr></thead>
            <tbody>
              {rows.map(function (r) {
                return (
                  <tr key={r.code}>
                    <td>
                      <a href={'個股技術分析.html?code=' + r.code} style={{ textDecoration: 'none', color: 'inherit' }}>
                        <span className="wl-ticker num">{r.code}</span>
                        <span className="kai" style={{ marginLeft: 10, fontSize: 13, color: 'var(--gold-deep, #9a7b35)' }}>{r.name}</span>
                      </a>
                    </td>
                    <td className="r num">
                      {r.px != null ? r.px.toLocaleString() : '—'}
                      {r.todayChg != null && <span style={{ marginLeft: 8, fontSize: 12, color: upDn(r.todayChg) }}>{fmtPct(r.todayChg)}</span>}
                    </td>
                    <td className="r num" style={{ color: r.floatPct != null ? upDn(r.floatPct) : 'var(--ink-mute)' }}>
                      {r.floatPct != null ? fmtPct(r.floatPct) : '—'}
                    </td>
                    <td className="r num" style={{ fontSize: 12, color: 'var(--ink-mute)' }}>
                      {r.sl > 0 ? (r.slHit ? '已觸停損' : (r.distSL != null ? r.distSL.toFixed(1) + '%' : '—')) : '—'}
                      {' / '}
                      {r.tp > 0 ? (r.tpHit ? '已達停利' : (r.distTP != null ? r.distTP.toFixed(1) + '%' : '—')) : '—'}
                    </td>
                    <td className="r">
                      <span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: '50%', background: PV_LAMP[r.lamp], verticalAlign: '0px', marginRight: 6 }} />
                      <span className="kai" style={{ fontSize: 'var(--fs-nano)', color: PV_LAMP[r.lamp] }}>
                        {r.boardVerdict === '今日優質選擇' ? '精選' : r.boardVerdict === '避開' ? '避開' : r.tpHit ? '停利' : r.slHit ? '停損' : r.pick ? '精選' : r.lamp === 'watch' ? '留意' : '持有'}
                      </span>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>

          {/* AI 白話（選配、使用者主動觸發，不燒背景額度）*/}
          <div style={{ marginTop: 12 }}>
            {plain.status === 'idle' && (
              <button className="kai" onClick={askPlain}
                style={{ fontSize: 12, padding: '5px 14px', cursor: 'pointer', background: 'none', border: '1px solid var(--paper-line)', color: 'var(--gold-deep, #9a7b35)' }}>
                ✎ 用大白話講今天我的帳房（AI）
              </button>
            )}
            {plain.status === 'loading' && <div className="kai" style={{ fontSize: 12, color: 'var(--gold-deep, #9a7b35)' }}>老師思考中…</div>}
            {plain.status === 'done' && <div className="kai" style={{ fontSize: 13, lineHeight: 1.9, color: 'var(--ink-soft, var(--ink))', marginTop: 4, paddingLeft: 12, borderLeft: '2px solid var(--gold)' }}>{plain.text}</div>}
            {plain.status === 'error' && <div className="kai" style={{ fontSize: 12, color: 'var(--bear)' }}>AI 暫時無法回應，稍後再試。</div>}
          </div>
          <div style={{ fontSize: 'var(--fs-nano)', color: 'var(--ink-mute)', fontStyle: 'italic', marginTop: 10 }}>
            研判為公開資料的機械化計算，非投資建議；本站不涉及下單。
          </div>
        </div>
      )}
    </div>
  );
}

function AllocBar({ holdings, pxMap, industryMap }) {
  var rows = holdings.map(function(p) {
    var live = pxMap[p.code];
    var st = calcPos(p, live && live.price > 0 ? live.price : null);
    var v = st.mktValue || st.adjAvgCost * st.openQty;
    return { code: p.code, name: p.name, value: v };
  }).filter(function(r){ return r.value > 0; }).sort(function(a,b){ return b.value - a.value; });
  var total = rows.reduce(function(s,r){ return s + r.value; }, 0);
  if (!total) return null;
  var TINTS = ['#9a7b35','#1a2438','#2a6b4c','#6e4a3a','#5e3a8e','#2c5e8e','#8a6a2a','#6b2a3c'];
  // C1 產業集中度：以 industryMap 分組，找不到歸「其他」
  var industry = industryMap || {};
  var bySec = {};
  rows.forEach(function(r){ var sec = industry[r.code] || '其他'; bySec[sec] = (bySec[sec] || 0) + r.value; });
  var secRows = Object.keys(bySec).map(function(k){ return { sector:k, value:bySec[k], w: bySec[k]/total*100 }; })
    .sort(function(a,b){ return b.value - a.value; });
  var topSec = secRows[0];
  var SECTOR_WARN = 40;
  var concentrated = topSec && topSec.w >= SECTOR_WARN;
  return (
    <div style={{ border:'1px solid var(--paper-line)', background:'var(--paper-bright)', padding:'14px 18px', marginBottom:22 }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:10 }}>
        <span className="kai" style={{ fontSize:13, letterSpacing:'.12em' }}>持股配置
          <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginLeft:8 }}>Allocation</span>
        </span>
        <span className="num" style={{ fontSize:13, color:'var(--ink-mute)' }}>
          {rows.length} 檔 · 市值 {fmtMoney(total)}
        </span>
      </div>
      <div style={{ display:'flex', height:18, overflow:'hidden', border:'1px solid var(--paper-line)' }}>
        {rows.map(function(r, i) {
          var w = r.value / total * 100;
          return (
            <div key={r.code} title={r.code + ' ' + r.name + ' ' + w.toFixed(1) + '%'}
              style={{ width: w + '%', background: TINTS[i % TINTS.length], opacity:.82,
                borderRight:'1px solid var(--paper-bright)', transition:'opacity .2s' }}
              onMouseEnter={function(e){ e.currentTarget.style.opacity = 1; }}
              onMouseLeave={function(e){ e.currentTarget.style.opacity = .82; }}/>
          );
        })}
      </div>
      <div style={{ display:'flex', flexWrap:'wrap', gap:'4px 16px', marginTop:8 }}>
        {rows.map(function(r, i) {
          return (
            <span key={r.code} style={{ fontSize:'var(--fs-nano)', color:'var(--ink-soft, var(--ink))' }}>
              <span style={{ display:'inline-block', width:8, height:8, background:TINTS[i % TINTS.length], marginRight:5, verticalAlign:'-1px' }}/>
              {r.code} <span className="kai" style={{ color:'var(--ink-mute)' }}>{r.name}</span>
              <b className="num" style={{ marginLeft:4 }}>{(r.value/total*100).toFixed(1)}%</b>
            </span>
          );
        })}
      </div>

      {/* C1 產業配置＋集中度警示 */}
      <div style={{ marginTop:14, paddingTop:12, borderTop:'1px solid var(--paper-line)' }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:8 }}>
          <span className="kai" style={{ fontSize:13, letterSpacing:'.12em' }}>產業配置
            <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginLeft:8 }}>By Sector</span>
          </span>
          <span className="num" style={{ fontSize:13, color:'var(--ink-mute)' }}>{secRows.length} 類</span>
        </div>
        <div style={{ display:'flex', height:18, overflow:'hidden', border:'1px solid var(--paper-line)' }}>
          {secRows.map(function(r, i){
            return <div key={r.sector} title={r.sector + ' ' + r.w.toFixed(1) + '%'}
              style={{ width: r.w + '%', background: TINTS[i % TINTS.length], opacity:.82, borderRight:'1px solid var(--paper-bright)' }}/>;
          })}
        </div>
        <div style={{ display:'flex', flexWrap:'wrap', gap:'4px 16px', marginTop:8 }}>
          {secRows.map(function(r, i){
            return (
              <span key={r.sector} style={{ fontSize:'var(--fs-nano)', color:'var(--ink-soft, var(--ink))' }}>
                <span style={{ display:'inline-block', width:8, height:8, background:TINTS[i % TINTS.length], marginRight:5, verticalAlign:'-1px' }}/>
                <span className="kai">{r.sector}</span>
                <b className="num" style={{ marginLeft:4 }}>{r.w.toFixed(1)}%</b>
              </span>
            );
          })}
        </div>
        {concentrated && (
          <div style={{ marginTop:10, padding:'7px 12px', background:'rgba(156,43,43,.08)', border:'1px solid rgba(156,43,43,.35)', fontSize:12, color:'var(--bear)' }}>
            ⚠ 產業集中：{topSec.sector} 佔 {topSec.w.toFixed(0)}%——單一產業曝險偏高，留意同向連動風險
          </div>
        )}
      </div>
    </div>
  );
}

// ── 月度結算 P&L 長條 ────────────────────────────────────
function MonthlyChart({ portfolio }) {
  var monthly = {};
  portfolio.forEach(function(p) {
    var st = calcPos(p, null);
    p.sellLegs.forEach(function(l) {
      var mon = l.date.slice(0, 7);
      monthly[mon] = (monthly[mon] || 0) + (l.price - st.adjAvgCost) * l.qty - (l.fee||0);
    });
    (p.divLegs || []).forEach(function(l) {
      var mon = l.date.slice(0, 7);
      monthly[mon] = (monthly[mon] || 0) + (l.amount || 0);
    });
  });
  var mons = Object.keys(monthly).sort().slice(-12);
  if (!mons.length) return null;
  var max = Math.max.apply(null, mons.map(function(m){ return Math.abs(monthly[m]); })) || 1;
  return (
    <div style={{ border:'1px solid var(--paper-line)', background:'var(--paper-bright)', padding:'14px 18px', marginBottom:20 }}>
      <div className="kai" style={{ fontSize:13, letterSpacing:'.12em', marginBottom:10 }}>月度結算
        <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginLeft:8 }}>Monthly P&L · 含股利</span>
      </div>
      <div style={{ display:'flex', alignItems:'flex-end', gap:8, height:110 }}>
        {mons.map(function(m) {
          var v = monthly[m];
          var h = Math.max(3, Math.abs(v) / max * 84);
          return (
            <div key={m} style={{ flex:1, textAlign:'center' }} title={m + ' ' + fmtPnl(v)}>
              <div className="num" style={{ fontSize:'var(--fs-nano)', color: upDn(v), marginBottom:2 }}>{(v/1000).toFixed(v >= 10000 || v <= -10000 ? 0 : 1)}K</div>
              <div style={{ height:h, background: upDn(v), opacity:.78, margin:'0 auto', maxWidth:38 }}/>
              <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:4, fontStyle:'italic' }}>{m.slice(2)}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── 結算績效區 ───────────────────────────────────────────
function PerfSection({ portfolio, pxMap, onExport }) {
  var stats = portfolio.map(function(p) {
    var live = pxMap[p.code];
    return { p: p, st: calcPos(p, live && live.price > 0 ? live.price : null) };
  });
  var closed = stats.filter(function(x){ return x.st.openQty <= 0 && x.st.sellQty > 0; });
  var pnls = closed.map(function(x){ return x.st.realizedPnl + x.st.divIncome; });
  var totalReal = stats.reduce(function(s,x){ return s + x.st.realizedPnl; }, 0);
  var totalDiv  = stats.reduce(function(s,x){ return s + x.st.divIncome; }, 0);
  var totalFees = stats.reduce(function(s,x){ return s + x.st.totalFees; }, 0);
  var wins = pnls.filter(function(v){ return v > 0; });
  var losses = pnls.filter(function(v){ return v < 0; });
  var winRate = pnls.length ? wins.length / pnls.length * 100 : 0;
  var avgWin  = wins.length ? wins.reduce(function(s,v){return s+v;},0) / wins.length : 0;
  var avgLoss = losses.length ? Math.abs(losses.reduce(function(s,v){return s+v;},0) / losses.length) : 0;
  var pf = avgLoss > 0 ? (avgWin/avgLoss).toFixed(2) : (wins.length ? '∞' : '—');
  var avgHold = closed.length
    ? Math.round(closed.reduce(function(s,x){ return s + (x.st.holdDays || 0); }, 0) / closed.length) : null;

  // C2 Kelly：用自己交易史的勝率×盈虧比，f* = W − (1−W)/R；預設半 Kelly 求穩
  var KELLY_MIN_SAMPLE = 20;
  var kW = winRate / 100;
  var kR = avgLoss > 0 ? avgWin / avgLoss : 0;
  var kFull = kR > 0 ? kW - (1 - kW) / kR : 0;
  var kellyPct = Math.max(0, kFull) * 100;      // 全 Kelly %
  var halfKelly = kellyPct / 2;                  // 半 Kelly（建議）
  var kellyEnough = pnls.length >= KELLY_MIN_SAMPLE;
  var kellyOk = pnls.length > 0 && avgLoss > 0 && kFull > 0;
  var kellyVal, kellySub;
  if (!pnls.length) { kellyVal = '—'; kellySub = '尚無平倉樣本'; }
  else if (avgLoss <= 0) { kellyVal = '—'; kellySub = '尚無虧損樣本，無法估算'; }
  else if (kFull <= 0) { kellyVal = '0%'; kellySub = '無正期望值，不宜加碼'; }
  else { kellyVal = halfKelly.toFixed(0) + '%'; kellySub = (kellyEnough ? '' : '樣本' + pnls.length + '筆偏少·') + '全 Kelly ' + kellyPct.toFixed(0) + '%'; }

  var KPI = function({ en, zh, val, color, sub }) {
    return (
      <div className="kpi-card" style={{ padding:'12px 14px', border:'1px solid var(--paper-line)', background:'var(--paper-bright)', flex:'1 1 118px' }}>
        <div style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', letterSpacing:'.1em', color:'var(--ink-mute)' }}>{en}</div>
        <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:5 }}>{zh}</div>
        <div className="num" style={{ fontWeight:700, fontSize:19, color: color||'var(--ink)' }}>{val}</div>
        {sub && <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2 }}>{sub}</div>}
      </div>
    );
  };

  return (
    <div>
      <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginBottom:18 }}>
        <KPI en="Realized P&L" zh="已實現損益" val={totalReal ? fmtPnl(totalReal) : '—'} color={totalReal ? upDn(totalReal) : undefined}/>
        <KPI en="Dividends" zh="累計股利" val={totalDiv > 0 ? '+$'+Math.round(totalDiv).toLocaleString() : '—'} color="var(--gold-deep, #9a7b35)"/>
        <KPI en="Win Rate" zh="勝率(含息)" val={pnls.length ? winRate.toFixed(1)+'%' : '—'}
          color={pnls.length ? upDn(winRate - 50) : undefined} sub={pnls.length ? wins.length+'贏 / '+losses.length+'輸' : '尚無平倉樣本'}/>
        <KPI en="Profit Factor" zh="盈虧比" val={pnls.length ? pf+'×' : '—'} color={parseFloat(pf) >= 1.5 ? upDn(1) : undefined}/>
        <KPI en="Avg Hold" zh="平均持有" val={avgHold != null ? avgHold+'天' : '—'}/>
        <KPI en="Total Fees" zh="累計費用" val={totalFees > 0 ? fmtMoney(totalFees) : '—'} sub="手續費＋證交稅"/>
        <KPI en="Kelly (½)" zh="建議部位" val={kellyVal}
          color={kellyOk && kellyEnough ? upDn(1) : undefined} sub={kellySub}/>
      </div>
      {pnls.length > 0 && avgLoss > 0 && (
        <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:-12, marginBottom:16, fontStyle:'italic' }}>
          Kelly 部位＝以你自己的實現勝率×盈虧比估算單筆下注比例，取半 Kelly 求穩；僅數學參考，非投資建議。
        </div>
      )}

      <MonthlyChart portfolio={portfolio}/>

      {/* 平倉記錄帳 */}
      {closed.length > 0 && (
        <div style={{ border:'1px solid var(--paper-line)', background:'var(--paper-bright)', padding:'14px 18px', marginBottom:18 }}>
          <div className="kai" style={{ fontSize:13, letterSpacing:'.12em', marginBottom:10 }}>結清帳目
            <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginLeft:8 }}>Closed Trades</span>
          </div>
          <table style={{ width:'100%', borderCollapse:'collapse', fontSize:12 }}>
            <thead><tr style={{ borderBottom:'1.5px solid var(--ink)' }}>
              {['代號','名稱','持有期間','天數','損益','報酬%','股利','含息合計'].map(function(h){
                return <th key={h} className="kai" style={{ padding:'4px 8px', textAlign:'left', fontSize:'var(--fs-nano)', fontWeight:400, color:'var(--ink-mute)' }}>{h}</th>;
              })}
            </tr></thead>
            <tbody>
              {closed.sort(function(a,b){ return (b.st.lastSell||'').localeCompare(a.st.lastSell||''); }).map(function(x) {
                var cost = x.st.adjAvgCost * x.st.sellQty;
                var retPct = cost > 0 ? x.st.realizedPnl / cost * 100 : 0;
                var total = x.st.realizedPnl + x.st.divIncome;
                return (
                  <tr key={x.p.id} style={{ borderBottom:'1px solid var(--paper-line)' }}>
                    <td className="num" style={{ padding:'6px 8px', fontWeight:700 }}>
                      <a href={'個股技術分析.html?code='+x.p.code} style={{ color:'var(--ink)', textDecoration:'none' }}>{x.p.code}</a>
                    </td>
                    <td className="kai" style={{ padding:'6px 8px', color:'var(--ink-soft, var(--ink))' }}>{x.p.name}</td>
                    <td style={{ padding:'6px 8px', color:'var(--ink-mute)', fontSize:'var(--fs-nano)' }}>{x.st.firstBuy} ~ {x.st.lastSell}</td>
                    <td className="num" style={{ padding:'6px 8px' }}>{x.st.holdDays != null ? x.st.holdDays : '—'}</td>
                    <td className="num" style={{ padding:'6px 8px', fontWeight:700, color: upDn(x.st.realizedPnl) }}>{fmtPnl(x.st.realizedPnl)}</td>
                    <td className="num" style={{ padding:'6px 8px', color: upDn(retPct) }}>{fmtPct(retPct)}</td>
                    <td className="num" style={{ padding:'6px 8px', color:'var(--gold-deep, #9a7b35)' }}>{x.st.divIncome > 0 ? '+$'+Math.round(x.st.divIncome).toLocaleString() : '—'}</td>
                    <td className="num" style={{ padding:'6px 8px', fontWeight:700, color: upDn(total) }}>{fmtPnl(total)}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}

      <div style={{ display:'flex', justifyContent:'flex-end' }}>
        <button onClick={onExport} className="kai"
          style={{ background:'none', border:'1px solid var(--paper-line)', cursor:'pointer',
            fontSize:'var(--fs-nano)', padding:'5px 14px', color:'var(--ink-soft, var(--ink))' }}>
          ⤓ 匯出 CSV 帳冊
        </button>
      </div>
    </div>
  );
}

// ── 股利所得課稅方式試算（114年度稅率級距，僅供參考，正式申報請洽國稅局/會計師）──
// 114年度綜合所得稅累進稅率：財政部賦稅署公告
const INCOME_BRACKETS_114 = [
  { limit: 590000,   rate: 0.05, deduct: 0 },
  { limit: 1330000,  rate: 0.12, deduct: 29500 },
  { limit: 2660000,  rate: 0.20, deduct: 118300 },
  { limit: 4980000,  rate: 0.30, deduct: 384300 },
  { limit: Infinity, rate: 0.40, deduct: 1080300 },
];
function progressiveTax(income) {
  if (income <= 0) return 0;
  var b = INCOME_BRACKETS_114.find(function(x){ return income <= x.limit; }) || INCOME_BRACKETS_114[INCOME_BRACKETS_114.length-1];
  return Math.max(0, income * b.rate - b.deduct);
}
function calcDividendTaxChoice(otherIncome, dividend) {
  if (dividend <= 0) return null;
  var creditCap = 80000;
  var credit = Math.min(dividend * 0.085, creditCap);
  var combinedTax = progressiveTax(otherIncome + dividend) - credit;
  var separateTax = progressiveTax(otherIncome) + dividend * 0.28;
  var better = combinedTax <= separateTax ? 'combined' : 'separate';
  return {
    combinedTax: Math.round(combinedTax), separateTax: Math.round(separateTax),
    credit: Math.round(credit), better: better,
    diff: Math.round(Math.abs(combinedTax - separateTax)),
  };
}

function TaxSection({ portfolio }) {
  var [otherIncome, setOtherIncome] = useState(function(){
    return parseFloat(localStorage.getItem('tw_tax_other_income') || '0') || 0;
  });
  function updateOtherIncome(v) {
    var n = parseFloat(v) || 0;
    setOtherIncome(n);
    localStorage.setItem('tw_tax_other_income', String(n));
  }

  var thisYear = new Date().getFullYear();
  var divRecords = [];
  portfolio.forEach(function(p) {
    (p.divLegs || []).forEach(function(l) {
      var y = (l.date || '').slice(0, 4);
      if (y && parseInt(y) === thisYear) {
        divRecords.push({ code: p.code, name: p.name, date: l.date, amount: l.amount || 0 });
      }
    });
  });
  var totalDividend = divRecords.reduce(function(s, r){ return s + r.amount; }, 0);
  var nhiFlags = divRecords.filter(function(r){ return r.amount > 20000; });
  var nhiTotal = nhiFlags.reduce(function(s, r){ return s + r.amount * 0.0211; }, 0);

  var result = calcDividendTaxChoice(otherIncome, totalDividend);

  return (
    <div>
      <p className="kai" style={{ fontSize:12, color:'var(--ink-mute)', lineHeight:1.9, marginBottom:16 }}>
        本試算依 114 年度綜合所得稅級距（財政部賦稅署公告）計算，僅供參考，實際申報以國稅局系統及會計師意見為準。所有輸入僅存在瀏覽器本機，不會上傳。
      </p>

      <div style={{ display:'flex', gap:16, flexWrap:'wrap', marginBottom:20 }}>
        <div className="kpi-card" style={{ padding:'12px 14px', border:'1px solid var(--paper-line)', background:'var(--paper-bright)', flex:'1 1 160px' }}>
          <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:5 }}>{thisYear}年已收股利（自動彙總）</div>
          <div className="num" style={{ fontWeight:700, fontSize:19 }}>{totalDividend > 0 ? '$'+Math.round(totalDividend).toLocaleString() : '—'}</div>
        </div>
        <div className="kpi-card" style={{ padding:'12px 14px', border:'1px solid var(--paper-line)', background:'var(--paper-bright)', flex:'1 1 200px' }}>
          <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:5 }}>其他年度所得（不含股利，手動輸入）</div>
          <input type="number" value={otherIncome || ''} onChange={function(e){ updateOtherIncome(e.target.value); }}
            placeholder="例：薪資所得淨額"
            style={{ width:'100%', padding:'4px 6px', border:'1px solid var(--paper-line)', fontFamily:'Times New Roman,serif', fontSize:16 }} />
        </div>
      </div>

      {!result && (
        <div className="ph-empty kai">今年尚無股利記錄可試算 — 到「持有中」倉位登記配息後這裡會自動帶入</div>
      )}

      {result && (
        <>
          <div style={{ display:'flex', gap:16, flexWrap:'wrap', marginBottom:16 }}>
            <div className="kpi-card" style={{ padding:'12px 14px', border: result.better==='combined' ? '2px solid var(--gold)' : '1px solid var(--paper-line)', background:'var(--paper-bright)', flex:'1 1 200px' }}>
              <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:5 }}>合併課稅（股利抵減 8.5%，上限8萬）</div>
              <div className="num" style={{ fontWeight:700, fontSize:19 }}>{fmtMoney(result.combinedTax)}</div>
              <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2 }}>可抵減稅額 {fmtMoney(result.credit)}</div>
            </div>
            <div className="kpi-card" style={{ padding:'12px 14px', border: result.better==='separate' ? '2px solid var(--gold)' : '1px solid var(--paper-line)', background:'var(--paper-bright)', flex:'1 1 200px' }}>
              <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:5 }}>分離課稅（股利獨立按28%）</div>
              <div className="num" style={{ fontWeight:700, fontSize:19 }}>{fmtMoney(result.separateTax)}</div>
            </div>
          </div>
          <div style={{ padding:'12px 16px', background:'var(--paper)', border:'1px solid var(--ink)', marginBottom:20 }}>
            <span className="kai" style={{ fontSize:13 }}>建議選擇：</span>
            <b className="kai" style={{ fontSize:14, marginLeft:6, color:'var(--gold-deep, #9a7b35)' }}>
              {result.better === 'combined' ? '合併課稅' : '分離課稅'}
            </b>
            <span style={{ fontSize:12, color:'var(--ink-mute)', marginLeft:10 }}>省下約 {fmtMoney(result.diff)}</span>
          </div>
        </>
      )}

      {nhiFlags.length > 0 && (
        <div style={{ border:'1px solid var(--paper-line)', background:'var(--paper-bright)', padding:'14px 18px' }}>
          <div className="kai" style={{ fontSize:13, letterSpacing:'.12em', marginBottom:10 }}>健保補充保費提醒
            <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginLeft:8 }}>NHI Supplementary Premium</span>
          </div>
          <p className="kai" style={{ fontSize:12, color:'var(--ink-mute)', marginBottom:10 }}>
            單筆股利超過 2 萬元，發放時會被直接扣繳 2.11% 補充保費（與課稅方式選擇無關，兩種都要扣）：
          </p>
          <table style={{ width:'100%', borderCollapse:'collapse', fontSize:12 }}>
            <thead><tr style={{ borderBottom:'1.5px solid var(--ink)' }}>
              {['代號','名稱','發放日','單筆股利','補充保費(2.11%)'].map(function(h){
                return <th key={h} className="kai" style={{ padding:'4px 8px', textAlign:'left', fontSize:'var(--fs-nano)', fontWeight:400, color:'var(--ink-mute)' }}>{h}</th>;
              })}
            </tr></thead>
            <tbody>
              {nhiFlags.map(function(r, i) {
                return (
                  <tr key={i} style={{ borderBottom:'1px solid var(--paper-line)' }}>
                    <td className="num" style={{ padding:'6px 8px', fontWeight:700 }}>{r.code}</td>
                    <td className="kai" style={{ padding:'6px 8px' }}>{r.name}</td>
                    <td style={{ padding:'6px 8px', color:'var(--ink-mute)', fontSize:'var(--fs-nano)' }}>{r.date}</td>
                    <td className="num" style={{ padding:'6px 8px' }}>{fmtMoney(r.amount)}</td>
                    <td className="num" style={{ padding:'6px 8px', color:'var(--gold-deep, #9a7b35)' }}>{fmtMoney(r.amount * 0.0211)}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          <div style={{ textAlign:'right', marginTop:10, fontSize:12 }}>
            <span className="kai" style={{ color:'var(--ink-mute)' }}>合計補充保費：</span>
            <b className="num">{fmtMoney(nhiTotal)}</b>
          </div>
        </div>
      )}
    </div>
  );
}

// ── 主組件 ───────────────────────────────────────────────
function App() {
  var [portfolio, setPortfolio] = useState(loadPortfolio);
  var [nameMap, setNameMap] = useState({});
  var [closeMap, setCloseMap] = useState({});
  var [livePx, setLivePx] = useState({});
  var [industryMap, setIndustryMap] = useState({});
  var [addCode, setAddCode] = useState('');
  var [discount, setDiscount] = useState(function(){ return parseFloat(localStorage.getItem('tw_fee_discount')||'0.6'); });
  var [taxMode, setTaxMode] = useState(function(){
    var m = localStorage.getItem('tw_fee_taxmode');
    if (m) return m;
    return localStorage.getItem('tw_fee_etf') === '1' ? 'etf' : 'stock';
  });
  var taxRate = TAX_RATES[taxMode] != null ? TAX_RATES[taxMode] : 0.003;

  function save(arr) { setPortfolio(arr); savePortfolio(arr); }
  function updatePos(id, next) { save(portfolio.map(function(p){ return p.id === id ? next : p; })); }
  function deletePos(id) { save(portfolio.filter(function(p){ return p.id !== id; })); }

  // 全市場清單：名稱對照 + 收盤備援價
  useEffect(function() {
    fetch('/api/stock?type=list&market=all')
      .then(function(r){ return r.json(); })
      .then(function(list) {
        if (!Array.isArray(list)) return;
        var nMap = {}, cMap = {};
        list.forEach(function(s) {
          if (s.Name) nMap[s.Code] = s.Name;
          var close = parseFloat(s.ClosingPrice) || 0;
          var change = parseFloat(s.Change) || 0;
          var prev = close - change;
          if (close > 0) cMap[s.Code] = { price: close, chg: prev > 0 ? +((change/prev)*100).toFixed(2) : 0 };
        });
        setNameMap(nMap); setCloseMap(cMap);
      })
      .catch(function(){});
  }, []);

  // 產業對照（C1 集中度）：全市場代號→產業，抓一次（沿用 scan.jsx 慣例）
  useEffect(function() {
    fetch('/industry_codes.json')
      .then(function(r){ return r.json(); })
      .then(function(m){ if (m && typeof m === 'object') setIndustryMap(m); })
      .catch(function(){});
  }, []);

  // 即時報價：TWSE MIS，全部倉位代號，盤中 30s 刷新
  var qhN = window.useQhRefresh ? window.useQhRefresh() : 0;
  var codesKey = portfolio.map(function(p){ return p.code; })
    .filter(function(c,i,a){ return a.indexOf(c) === i; }).join(',');
  useEffect(function() {   // QuoteHub v2 訂閱(上限 24 檔沿用),主迴圈統一排程
    if (!codesKey) return;
    return window.QuoteHub.subscribe({
      type: 'peer_prices', codes: codesKey.split(',').slice(0, 24),
      onData: function(d) {
        var out = {};
        ((d && d.msgArray) || []).forEach(function(q) {
          var code = (q.ch || q.c || '').split('.')[0].replace(/^(tse_|otc_)/, '');
          var z = parseFloat(q.z), y = parseFloat(q.y);
          if (!z || isNaN(z)) z = parseFloat(String(q.b||'').split('_')[0]) || y;
          if (code && z > 0) out[code] = { price: z, chg: (y > 0) ? +(((z - y) / y) * 100).toFixed(2) : 0 };
        });
        if (Object.keys(out).length) setLivePx(function(prev){ return Object.assign({}, prev, out); });
      }
    });
  }, [codesKey, qhN]);

  var pxMap = useMemo(function() {
    return Object.assign({}, closeMap, livePx);
  }, [closeMap, livePx]);

  // 分組
  var holdings = portfolio.filter(function(p){
    if (p.watch) return false;
    var st = calcPos(p, null);
    return !(st.openQty <= 0 && st.sellQty > 0); // 已結清的歸入績效
  });
  var watching = portfolio.filter(function(p){ return p.watch; });
  var closedN  = portfolio.filter(function(p){
    var st = calcPos(p, null);
    return !p.watch && st.openQty <= 0 && st.sellQty > 0;
  }).length;

  // 總帳數字
  var hero = useMemo(function() {
    var mkt = 0, cost = 0, unreal = 0, today = 0, real = 0, div = 0;
    portfolio.forEach(function(p) {
      var live = pxMap[p.code];
      var st = calcPos(p, live && live.price > 0 ? live.price : null);
      real += st.realizedPnl;
      div  += st.divIncome;
      if (p.watch || st.openQty <= 0) return;
      var v = st.mktValue || st.adjAvgCost * st.openQty;
      mkt  += v;
      cost += st.adjAvgCost * st.openQty;
      if (st.unrealizedPnl != null) unreal += st.unrealizedPnl;
      if (live && live.price > 0 && live.chg != null) {
        var prev = live.price / (1 + live.chg/100);
        today += (live.price - prev) * st.openQty;
      }
    });
    return { mkt: mkt, cost: cost, unreal: unreal, unrealPct: cost > 0 ? unreal/cost*100 : 0, today: today, real: real, div: div };
  }, [portfolio, pxMap]);

  // 加入：觀察 or 直接建倉
  function addStock(asWatch) {
    var code = addCode.trim().toUpperCase();
    if (!code) return;
    if (portfolio.some(function(p){ return p.code === code; })) { setAddCode(''); return; }
    save([].concat(portfolio, [{
      id: 'pf_' + Date.now(), code: code, name: nameMap[code] || code, watch: !!asWatch,
      buyLegs: [], sellLegs: [], divLegs: [], sl: 0, tp: 0, note: '',
    }]));
    setAddCode('');
  }

  // CSV 匯出（含 BOM，Excel 可開）
  function exportCSV() {
    var rows = [['代號','名稱','類型','日期','價格/每股股利','股數','手續費','金額','備註']];
    portfolio.forEach(function(p) {
      p.buyLegs.forEach(function(l){ rows.push([p.code,p.name,'買入',l.date,l.price,l.qty,l.fee||0,-(l.price*l.qty+(l.fee||0)),l.note||'']); });
      p.sellLegs.forEach(function(l){ rows.push([p.code,p.name,'賣出',l.date,l.price,l.qty,l.fee||0,l.price*l.qty-(l.fee||0),l.note||'']); });
      (p.divLegs||[]).forEach(function(l){ rows.push([p.code,p.name,'配息',l.date,l.perShare||'',l.qty||'',0,l.amount,l.note||'']); });
    });
    var csv = '﻿' + rows.map(function(r){
      return r.map(function(c){ var s=String(c==null?'':c); return /[,"\n]/.test(s) ? '"'+s.replace(/"/g,'""')+'"' : s; }).join(',');
    }).join('\r\n');
    var a = document.createElement('a');
    a.href = URL.createObjectURL(new Blob([csv], { type:'text/csv;charset=utf-8' }));
    a.download = 'portfolio_' + new Date().toISOString().slice(0,10) + '.csv';
    a.click();
    URL.revokeObjectURL(a.href);
  }

  var heroHasData = holdings.length > 0;

  return (
    <>
      {/* ═══ 總帳 Hero ═══ */}
      <div className="ph-hero">
        <div className="ph-hero-left">
          <div className="ph-hero-kicker">— The Ledger House —</div>
          <h1 className="ph-hero-title kai">持股帳房</h1>
          <div className="ph-hero-date">{new Date().toLocaleDateString('zh-TW', { year:'numeric', month:'long', day:'numeric' })} · 持有 {holdings.length} 檔 · 觀察 {watching.length} 檔 · 結清 {closedN} 筆</div>
        </div>
        <div className="ph-hero-right">
          <div className="ph-hero-main">
            <div className="ph-hero-lbl">Portfolio Value · 持股市值</div>
            <div className="ph-hero-val num">{heroHasData ? fmtMoney(hero.mkt) : '—'}</div>
          </div>
          <div className="ph-hero-stats">
            <div className="ph-stat">
              <span className="ph-stat-lbl">今日損益</span>
              <span className="ph-stat-val num" style={{ color: upDn(hero.today) }}>{heroHasData && hero.today ? fmtPnl(hero.today) : '—'}</span>
            </div>
            <div className="ph-stat">
              <span className="ph-stat-lbl">浮動損益</span>
              <span className="ph-stat-val num" style={{ color: upDn(hero.unreal) }}>
                {heroHasData ? fmtPnl(hero.unreal) + '（' + fmtPct(hero.unrealPct) + '）' : '—'}
              </span>
            </div>
            <div className="ph-stat">
              <span className="ph-stat-lbl">已實現</span>
              <span className="ph-stat-val num" style={{ color: hero.real ? upDn(hero.real) : 'var(--ink-mute)' }}>{hero.real ? fmtPnl(hero.real) : '—'}</span>
            </div>
            <div className="ph-stat">
              <span className="ph-stat-lbl">累計股利</span>
              <span className="ph-stat-val num" style={{ color:'var(--gold-deep, #9a7b35)' }}>{hero.div > 0 ? '+$'+Math.round(hero.div).toLocaleString() : '—'}</span>
            </div>
          </div>
        </div>
      </div>

      {/* 個人化每日研判 */}
      <PersonalVerdict holdings={holdings} pxMap={pxMap} industryMap={industryMap} />

      {/* 快速加入 */}
      <div className="ph-add">
        <input className="ph-add-input num" placeholder="輸入代號加入，如 2330 · 3324 …"
          value={addCode}
          onChange={function(e){ setAddCode(e.target.value); }}
          onKeyDown={function(e){ if (e.key === 'Enter') addStock(true); }}/>
        {addCode.trim() && nameMap[addCode.trim().toUpperCase()] && (
          <span className="kai" style={{ alignSelf:'center', padding:'0 10px', fontSize:12, color:'var(--gold-deep, #9a7b35)', whiteSpace:'nowrap' }}>
            {nameMap[addCode.trim().toUpperCase()]}
            {pxMap[addCode.trim().toUpperCase()] ? ' · ' + pxMap[addCode.trim().toUpperCase()].price.toLocaleString() : ''}
          </span>
        )}
        <button className="ph-add-btn watch kai" onClick={function(){ addStock(true); }}>＋ 觀察</button>
        <button className="ph-add-btn hold kai" onClick={function(){ addStock(false); }}>＋ 建倉</button>
      </div>

      {/* 費率設定 */}
      <div className="fee-bar" style={{ display:'flex', alignItems:'center', gap:16, padding:'8px 14px', background:'var(--paper-bright)', border:'1px solid var(--paper-line)', marginBottom:26, flexWrap:'wrap' }}>
        <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>元大費率：</span>
        <div style={{ display:'flex', alignItems:'center', gap:6 }}>
          <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>手續費折扣</span>
          {[['28折',0.28],['4折',0.4],['5折',0.5],['6折',0.6],['65折',0.65],['原始',1]].map(function(d){
            var active = Math.abs(discount - d[1]) < 0.01;
            return (
              <button key={d[0]} className="kai" onClick={function(){ setDiscount(d[1]); localStorage.setItem('tw_fee_discount', String(d[1])); }}
                style={{ padding:'2px 10px', fontSize:'var(--fs-nano)', cursor:'pointer',
                  background: active ? 'var(--ink)' : 'none',
                  color:      active ? 'var(--paper)' : 'var(--ink-mute)',
                  border:     active ? 'none' : '1px solid var(--paper-line)' }}>{d[0]}</button>
            );
          })}
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:6 }}>
          <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>證交稅</span>
          {[['股票 0.3%','stock'],['ETF 0.1%','etf'],['當沖 0.15%','dt']].map(function(d){
            var active = taxMode === d[1];
            return (
              <button key={d[0]} className="kai" onClick={function(){ setTaxMode(d[1]); localStorage.setItem('tw_fee_taxmode', d[1]); }}
                style={{ padding:'2px 10px', fontSize:'var(--fs-nano)', cursor:'pointer',
                  background: active ? 'var(--ink)' : 'none',
                  color:      active ? 'var(--paper)' : 'var(--ink-mute)',
                  border:     active ? 'none' : '1px solid var(--paper-line)' }}>{d[0]}</button>
            );
          })}
        </div>
        <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontFamily:'Times New Roman,serif', fontStyle:'italic' }}>
          整股低消 $20 · 零股低消 $1 · 無條件捨去
        </span>
      </div>

      {/* ═══ 01 持有中 ═══ */}
      <window.SectionHead kicker="Holdings" title="持有中" titleEn="Open Positions"
        sub="買入即追蹤浮動損益 · 了結後轉入結算績效" />
      {holdings.length === 0 ? (
        <div className="ph-empty kai">尚無持倉 — 輸入代號按「＋建倉」，再登記第一筆買入</div>
      ) : (
        <>
          {holdings.map(function(p) {
            return (
              <HoldingCard key={p.id} pos={p} live={pxMap[p.code]}
                onUpdate={function(next){ updatePos(p.id, next); }}
                onDelete={function(){ deletePos(p.id); }}
                discount={discount} taxRate={taxRate}/>
            );
          })}
          <div style={{ marginTop:14 }}>
            <AllocBar holdings={holdings} pxMap={pxMap} industryMap={industryMap}/>
          </div>
        </>
      )}

      {/* ═══ 02 觀察中 ═══ */}
      <window.SectionHead kicker="Watching" title="觀察名單" titleEn="On the Radar"
        sub="尚未進場 · 點「建倉」開始記帳" />
      {watching.length === 0 ? (
        <div className="ph-empty kai">觀察名單空白 — 輸入代號按「＋觀察」</div>
      ) : (
        <table className="wl-table" style={{ marginBottom:30 }}>
          <thead><tr>
            <th>代號 / 名稱</th>
            <th className="r">現價</th>
            <th className="r">漲跌%</th>
            <th className="r" style={{ width:200 }}></th>
          </tr></thead>
          <tbody>
            {watching.map(function(p) {
              var live = pxMap[p.code];
              return (
                <tr key={p.id}>
                  <td>
                    <a href={'個股技術分析.html?code='+p.code} style={{ textDecoration:'none', color:'inherit' }}>
                      <span className="wl-ticker num">{p.code}</span>
                      <span className="kai" style={{ marginLeft:10, fontSize:13, color:'var(--gold-deep, #9a7b35)' }}>{p.name}</span>
                    </a>
                  </td>
                  <td className="r"><span className="wl-price num">{live && live.price > 0 ? live.price.toLocaleString() : '—'}</span></td>
                  <td className="r">
                    <span className="num" style={{ fontWeight:700, color: live ? upDn(live.chg||0) : 'var(--ink-mute)' }}>
                      {live ? fmtPct(live.chg||0) : '—'}
                    </span>
                  </td>
                  <td className="r">
                    <button className="kai" onClick={function(){ updatePos(p.id, Object.assign({}, p, { watch:false })); }}
                      style={{ padding:'4px 14px', fontSize:'var(--fs-nano)', cursor:'pointer', marginRight:8,
                        background:'var(--ink)', color:'var(--paper-bright)', border:'none' }}>建倉 →</button>
                    <button className="wl-del" onClick={function(){ deletePos(p.id); }} title="移除">✕</button>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      )}

      {/* ═══ 03 結算績效 ═══ */}
      <window.SectionHead kicker="Performance" title="結算績效" titleEn="The Reckoning"
        sub="已了結交易的成績單 · 勝率與盈虧比皆含股利" />
      <PerfSection portfolio={portfolio} pxMap={pxMap} onExport={exportCSV}/>

      {/* ═══ 04 節稅試算 ═══ */}
      <window.SectionHead kicker="Tax Planning" title="節稅試算" titleEn="Dividend Tax Filing"
        sub="合併課稅 vs 分離課稅 28%，依114年度級距試算 · 僅供參考" />
      <TaxSection portfolio={portfolio} />

      <window.FooterBar />
    </>
  );
}

window.TWWatchlistContent = App;
