// Helm — 保障規劃。① 保障時間軸 ② 長照準備金 ③ 重疾一次金 ④ 身故(需求法,含配偶/父母/子女)⑤ 保險 vs 投資 + 尾端壓測。
//   讀文件金庫(window.HELM.docs)+ 扶養設定(HelmPrefs "helm-dependents")。公式見 protection-engine.js(多 agent 辯論+紅隊驗證)。
//   教育性質試算,非投資/保險/稅務建議、不針對特定商品;所有假設可調。
(function () {
  const NS = window.HelmDesignSystem_9613a7;
  const { Button } = NS;
  const P = window.HelmProtect;
  const num = (P && P.num) || function (x) { return Number(String(x == null ? '' : x).replace(/[^0-9.\-]/g, '')) || 0; };
  function wan(n) { n = Math.round(num(n)); if (!n) return '0'; return Math.abs(n) >= 10000 ? (Math.round(n / 1000) / 10).toLocaleString('en-US', { maximumFractionDigits: 1 }) + '萬' : n.toLocaleString('en-US'); }

  const RISKS = [
    { key: '身故', label: '身故 / 壽險', color: '#3a9e72' },
    { key: '意外', label: '意外身故失能', color: '#d98a3d' },
    { key: '醫療', label: '住院醫療', color: '#3d7fd9' },
    { key: '重疾', label: '重大疾病 / 癌症', color: '#d04f4f', alt: '癌症' },
    { key: '長照', label: '長期照顧', color: '#9162d4' },
  ];

  function Timeline({ items, currentAge, planEnd }) {
    const AGE_MIN = currentAge, AGE_MAX = 105;
    const X0 = 12, X1 = 322, CW = X1 - X0;
    const xs = (a) => X0 + (Math.max(AGE_MIN, Math.min(AGE_MAX, a)) - AGE_MIN) / (AGE_MAX - AGE_MIN) * CW;
    const LABEL_H = 15, BAR_H = 13, ROW_GAP = 14, Y0 = 6;
    const pitch = LABEL_H + BAR_H + ROW_GAP;
    const chartBottom = Y0 + RISKS.length * pitch;
    const H = chartBottom + 24;

    const rows = RISKS.map(function (r) {
      const match = items.filter(function (i) { return i.risk === r.key || (r.alt && i.risk === r.alt); });
      if (!match.length) return { r: r, covered: false };
      let amount = 0, endAge = 0, lifelong = false, hasDaily = false;
      match.forEach(function (m) {
        amount += m.amount || 0;
        if (m.lifelong || m.endAge == null) lifelong = true;
        if (m.endAge != null && m.endAge > endAge) endAge = m.endAge;
        if (r.key === '醫療' && !m.amount) hasDaily = true;
      });
      if (lifelong) endAge = AGE_MAX;
      return { r: r, covered: true, amount: amount, endAge: endAge, lifelong: lifelong, hasDaily: hasDaily };
    });
    const cliffAges = rows.filter(function (d) { return d.covered && !d.lifelong && d.endAge < planEnd; }).map(function (d) { return d.endAge; });
    const cliff = cliffAges.length ? Math.min.apply(null, cliffAges) : null;
    const ticks = [currentAge, 65, 76, 85, 95, 105].filter(function (t, i, a) { return t >= AGE_MIN && a.indexOf(t) === i; });

    return (
      <svg viewBox={'0 0 340 ' + H} width="100%" role="img" aria-label="保障時間軸" style={{ display: 'block' }}>
        <line x1={xs(planEnd)} y1={Y0 - 2} x2={xs(planEnd)} y2={chartBottom} stroke="var(--line)" strokeWidth="1" strokeDasharray="2 3" />
        {cliff != null && <line x1={xs(cliff)} y1={Y0 - 2} x2={xs(cliff)} y2={chartBottom + 2} stroke="#d04f4f" strokeWidth="1.5" strokeDasharray="3 2" />}
        {rows.map(function (d, i) {
          const ly = Y0 + i * pitch + 11;
          const by = Y0 + i * pitch + LABEL_H;
          const right = d.covered ? (d.lifelong ? '終身' : (d.endAge ? '到 ' + d.endAge + ' 歲' : '')) : '未保';
          const amt = d.covered ? (d.amount ? wan(d.amount) : (d.hasDaily ? '日額型' : '')) : '';
          return (
            <g key={d.r.key}>
              <text x={X0} y={ly} fontSize="11" fill="var(--text-secondary)">{d.r.label}</text>
              <text x={X1} y={ly} fontSize="10.5" textAnchor="end" fill={d.covered ? 'var(--text-tertiary)' : '#d04f4f'}>{amt ? amt + ' · ' : ''}{right}</text>
              <rect x={X0} y={by} width={CW} height={BAR_H} rx="3" fill="var(--surface-sunken)" />
              {d.covered ? (
                <g>
                  <rect x={X0} y={by} width={xs(d.lifelong ? AGE_MAX : d.endAge) - X0} height={BAR_H} rx="3" fill={d.r.color} opacity="0.88" />
                  {!d.lifelong && d.endAge < AGE_MAX && (
                    <g>
                      <rect x={xs(d.endAge)} y={by} width={X1 - xs(d.endAge)} height={BAR_H} rx="3" fill="#d04f4f" opacity="0.12" />
                      <circle cx={xs(d.endAge)} cy={by + BAR_H / 2} r="3" fill="#d04f4f" />
                    </g>
                  )}
                </g>
              ) : (
                <rect x={X0} y={by} width={CW} height={BAR_H} rx="3" fill="none" stroke="#d04f4f" strokeWidth="1" strokeDasharray="3 2" opacity="0.6" />
              )}
            </g>
          );
        })}
        {ticks.map(function (t) {
          const isCliff = cliff != null && t === cliff;
          return (
            <g key={t}>
              <line x1={xs(t)} y1={chartBottom} x2={xs(t)} y2={chartBottom + 4} stroke="var(--line)" strokeWidth="1" />
              <text x={xs(t)} y={chartBottom + 15} fontSize="9.5" textAnchor="middle" fill={isCliff ? '#d04f4f' : 'var(--text-tertiary)'}>{t === currentAge ? '現在' : t}{isCliff ? '·斷崖' : (t === 95 ? '·規劃' : '')}</text>
            </g>
          );
        })}
      </svg>
    );
  }

  function Slider({ label, value, min, max, step, onChange, fmt }) {
    return (
      <div className="pl-sl">
        <div className="pl-sl__top"><span>{label}</span><b>{fmt ? fmt(value) : value}</b></div>
        <input type="range" min={min} max={max} step={step} value={value} className="pl-sl__range" onChange={function (e) { onChange(Number(e.target.value)); }} />
      </div>
    );
  }

  function Stepper({ label, value, min, max, onChange }) {
    return (
      <div className="pl-step">
        <span>{label}</span>
        <span className="pl-step__ctrl">
          <button type="button" onClick={function () { onChange(Math.max(min, value - 1)); }} disabled={value <= min} aria-label="減">−</button>
          <b>{value}</b>
          <button type="button" onClick={function () { onChange(Math.min(max, value + 1)); }} disabled={value >= max} aria-label="加">＋</button>
        </span>
      </div>
    );
  }

  function ProtectionScreen({ onClose, onOpenDocs }) {
    const H = window.HELM || {};
    const birthYear = H.birth ? Number(String(H.birth).slice(0, 4)) : 1989;
    const currentAge = (new Date().getFullYear()) - birthYear;
    const planEnd = P.DEFAULTS.planningEndAge;
    const items = P.timeline(H.docs || [], birthYear, planEnd);
    const docs = H.docs || [];

    const lifeSum = items.filter(function (i) { return i.risk === '身故'; }).reduce(function (s, i) { return s + (i.amount || 0); }, 0);
    const lifePremium = docs.filter(function (d) { return d.type === '壽險'; }).reduce(function (s, d) { return s + num(d.premium); }, 0);
    const totalPremium = docs.reduce(function (s, d) { return s + num(d.premium); }, 0);
    const critCurrent = items.filter(function (i) { return i.risk === '重疾' || i.risk === '癌症'; }).reduce(function (s, i) { return s + (i.amount || 0); }, 0);
    const annualExpenseHelm = Math.round(((H.cashflow && H.cashflow.expenseSum) || 0) * 12);
    const annualIncomeHelm = Math.round(((H.cashflow && H.cashflow.incomeSum) || 0) * 12);

    // 扶養設定(跨裝置記住)
    const [dep, setDep] = React.useState(function () {
      try { return Object.assign({ spouse: false, parents: 0, children: 0 }, JSON.parse((window.HelmPrefs && window.HelmPrefs.get('helm-dependents')) || '{}')); }
      catch (e) { return { spouse: false, parents: 0, children: 0 }; }
    });
    function saveDep(next) { setDep(next); try { window.HelmPrefs.set('helm-dependents', JSON.stringify(next)); } catch (e) {} }
    const hasDep = dep.spouse || dep.parents > 0 || dep.children > 0;

    const [ltc, setLtc] = React.useState({ monthly: P.DEFAULTS.ltcMonthlyCost, med: P.DEFAULTS.inflationMedical, years: P.DEFAULTS.ltcYears, start: P.DEFAULTS.careStartAge });
    const [income, setIncome] = React.useState(annualIncomeHelm || 600000);
    const [survivorCost, setSurvivorCost] = React.useState(annualExpenseHelm ? Math.round(annualExpenseHelm * 0.6 / 10000) * 10000 : 360000);
    const [supportYears, setSupportYears] = React.useState(25);
    const [debt, setDebt] = React.useState(Math.round(H.totalLiab || 0));
    const [retRate, setRetRate] = React.useState(0.044);
    const [critAge, setCritAge] = React.useState(85);
    const [critCost, setCritCost] = React.useState(2500000);
    const [dd, setDd] = React.useState(0.30);

    const reserve = P.ltcReserve({ currentAge: currentAge, ltcMonthlyCost: ltc.monthly, inflationMedical: ltc.med, ltcYears: ltc.years, careStartAge: ltc.start });
    const crit = P.criticalGap({ annualIncome: income, current: critCurrent });
    const eduFund = dep.children * 4000000;
    const lg = P.lifeGap({ dependents: hasDep, annualExpense: survivorCost, survivorRatio: 1, supportYears: supportYears, debt: debt, eduFund: eduFund, funeral: P.DEFAULTS.funeral, liquidAssets: H.liquid || 0, existingLifeCover: lifeSum });
    const inv = function (age) { return P.fvPremiumInvested(lifePremium || 11138, 20, 30, age, retRate); };
    const fs = P.forcedSaleLoss(critCost, dd);

    if (!docs.length) {
      return (
        <div className="fpage" role="dialog" aria-modal="true" aria-label="保障規劃">
          <div className="fpage__panel">
            <header className="fpage__bar"><button className="fpage__cancel" onClick={onClose}><i className="ph ph-arrow-left" aria-hidden="true" />返回</button><span className="fpage__title">保障規劃</span><span aria-hidden="true" /></header>
            <div className="fpage__scroll"><div className="fpage__body">
              <section className="fpage__card"><div className="wt-empty"><i className="ph ph-shield-warning" aria-hidden="true" /><p>還沒有保險資料</p><span>先去「文件金庫」把保單丟進來,這裡就能畫出你的保障時間軸與缺口。</span><Button variant="primary" onClick={onOpenDocs} style={{ marginTop: 12 }}>前往文件金庫</Button></div></section>
            </div></div>
          </div>
        </div>
      );
    }

    return (
      <div className="fpage" role="dialog" aria-modal="true" aria-label="保障規劃">
        <div className="fpage__panel">
          <header className="fpage__bar"><button className="fpage__cancel" onClick={onClose}><i className="ph ph-arrow-left" aria-hidden="true" />返回</button><span className="fpage__title">保障規劃</span><span aria-hidden="true" /></header>
          <div className="fpage__scroll"><div className="fpage__body">

            <p className="pl-disc">教育性質試算,依你輸入的假設與通用公式計算,<b>不構成投資、保險或稅務建議</b>,也不針對任何特定商品。實際決策請與合格的理財/保險專業人員討論。</p>

            {/* 卡1 時間軸 */}
            <section className="fpage__card">
              <div className="fpage__card-head"><span className="t-overline">保障時間軸 · 你現在 {currentAge} 歲</span></div>
              <Timeline items={items} currentAge={currentAge} planEnd={planEnd} />
              <p className="pl-note">紅點＝該保障結束的年齡;紅框＝目前未保。把保單補進文件金庫,這裡會自動更新。</p>
            </section>

            {/* 卡2 長照 */}
            <section className="fpage__card">
              <div className="fpage__card-head"><span className="t-overline">長照準備金(最大的長期工程)</span></div>
              <div className="pl-big"><span className="pl-big__num">約 {wan(reserve)}</span><span className="pl-big__unit">今日現值</span></div>
              <p className="pl-note">若 {ltc.start} 歲起需照顧 {ltc.years} 年,這是<b>今天要等值準備</b>的金額(已折現)。是「真的發生時要全額拿出來」的數字,不是機率加權的平均。</p>
              <Slider label="每月照顧費" value={ltc.monthly} min={27000} max={70000} step={1000} onChange={function (v) { setLtc(Object.assign({}, ltc, { monthly: v })); }} fmt={function (v) { return (v / 10000).toFixed(1) + '萬/月'; }} />
              <Slider label="醫療/長照通膨" value={ltc.med} min={0.02} max={0.05} step={0.005} onChange={function (v) { setLtc(Object.assign({}, ltc, { med: v })); }} fmt={function (v) { return (v * 100).toFixed(1) + '%'; }} />
              <Slider label="需照顧年數" value={ltc.years} min={3} max={12} step={1} onChange={function (v) { setLtc(Object.assign({}, ltc, { years: v })); }} fmt={function (v) { return v + ' 年'; }} />
              <Slider label="幾歲開始" value={ltc.start} min={70} max={90} step={1} onChange={function (v) { setLtc(Object.assign({}, ltc, { start: v })); }} fmt={function (v) { return v + ' 歲'; }} />
              <p className="pl-warn"><i className="ph ph-info" aria-hidden="true" /> 這筆純靠投資較吃力:長照常在高齡、資產正消耗、又可能碰上股市回落時發生,被迫低點變現會打殘退休金。保險的價值在「不必在最壞時點賣資產」,而非報酬率。</p>
            </section>

            {/* 卡3 重疾 */}
            <section className="fpage__card">
              <div className="fpage__card-head"><span className="t-overline">重大疾病 / 癌症一次金</span></div>
              <div className="pl-row"><span>建議目標</span><b>{wan(crit.target)}</b></div>
              <div className="pl-row"><span>你目前</span><b className={critCurrent ? '' : 'pl-zero'}>{critCurrent ? wan(critCurrent) : '0(未保)'}</b></div>
              <div className="pl-row pl-row--gap"><span>缺口</span><b className={crit.gap ? 'pl-zero' : ''}>{wan(crit.gap)}</b></div>
              <Slider label="你的年收入(估)" value={income} min={300000} max={2000000} step={50000} onChange={setIncome} fmt={function (v) { return wan(v); }} />
              <p className="pl-note">台灣癌症自費新藥常達百萬(調查:逾 6 成自費破 30 萬、近 1/4 破 200 萬)。年輕時保費便宜,是工作期較划算補的缺口。</p>
            </section>

            {/* 卡4 身故(需求法) */}
            <section className="fpage__card">
              <div className="fpage__card-head"><span className="t-overline">身故 / 壽險(需求法)</span></div>
              <div className="pl-deps">
                <label className="pl-toggle"><input type="checkbox" checked={dep.spouse} onChange={function (e) { saveDep(Object.assign({}, dep, { spouse: e.target.checked })); }} /><span>有配偶</span></label>
                <Stepper label="受扶養父母" value={dep.parents} min={0} max={3} onChange={function (v) { saveDep(Object.assign({}, dep, { parents: v })); }} />
                <Stepper label="子女" value={dep.children} min={0} max={6} onChange={function (v) { saveDep(Object.assign({}, dep, { children: v })); }} />
              </div>
              {!hasDep ? (
                <p className="pl-ok"><i className="ph ph-check-circle" aria-hidden="true" /> 沒有受扶養人時,身故沒有「要替別人撐生活費」的責任,缺口趨近 0;現有壽險{lifeSum ? '(' + wan(lifeSum) + ')' : ''}通常已夠處理身後事。</p>
              ) : (
                <div>
                  <p className="pl-note" style={{ marginTop: 4 }}>你有需要照顧的家人,身故缺口要用「遺族需求法」算:把家人未來要用的錢加總,扣掉你已有的資產與壽險。</p>
                  <Slider label="遺族每年生活費" value={survivorCost} min={120000} max={1200000} step={20000} onChange={setSurvivorCost} fmt={function (v) { return wan(v) + '/年'; }} />
                  <Slider label="需支應年數" value={supportYears} min={5} max={40} step={1} onChange={setSupportYears} fmt={function (v) { return v + ' 年'; }} />
                  <Slider label="要還清的負債" value={debt} min={0} max={20000000} step={100000} onChange={setDebt} fmt={function (v) { return wan(v); }} />
                  <p className="pl-hint">負債預設帶入你的總負債{H.totalLiab ? '(' + wan(H.totalLiab) + ')' : ''}。若房貸已有「房貸壽險」會在身故時還清,可把房貸金額扣掉。{dep.children ? ' 已含子女教育金 ' + wan(eduFund) + '(每人 400 萬)。' : ''}</p>
                  <div className="pl-row" style={{ marginTop: 8 }}><span>家庭未來需求合計</span><b>{wan(lg.need)}</b></div>
                  <div className="pl-row"><span>你的流動資產 + 現有壽險</span><b>{wan(lg.resource)}</b></div>
                  <div className="pl-row pl-row--gap"><span>身故保障缺口</span><b className={lg.gap ? 'pl-zero' : ''}>{lg.gap ? wan(lg.gap) : '0 · 已足'}</b></div>
                  {lg.gap > 0 && <p className="pl-warn"><i className="ph ph-warning" aria-hidden="true" /> 你現有壽險 {wan(lifeSum)},距家人需要還差約 {wan(lg.gap)}。定期壽險補這段保費很低(這金額的定期壽險年繳通常幾千元),是有家庭後優先級很高的缺口。</p>}
                </div>
              )}
            </section>

            {/* 卡5 保險 vs 投資 + 尾端壓測 */}
            <section className="fpage__card">
              <div className="fpage__card-head"><span className="t-overline">保險 vs 投資</span></div>
              <p className="pl-note" style={{ marginTop: 0 }}>① 機會成本:你的<b>壽險</b>年繳約 {wan(lifePremium || 11138)}(全部保險合計約 {wan(totalPremium || 17240)}/年、半年繳一次約 {wan((totalPremium || 17240) / 2)})。把這筆壽險保費改拿去投資(20 年繳、30 歲起算)會是:</p>
              <div className="pl-row"><span>投資到 65 歲</span><b>{wan(inv(65))}</b></div>
              <div className="pl-row"><span>投資到 85 歲</span><b>{wan(inv(85))}</b></div>
              <div className="pl-row pl-row--gap"><span>保險端(身故保障)</span><b>{wan(lifeSum)} · 終身</b></div>
              <Slider label="假設年化報酬" value={retRate} min={0.02} max={0.08} step={0.002} onChange={setRetRate} fmt={function (v) { return (v * 100).toFixed(1) + '%'; }} />
              <p className="pl-hint">投資累積通常多很多(終身壽險的內部報酬率約只有 1~2%);但保險給的是「身故立刻給付」的保障與避險,不是報酬——兩者目的不同,不是誰取代誰。</p>

              <div className="fpage__card-head" style={{ marginTop: 16 }}><span className="t-overline">② 尾端壓力測試</span></div>
              <p className="pl-note" style={{ marginTop: 0 }}>萬一 {critAge} 歲一場重疾要自費 {wan(critCost)},又剛好碰到股市回落 {Math.round(dd * 100)}%:</p>
              <Slider label="重疾發生年齡" value={critAge} min={70} max={95} step={1} onChange={setCritAge} fmt={function (v) { return v + ' 歲'; }} />
              <Slider label="一次性自費" value={critCost} min={500000} max={5000000} step={100000} onChange={setCritCost} fmt={function (v) { return wan(v); }} />
              <Slider label="同期市場回落" value={dd} min={0.1} max={0.5} step={0.05} onChange={setDd} fmt={function (v) { return Math.round(v * 100) + '%'; }} />
              <div className="pl-comp">
                <div className="pl-comp__col pl-comp__col--good"><span className="pl-comp__h">有保險</span><b>理賠直接給 {wan(critCost)}</b><span>投資組合一毛不動,等市場回來</span></div>
                <div className="pl-comp__col pl-comp__col--bad"><span className="pl-comp__h">沒保險(自費)</span><b>被迫賣掉高點值 {wan(fs.peakValueSold)}</b><span>比理賠多燒約 {wan(fs.extraLoss)},這筆未來複利也沒了</span></div>
              </div>
              <p className="pl-hint">這就是為什麼長照/重疾不適合「純靠投資扛」:不是報酬問題,是「被迫在最壞時點變現」會永久打殘退休金。</p>
            </section>

            <p className="pl-foot">假設可調、數字即時更新。可直接拿給保險朋友核對——公式與預設值來自台灣官方資料(內政部生命表、主計總處、衛福部、健保署),並經多方試算交叉驗證。</p>

          </div></div>
        </div>
      </div>
    );
  }

  window.ProtectionScreen = ProtectionScreen;
})();
