"use client"; import { useMemo, useState } from "react"; import type { ValuationResponse } from "@/types/api"; import { deltaClass, fmtCurrency, fmtLarge, fmtPct } from "@/lib/format"; import { computeDcf } from "@/lib/dcf"; type Props = { data: ValuationResponse }; function pctVsCurrent(implied?: number | null, current?: number | null): number | null { if (implied == null || current == null || current === 0) return null; return (implied - current) / current; } function SummaryChip({ label, price, current, accent = false, customised = false, }: { label: string; price?: number | null; current?: number | null; accent?: boolean; customised?: boolean; }) { const pct = pctVsCurrent(price, current); return (
{label} {customised && } {price != null ? fmtCurrency(price) : "—"} {pct != null && ( {fmtPct(pct, 1, true)} )}
); } function MultipleRow({ label, multiple, price, current, }: { label: string; multiple?: number | null; price?: number | null; current?: number | null; }) { const pct = pctVsCurrent(price, current); return (
{label} {multiple != null ? `${multiple.toFixed(1)}×` : "—"} {price != null ? fmtCurrency(price) : "—"} {pct != null ? fmtPct(pct, 1, true) : "—"}
); } function DCFSlider({ label, value, min, max, step, fmt, onChange, }: { label: string; value: number; min: number; max: number; step: number; fmt: (v: number) => string; onChange: (v: number) => void; }) { return (
{label} {fmt(value)}
onChange(parseFloat(e.target.value))} />
{fmt(min)} {fmt(max)}
); } export function ValuationCard({ data }: Props) { const { dcf, ev_ebitda, ev_revenue, price_to_book, current_price } = data; // Slider defaults captured once from the initial API response const [defaults] = useState(() => ({ wacc: dcf.wacc, terminalGrowth: dcf.terminal_growth, projectionYears: dcf.projection_years, growthRate: dcf.growth_rate_used ?? 0.05, })); const [wacc, setWacc] = useState(defaults.wacc); const [terminalGrowth, setTerminalGrowth] = useState(defaults.terminalGrowth); const [projectionYears, setProjectionYears] = useState(defaults.projectionYears); const [growthRate, setGrowthRate] = useState(defaults.growthRate); const isCustomised = wacc !== defaults.wacc || terminalGrowth !== defaults.terminalGrowth || projectionYears !== defaults.projectionYears || growthRate !== defaults.growthRate; function handleReset() { setWacc(defaults.wacc); setTerminalGrowth(defaults.terminalGrowth); setProjectionYears(defaults.projectionYears); setGrowthRate(defaults.growthRate); } // equityBridge = net_debt + preferred equity + minority interest. // Derived as enterprise_value - equity_value (both from API); stays fixed while sliders move. const equityBridge = dcf.enterprise_value != null && dcf.equity_value != null ? dcf.enterprise_value - dcf.equity_value : (dcf.net_debt ?? 0); const computed = useMemo(() => { if (!dcf.available || dcf.error || !dcf.base_fcf || !data.shares_outstanding) return null; return computeDcf( { baseFcf: dcf.base_fcf, equityBridge, sharesOutstanding: data.shares_outstanding }, { wacc, terminalGrowth, projectionYears, growthRate } ); }, [wacc, terminalGrowth, projectionYears, growthRate, equityBridge, dcf, data.shares_outstanding]); const livePrice = computed && !("error" in computed) ? computed.intrinsicValuePerShare : null; const hasMultiples = ev_ebitda.available || ev_revenue.available || price_to_book.available; return (
{/* Summary strip */}
Market Price {current_price != null ? fmtCurrency(current_price) : "—"}
{/* DCF detail */}
Discounted Cash Flow {dcf.available && !dcf.error && isCustomised && ( )}
{!dcf.available && (

Insufficient free cash flow history for DCF.

)} {dcf.available && dcf.error && (

{dcf.error}

)} {dcf.available && !dcf.error && (
{/* Left: sliders */}
Assumptions
`${(v * 100).toFixed(1)}%`} onChange={setWacc} /> `${(v * 100).toFixed(2)}%`} onChange={setTerminalGrowth} /> `${(v * 100).toFixed(0)}%`} onChange={setGrowthRate} /> `${v} yr`} onChange={(v) => setProjectionYears(Math.round(v))} />
{/* Divider */}
{/* Right: results */}
Results
{computed && "error" in computed ? (

{computed.error}

) : ( <>
Base FCF (TTM) {dcf.base_fcf != null ? fmtLarge(dcf.base_fcf) : "—"}
Historical growth {dcf.growth_rate_used != null ? fmtPct(dcf.growth_rate_used) : "—"}
FCF PV Sum {computed ? fmtLarge(computed.fcfPvSum) : "—"}
Terminal Value PV {computed ? fmtLarge(computed.terminalValuePv) : "—"}
Enterprise Value {computed ? fmtLarge(computed.enterpriseValue) : "—"}
Net Debt {dcf.net_debt != null ? fmtLarge(dcf.net_debt) : "—"}
Intrinsic Value {livePrice != null ? fmtCurrency(livePrice) : "—"} {data.shares_outstanding != null && ( {fmtLarge(data.shares_outstanding)} shares )}
)}
)}
{/* Multiples */} {hasMultiples && (
Multiples — at current market multiple
{ev_ebitda.available && ( )} {ev_revenue.available && ( )} {price_to_book.available && ( )}
)}
); }