// LoanTool — Tool 02 / Practice Loan Calculator.
//
// Depends on (must be loaded before this file):
//   tools-shared.jsx  — 9 shared layout components + fmtMoney helpers
//   loan-engine.jsx   — computeLoan, computeAPRCurve
//   loan-charts.jsx   — BalanceChart, APRCurveChart
//   loan-tool-pieces.jsx — SegmentedToggle, PrincipalInterestPie, AnnualAmortTable, ChartFrame
//
// Mounted into <div id="loan-apr-root"> in Views/Tools/LoanAPR.cshtml.
//
// Production port notes:
//   - App wrapper (SiteNav + SiteFooter) removed — handled by _Layout.cshtml
//   - ctaHref updated from "Pareto Contact.html" to "/Home/Index#contact-section"
//   - relatedInsight.href updated from "Pareto Insights.html" to "/Home/Insights"
//   - window.LoanTool export removed — component mounts directly below

const { useState: lUseState, useMemo: lUseMemo, useEffect: lUseEffect, useRef: lUseRef } = React;

function LoanTool() {
  // ── Inputs ─────────────────────────────────────────────────────
  const [loanAmount, setLoanAmount] = lUseState(3000000);

  // Financed fees removed from this tool. Hardwired to 0; engine signature unchanged.
  const financedFees = 0;

  const [rate, setRate] = lUseState(7.5);
  const [amortYears, setAmortYears] = lUseState(20);
  const [hasBalloonInput, setHasBalloonInput] = lUseState(true);
  const [balloonYears, setBalloonYears] = lUseState(10);

  const effectiveBalloonYears = hasBalloonInput ? balloonYears : amortYears;

  // Extra payment — controlled by a mode: '1.25x' / '1.5x' / '2x' / 'custom'.
  // In a multiplier mode, the extra recomputes whenever the base payment
  // changes. In custom mode, the user's typed value is preserved.
  const [extraMode, setExtraMode] = lUseState('1.5x');
  const [customExtra, setCustomExtra] = lUseState(0);
  const MULT = { '1.25x': 0.25, '1.5x': 0.5, '2x': 1.0 };

  // Ref + observer to drive the floating extra-payment bar.
  // Watch the toggle's container in the right column — the float surfaces
  // once that toggle scrolls out of view, so the user can keep adjusting
  // without scrolling back.
  const toggleRef = lUseRef(null);
  const [floatVisible, setFloatVisible] = lUseState(false);
  const [chartZoomed, setChartZoomed] = lUseState(false);
  lUseEffect(() => {
    // Watch the toggle's container in the right column. Once the toggle
    // itself scrolls past the viewport top, surface the floating bar so
    // the user can keep adjusting without scrolling back.
    const el = toggleRef.current;
    if (!el || typeof IntersectionObserver === 'undefined') return;
    const onScroll = () => {
      const rect = el.getBoundingClientRect();
      setFloatVisible(rect.bottom <= 0);
    };
    const onZoom = (e) => setChartZoomed(!!(e.detail && e.detail.zoomed));
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('chartframe:zoom', onZoom);
    onScroll();
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('chartframe:zoom', onZoom);
    };
  }, []);

  // First compute the BASE loan with no extras — we need its payment to
  // resolve the multiplier modes.
  const baseLoan = lUseMemo(() => computeLoan({
    loanAmount, financedFees, ratePct: rate,
    amortYears, balloonYears: effectiveBalloonYears, extraMonthly: 0,
  }), [loanAmount, financedFees, rate, amortYears, effectiveBalloonYears]);

  const extraMonthly = extraMode === 'custom'
    ? (Number(customExtra) || 0)
    : baseLoan.payment * MULT[extraMode];

  // ── Compute ────────────────────────────────────────────────────
  const loan = lUseMemo(() => computeLoan({
    loanAmount, financedFees, ratePct: rate,
    amortYears, balloonYears: effectiveBalloonYears, extraMonthly,
  }), [loanAmount, financedFees, rate, amortYears, effectiveBalloonYears, extraMonthly]);

  // ── Derived display values ─────────────────────────────────────
  const interestSaved = loan.totalsBank.totalInterest - loan.totalsAccel.totalInterest;
  const interestSavedPct = loan.totalsBank.totalInterest > 0
    ? (interestSaved / loan.totalsBank.totalInterest) * 100
    : 0;
  const monthsSaved = loan.totalsBank.payoffMonth - loan.totalsAccel.payoffMonth;
  const yearsSaved = monthsSaved / 12;
  const extraIsZero = (Number(extraMonthly) || 0) <= 0;

  // For pie sizing — use the larger of the two totals as the scale max.
  const bankTotal = loan.principal + loan.totalsBank.totalInterest;
  const accelTotal = loan.principal + loan.totalsAccel.totalInterest;
  const scaleMax = Math.max(bankTotal, accelTotal);

  return (
    <>
      <ToolPageHeader
        eyebrow="Tool 02 / Loan"
        title="See your loan the way the bank does. Then the way it changes."
        scope="See, side by side, what a loan actually costs over its life and what paying a little extra each month really buys you."
      />

      <ToolFrame>
        {/* === LEFT: inputs === */}
        <InputCard title="Loan terms">
          <MoneyField
            label="Loan amount"
            hint="Cash you actually receive"
            value={loanAmount}
            onChange={setLoanAmount}
            min={0} step={1000}
            prefix="$"
          />

          <NumberField
            label="Interest rate"
            hint="Annual, fixed"
            value={rate}
            onChange={setRate}
            min={0} max={30} step={0.05}
            suffix="%"
          />

          <NumberField
            label="Amortization"
            hint="The schedule the payment is sized to"
            value={amortYears}
            onChange={setAmortYears}
            min={1} max={40} step={1}
            suffix="yr"
          />

          <BalloonToggleField
            hasBalloon={hasBalloonInput}
            setHasBalloon={setHasBalloonInput}
            balloonYears={balloonYears}
            setBalloonYears={setBalloonYears}
            amortYears={amortYears}
          />

          {/* Monthly payment — closing summary of the loan terms. The base
              payment is a structural fact derived from the inputs above, so
              it lives at the bottom of the input panel rather than next to
              the savings hero (which describes a different loan: yours). */}
          <div style={{
            marginTop: 4, paddingTop: 18,
            borderTop: '1px solid var(--pdt-line-soft)',
            display: 'flex', justifyContent: 'space-between',
            alignItems: 'baseline', gap: 14, flexWrap: 'wrap',
          }}>
            <div style={{
              fontFamily: 'var(--pdt-mono)', fontSize: 10,
              color: 'var(--pdt-mute)', letterSpacing: '0.08em',
              textTransform: 'uppercase', fontWeight: 500,
            }}>
              Monthly payment
            </div>
            <div style={{
              fontFamily: 'var(--pdt-sans)', fontSize: 22, fontWeight: 500,
              letterSpacing: '-0.01em', color: 'var(--pdt-ink)',
            }}>
              {fmtMoneyFull(baseLoan.payment)}
            </div>
          </div>
        </InputCard>

        {/* === RIGHT: pay it down faster === */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
          {/* Extra-payment toggle — its own quiet container, sitting above
              the savings hero. The toggle is the cause; the hero is the
              effect. Distinct surface so they don't visually compete. */}
          <div ref={toggleRef} style={{
            background: 'var(--pdt-paper)',
            border: '1px solid var(--pdt-line)',
            padding: 'clamp(20px, 3vw, 28px)',
          }}>
            <ExtraPaymentControl
              extraMode={extraMode}
              setExtraMode={setExtraMode}
              customExtra={customExtra}
              setCustomExtra={setCustomExtra}
              extraMonthly={extraMonthly}
              basePayment={baseLoan.payment}
            />
          </div>

          {/* SAVINGS HERO */}
          <div style={{
            background: extraIsZero ? 'var(--pdt-paper-deep)' : 'var(--pdt-mint)',
            border: extraIsZero
              ? '1px solid var(--pdt-line)'
              : '1px solid var(--pdt-sage)',
            padding: 'clamp(28px, 4vw, 44px)',
            transition: 'background .2s ease',
          }}>
            {extraIsZero ? (
              <>
                <div className="t-eyebrow" style={{
                  color: 'var(--pdt-mute)', marginBottom: 12,
                }}>
                  Add an extra monthly payment
                </div>
                <div style={{
                  fontFamily: 'var(--pdt-serif)', fontStyle: 'italic',
                  fontSize: 'clamp(18px, 2vw, 22px)', lineHeight: 1.5,
                  color: 'var(--pdt-ink-soft)',
                }}>
                  Use the field on the left, or pick a multiplier, to see what
                  paying down faster actually buys you.
                </div>
              </>
            ) : (
              <SavingsHero
                extraMonthly={extraMonthly}
                interestSaved={interestSaved}
                interestSavedPct={interestSavedPct}
                loan={loan}
                yearsSaved={yearsSaved}
              />
            )}
          </div>
        </div>
      </ToolFrame>

      {/* Full-width balance chart — promoted out of the right column so the
          x-axis can breathe. Same container width as the side-by-side below. */}
      <section style={{
        maxWidth: 1240, margin: '0 auto',
        padding: '0 clamp(20px, 4vw, 56px) 32px',
      }}>
        <ChartFrame
          title="Loan balance over time"
          legend={
            <>
              <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{
                  width: 14, height: 2, background: 'var(--pdt-ink)',
                  display: 'inline-block',
                }}></span>
                BANK
              </span>
              {!extraIsZero && (
                <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <span style={{
                    width: 14, height: 2.5, background: 'var(--pdt-green)',
                    display: 'inline-block',
                  }}></span>
                  WITH EXTRA PRINCIPAL PAYMENTS
                </span>
              )}
              {loan.hasBalloon && (
                <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <span style={{
                    width: 14, height: 2, background: 'var(--pdt-amber)',
                    display: 'inline-block', borderTop: '1px dashed var(--pdt-amber)',
                  }}></span>
                  BALLOON
                </span>
              )}
            </>
          }
          footnote={
            loan.hasBalloon
              ? <>The amber line marks the balloon payment. On that date, whatever
              balance remains is owed in full,
              {' '}{fmtMoneyFull(loan.totalsBank.balloonLump)} on the bank's
              schedule. Most borrowers refinance at this point, but the
              balance has to come from somewhere.</>
              : <>The shaded region is the principal you've already paid down. The bank line is what you'd carry on the standard schedule; the green line is what you'd carry with extra principal payments.</>
          }
        >
          <BalanceChart
            scheduleBank={loan.schedule}
            scheduleAccel={loan.scheduleAccel}
            principal={loan.principal}
            hasBalloon={loan.hasBalloon}
            balloonMonth={loan.balloon?.month}
            payoffMonthAccel={loan.totalsAccel.payoffMonth}
            savingsLabel={!extraIsZero ? fmtMoneyFull(interestSaved) + ' saved' : null}
          />
        </ChartFrame>
      </section>

      {/* Full-width scenario comparison — promoted out of the right column
          so the pies + legends + annual tables get the breathing room they
          need. Centered max-width container matches the rest of the page. */}
      <section style={{
        maxWidth: 1240, margin: '0 auto',
        padding: '0 clamp(20px, 4vw, 56px) 64px',
      }}>
        <ChartFrame
          title="Side-by-side scenarios"
          headerNote={
            <>Each pie is sized by total cost. Same loan, but the right
            pie shrinks because you pay less interest. The annual schedule
            underneath each one shows the year-by-year balance.</>
          }
        >
          <div className="loan-scenario-grid" style={{
            gap: 1,
            background: 'var(--pdt-line)',
            border: '1px solid var(--pdt-line)',
            ['--cols']: extraIsZero ? '1fr' : '1fr 1fr',
          }}>
            <ScenarioColumn
              eyebrow="Bank's schedule"
              accent="ink"
              loan={loan}
              schedule={loan.schedule}
              totals={loan.totalsBank}
              scaleMax={scaleMax}
            />
            {!extraIsZero && (
              <ScenarioColumn
                eyebrow="With extra principal payments"
                accent="forest"
                loan={loan}
                schedule={loan.scheduleAccel}
                totals={loan.totalsAccel}
                scaleMax={scaleMax}
                showBalloonRowWhenZero={loan.hasBalloon}
              />
            )}
          </div>
        </ChartFrame>
      </section>

      <ToolReference
        glossary={[
          { term: 'Principal',
            definition: <>The amount you borrowed.</> },
          { term: 'Interest',
            definition: <>What the bank charges to lend you money. On a fixed-rate
            loan, the rate is constant, but the dollar amount of interest
            you pay each month falls slowly as the balance shrinks.</> },
          { term: 'Amortization term',
            definition: <>The schedule the monthly payment is sized to. A 20-year
            amortization means the payment is set so the loan would pay off
            in 20 years.</> },
          { term: 'Balloon',
            definition: <>A balloon means the loan must be paid off before monthly
            payments reduce the balance to zero. On the balloon date, the entire
            outstanding balance is owed at once. A "20 due in 10" loan has 20-year
            loan payments but balloons at year 10.</> },
          { term: 'Extra payment',
            definition: <>An amount you add to the regular monthly payment, applied
            entirely to principal. Every dollar of extra principal saves you
            future interest on that dollar, and shortens the loan.</> },
        ]}
        whyCare={
          <>
            <p style={{ margin: '0 0 14px' }}>
              The lender quotes a payment and a rate, but that's just a slice
              of the loan. The rest of it is the shape of the amortization curve,
              what's owed at the balloon, and how fast the principal balance
              actually pays down.
            </p>
            <p style={{ margin: '0 0 14px' }}>
              In the first five years of a 20-year loan, roughly two-thirds
              of every payment is interest. That's normal, but it means modest
              extra payments early have outsized impact. If there's a balloon
              at year 10, it hits while most of the principal is still on the
              books, which means a large refinance.
            </p>
            <p style={{ margin: 0 }}>
              Run the numbers before you sign. Then run them again with extra
              payments, because that's the loan you'll actually live with.
            </p>
          </>
        }
      />

      <ToolFooter
        doesntMeasure={
          <>
            It treats the rate as fixed and the schedule as clean. Real practice
            loans often carry prepayment penalties, escrow, seller-financed
            second positions, or SBA-style guarantees that change the picture.
            And it doesn't model your tax situation. Extra principal payments
            interact with your interest deduction in ways that matter.
          </>
        }
        ctaText="If you're staring at a term sheet and wondering whether the structure makes sense for your practice, that's a conversation worth having."
        ctaHref="/Home/Index#contact-section"
        // Related reading temporarily hidden — restore by uncommenting
        // relatedInsight={{
        //   title: 'How to make money by paying it down faster.',
        //   href: '/Home/Insights',
        // }}
      />

      {/* Floating extra-payment control — appears once the input panel
          scrolls out of view, gives the reader a portable copy of the most
          experiential control on the page. */}
      <FloatingExtraPaymentBar
        visible={floatVisible || chartZoomed}
        forceFront={chartZoomed}
        extraMode={extraMode}
        setExtraMode={setExtraMode}
        customExtra={customExtra}
        setCustomExtra={setCustomExtra}
        extraMonthly={extraMonthly}
        basePayment={baseLoan.payment}
      />
    </>
  );
}

// ─────────────────────────────────────────────────────────────────
// SavingsHero — the projector-friendly headline. Same big-number, but the
// narrative subtitle correctly distinguishes:
//   (a) loan paid off entirely (no balloon, OR balloon never reached because
//       extras paid it off first)  → "paid off X years sooner"
//   (b) loan still hits the balloon, but with a smaller balance owed
//       → "$X less owed at balloon" framing
// ─────────────────────────────────────────────────────────────────
function SavingsHero({ extraMonthly, interestSaved, interestSavedPct, loan, yearsSaved }) {
  const accelPaidOff = loan.totalsAccel.paidOff;
  const bankPaidOff = !loan.hasBalloon; // bank schedule pays off only if no balloon

  // What's owed at balloon, on each schedule (0 if no balloon).
  const bankBalloon = loan.totalsBank.balloonLump;
  const accelBalloon = loan.totalsAccel.balloonLump;
  const balloonReduction = bankBalloon - accelBalloon;

  return (
    <>
      <div className="t-eyebrow" style={{
        color: 'var(--pdt-forest-deep)', marginBottom: 14,
      }}>
        By paying {fmtMoneyFull(extraMonthly)}/mo extra, you save
      </div>
      <div style={{
        fontSize: 'clamp(56px, 9vw, 104px)', fontWeight: 500,
        letterSpacing: '-0.03em', lineHeight: 0.95,
        color: 'var(--pdt-forest-deep)', marginBottom: 6,
      }}>
        {fmtMoneyFull(interestSaved)}
      </div>
      <div style={{
        fontFamily: 'var(--pdt-mono)', fontSize: 12,
        letterSpacing: '0.10em', textTransform: 'uppercase',
        color: 'var(--pdt-forest-deep)', opacity: 0.75,
        marginBottom: 18,
      }}>
        in interest payments
      </div>
      <div style={{
        fontFamily: 'var(--pdt-serif)', fontStyle: 'italic',
        fontSize: 'clamp(16px, 1.7vw, 19px)', lineHeight: 1.55,
        color: 'var(--pdt-forest-deep)', opacity: 0.9,
        maxWidth: 720,
      }}>
        That's {interestSavedPct.toFixed(0)}% less interest paid to the bank
        {loan.hasBalloon && !accelPaidOff && (
          <>. The balloon still hits at year {Math.round(loan.balloon.month / 12)},
          but you walk in owing <strong>{fmtMoneyFull(accelBalloon)}</strong> instead
          of <strong>{fmtMoneyFull(bankBalloon)}</strong>, which is
          {' '}{fmtMoneyFull(balloonReduction)} less principal to refinance.</>
        )}
        {loan.hasBalloon && accelPaidOff && (
          <>. The bank's schedule never pays off. At the balloon (year
          {' '}{Math.round(loan.balloon.month / 12)}), <strong>{fmtMoneyFull(bankBalloon)}</strong>
          {' '}of principal is still owed and must be refinanced. With extra principal payments,
          the loan is paid off entirely in <strong>{(loan.totalsAccel.payoffMonth / 12).toFixed(1)} years</strong>,
          no refinance.</>
        )}
        {!loan.hasBalloon && accelPaidOff && (
          <>, and the loan is <strong>paid off {yearsSaved.toFixed(1)} years sooner</strong>:
          {' '}{(loan.totalsAccel.payoffMonth / 12).toFixed(1)} years instead of {Math.round(loan.totalsBank.payoffMonth / 12)}.</>
        )}
      </div>
    </>
  );
}

// ─────────────────────────────────────────────────────────────────
// ScenarioColumn — one column of the bank vs your-view comparison.
// Stacks: eyebrow, total cost, principal/interest pie, am table.
// Pie is sized proportional to total cost via scaleMax.
// ─────────────────────────────────────────────────────────────────
function ScenarioColumn({ eyebrow, accent, loan, schedule, totals, scaleMax, showBalloonRowWhenZero = false }) {
  const isForest = accent === 'forest';
  const total = loan.principal + totals.totalInterest;
  const bgColor = isForest ? 'var(--pdt-mint)' : 'var(--pdt-paper)';
  const eyebrowColor = isForest ? 'var(--pdt-forest-deep)' : 'var(--pdt-mute)';
  const numColor = isForest ? 'var(--pdt-forest-deep)' : 'var(--pdt-ink)';

  return (
    <div style={{
      background: bgColor,
      padding: 'clamp(20px, 2.5vw, 28px)',
      display: 'flex', flexDirection: 'column', gap: 20,
    }}>
      {/* Header */}
      <div>
        <div className="t-eyebrow" style={{ color: eyebrowColor, marginBottom: 8 }}>
          {eyebrow}
        </div>
        <div style={{
          fontSize: 'clamp(20px, 2.4vw, 26px)', fontWeight: 500,
          letterSpacing: '-0.01em', color: numColor, marginBottom: 4,
        }}>
          {fmtMoneyFull(totals.totalInterest)}
        </div>
        <div style={{
          fontFamily: 'var(--pdt-mono)', fontSize: 10,
          color: eyebrowColor, opacity: 0.8,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>
          INTEREST PAID
          {totals.balloonLump > 0 && (
            <> · {fmtMoneyFull(totals.balloonLump)} principal payment due @ yr {Math.round(loan.balloon.month / 12)}</>
          )}
        </div>
      </div>

      {/* Pie — sized proportional to total cost */}
      <div style={{
        display: 'flex', justifyContent: 'center',
        padding: '8px 0',
      }}>
        <PrincipalInterestPie
          principal={loan.principal}
          interest={totals.totalInterest}
          balloonLump={totals.balloonLump || 0}
          showBalloonRowWhenZero={showBalloonRowWhenZero}
          size={200}
          scaleMax={scaleMax}
        />
      </div>

      {/* AM table */}
      <div>
        <div className="t-eyebrow" style={{ color: eyebrowColor, marginBottom: 10 }}>
          Annual schedule
        </div>
        <AnnualAmortTable
          schedule={schedule}
          balloonMonth={loan.hasBalloon ? loan.balloon.month : null}
          balloonLump={totals.balloonLump}
        />
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// BalloonToggleField — yes/no toggle + balloon term input.
// ─────────────────────────────────────────────────────────────────
function BalloonToggleField({
  hasBalloon, setHasBalloon, balloonYears, setBalloonYears, amortYears,
}) {
  return (
    <div>
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        marginBottom: hasBalloon ? 12 : 0, gap: 10,
      }}>
        <div>
          <div style={{
            fontFamily: 'var(--pdt-mono)', fontSize: 10,
            color: 'var(--pdt-mute)', letterSpacing: '0.08em',
            textTransform: 'uppercase', fontWeight: 500,
            marginBottom: 4,
          }}>
            Balloon payment
          </div>
          <div style={{
            fontFamily: 'var(--pdt-serif)', fontStyle: 'italic',
            fontSize: 13, color: 'var(--pdt-mute)',
          }}>
            Does this loan have a balloon?
          </div>
        </div>
        <SegmentedToggle
          value={hasBalloon ? 'yes' : 'no'}
          onChange={(v) => setHasBalloon(v === 'yes')}
          opts={[
            { id: 'no', label: 'No' },
            { id: 'yes', label: 'Yes' },
          ]}
        />
      </div>
      {hasBalloon && (
        <NumberField
          label="Balloon term"
          hint="When you must pay off the remaining balance"
          value={balloonYears}
          onChange={setBalloonYears}
          min={1} max={Math.max(1, amortYears)} step={1}
          suffix="yr"
        />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// ExtraPaymentControl — the segmented mode picker + readout.
// Rendered both inline (in the InputCard) and inside the floating bar.
// `compact` flips the layout from stacked-vertical (inline) to a tight
// horizontal row used by the float.
// ─────────────────────────────────────────────────────────────────
function ExtraPaymentControl({
  extraMode, setExtraMode, customExtra, setCustomExtra,
  extraMonthly, basePayment, compact = false,
}) {
  if (compact) {
    // ── Compact (floating bar) layout: buttons + tiny live readout ──
    return (
      <div style={{
        display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap',
      }}>
        <div style={{
          fontFamily: 'var(--pdt-mono)', fontSize: 10,
          color: 'var(--pdt-mute)', letterSpacing: '0.08em',
          textTransform: 'uppercase', fontWeight: 500,
          whiteSpace: 'nowrap',
        }}>
          Extra / mo
        </div>
        <SegmentedToggle
          value={extraMode}
          onChange={setExtraMode}
          opts={[
            { id: '1.25x', label: '1.25×' },
            { id: '1.5x',  label: '1.5×' },
            { id: '2x',    label: '2×' },
            { id: 'custom', label: 'Custom' },
          ]}
          size="sm"
        />
        {extraMode === 'custom' ? (
          <div style={{
            display: 'flex', alignItems: 'center', gap: 6,
          }}>
            <span style={{
              fontFamily: 'var(--pdt-mono)', fontSize: 12,
              color: 'var(--pdt-mute)',
            }}>$</span>
            <input
              type="text"
              inputMode="decimal"
              value={(() => {
                if (!customExtra && customExtra !== 0) return '';
                const s = String(customExtra);
                if (s === '' || s === '0') return '';
                const dot = s.indexOf('.');
                const intPart = dot >= 0 ? s.slice(0, dot) : s;
                const decPart = dot >= 0 ? s.slice(dot) : '';
                return (Number(intPart) || 0).toLocaleString('en-US') + decPart;
              })()}
              onChange={(e) => {
                const raw = e.target.value.replace(/[^0-9.]/g, '');
                const parts = raw.split('.');
                const cleaned = parts.length > 2
                  ? parts[0] + '.' + parts.slice(1).join('')
                  : raw;
                setCustomExtra(cleaned || '');
              }}
              placeholder="0"
              style={{
                width: 110,
                fontFamily: 'var(--pdt-sans)', fontSize: 14,
                fontWeight: 500, color: 'var(--pdt-ink)',
                background: 'var(--pdt-paper)',
                border: '1px solid var(--pdt-line)',
                padding: '4px 8px',
                outline: 'none',
              }}
            />
            <span style={{
              fontFamily: 'var(--pdt-serif)', fontStyle: 'italic',
              fontSize: 12, color: 'var(--pdt-mute)',
            }}>/mo</span>
          </div>
        ) : (
          <div style={{
            fontFamily: 'var(--pdt-sans)', fontSize: 15,
            fontWeight: 500, color: 'var(--pdt-ink)',
            letterSpacing: '-0.01em', whiteSpace: 'nowrap',
          }}>
            {fmtMoneyFull(extraMonthly)}
            <span style={{
              fontFamily: 'var(--pdt-serif)', fontStyle: 'italic',
              fontWeight: 400, color: 'var(--pdt-mute)',
              marginLeft: 4, fontSize: 13,
            }}>/mo</span>
          </div>
        )}
      </div>
    );
  }

  // ── Inline (input panel) layout: the original stacked block ──
  return (
    <>
      <div style={{
        fontFamily: 'var(--pdt-mono)', fontSize: 10,
        color: 'var(--pdt-mute)', letterSpacing: '0.08em',
        textTransform: 'uppercase', fontWeight: 500,
        marginBottom: 10,
      }}>
        Extra monthly payment
      </div>

      <div style={{ marginBottom: 12 }}>
        <SegmentedToggle
          value={extraMode}
          onChange={setExtraMode}
          opts={[
            { id: '1.25x', label: '1.25×' },
            { id: '1.5x',  label: '1.5×' },
            { id: '2x',    label: '2×' },
            { id: 'custom', label: 'Custom' },
          ]}
          size="sm"
          fullWidth
        />
      </div>

      {extraMode === 'custom' ? (
        <MoneyField
          label="Custom amount"
          hint="Applied to principal each month"
          value={customExtra}
          onChange={setCustomExtra}
          min={0} step={0.01}
          prefix="$"
        />
      ) : (
        <div style={{
          background: 'var(--pdt-paper-deep)',
          border: '1px solid var(--pdt-line-soft)',
          padding: '12px 14px',
        }}>
          <div style={{
            fontFamily: 'var(--pdt-mono)', fontSize: 10,
            color: 'var(--pdt-mute)', letterSpacing: '0.06em',
            textTransform: 'uppercase', marginBottom: 4,
          }}>
            Pay {extraMode === '1.25x' ? '1.25× the base payment'
               : extraMode === '1.5x'  ? '1.5× the base payment'
               : '2× the base payment'}
          </div>
          <div style={{
            fontFamily: 'var(--pdt-sans)', fontSize: 18,
            fontWeight: 500, color: 'var(--pdt-ink)',
            letterSpacing: '-0.01em',
          }}>
            {fmtMoneyFull(extraMonthly)} extra/mo
          </div>
          <div style={{
            fontFamily: 'var(--pdt-serif)', fontStyle: 'italic',
            fontSize: 12, color: 'var(--pdt-mute)', marginTop: 2,
          }}>
            ({fmtMoneyFull(basePayment + extraMonthly)} total/mo)
          </div>
        </div>
      )}
    </>
  );
}

// ─────────────────────────────────────────────────────────────────
// FloatingExtraPaymentBar — sticky portable copy of the control,
// fixed to the bottom-right of the viewport. Shows only after the
// inline control has scrolled out of view.
// ─────────────────────────────────────────────────────────────────
function FloatingExtraPaymentBar({
  visible, forceFront, extraMode, setExtraMode, customExtra, setCustomExtra,
  extraMonthly, basePayment,
}) {
  return (
    <div
      aria-hidden={!visible}
      style={{
        position: 'fixed',
        bottom: 24,
        right: 24,
        transform: visible
          ? 'translate(0, 0)'
          : 'translate(0, 140%)',
        opacity: visible ? 1 : 0,
        pointerEvents: visible ? 'auto' : 'none',
        transition: 'transform .25s ease, opacity .2s ease',
        zIndex: forceFront ? 1100 : 50,
        background: 'var(--pdt-paper)',
        border: '1px solid var(--pdt-line)',
        borderRadius: 2,
        padding: '10px 16px',
        boxShadow: '0 6px 24px -8px rgba(20, 30, 24, 0.18)',
        maxWidth: 'calc(100vw - 48px)',
      }}
    >
      <ExtraPaymentControl
        extraMode={extraMode}
        setExtraMode={setExtraMode}
        customExtra={customExtra}
        setCustomExtra={setCustomExtra}
        extraMonthly={extraMonthly}
        basePayment={basePayment}
        compact
      />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('loan-apr-root')).render(<LoanTool />);
