// Cash-distribution timeline — cumulative-position (J-curve) view.
//
// Renders the investor's running net cash balance over time: starts at zero,
// dips below as capital is called, bottoms out at peak deployment, climbs
// back through breakeven to total return. Each product type produces a
// distinctive silhouette — equity is a deep V with a vertical exit jump,
// debt is a shallow U with a stair-step coupon climb, long-term-hold is a
// slow wide curve that may stay underwater for years.
//
// Mounts as window.TimelineStory({ product, amount, dark, width }). Reads
// window.buildTimelineData (loaded by /timeline-data.js).

const { useState: tlUseState, useMemo: tlUseMemo } = React;

function tlQuarterIndex(q) {
  return q.year * 4 + (q.q - 1);
}
function tlAddQuarters(q, n) {
  const total = q.year * 4 + (q.q - 1) + n;
  const year = Math.floor(total / 4);
  return { year, q: total - year * 4 + 1 };
}
function tlQuarterLabel(q) {
  return "Q" + q.q + " '" + String(q.year).slice(-2);
}
function tlFormatShort(n) {
  const a = Math.abs(n);
  if (a >= 1_000_000) return "$" + (n / 1_000_000).toFixed(2) + "M";
  if (a >= 1_000)     return "$" + Math.round(n / 1_000) + "k";
  return "$" + Math.round(n);
}
function tlFormatFull(n) {
  return "$" + Math.round(Math.abs(n)).toLocaleString();
}

// Pick "nice" round-number axis ticks across [min, max], targeting ~count
// gridlines. Rounds the step to a 1/2/5×10ⁿ step so labels read cleanly
// (e.g., -$100k, -$50k, $0, +$50k rather than -$93.4k, -$46.7k…).
function tlNiceTicks(min, max, count) {
  const range = max - min;
  if (range <= 0) return [min];
  const rough = range / Math.max(1, count - 1);
  const mag = Math.pow(10, Math.floor(Math.log10(rough)));
  const norm = rough / mag;
  let nice = 10;
  if (norm < 1.5) nice = 1;
  else if (norm < 3) nice = 2;
  else if (norm < 7) nice = 5;
  const step = nice * mag;
  const start = Math.ceil(min / step) * step;
  const out = [];
  for (let v = start; v <= max + 1e-6; v += step) out.push(Math.round(v));
  return out;
}

function tlFormatTick(n) {
  if (n === 0) return "$0";
  const sign = n > 0 ? "+" : "−";
  return sign + tlFormatShort(Math.abs(n));
}

// ─── Tooltip ───

function Tooltip({ show, x, y, children }) {
  return (
    <div role="tooltip" style={{
      position: "fixed", left: x + 14, top: y + 14,
      background: "#393433", color: "#fff", padding: "6px 10px",
      fontSize: 11, lineHeight: 1.4, pointerEvents: "none",
      opacity: show ? 1 : 0, transition: "opacity 0.15s",
      maxWidth: 240, zIndex: 100,
    }}>
      {children}
    </div>
  );
}

// ─── J-curve view ───
//
// Builds the cumulative-balance series from the product's cashFlows, scaled
// to the active scenario (so exit/distribution magnitudes flex with the
// Low/Mid/High toggle). The line is rendered twice — clipped above and
// below the zero baseline — so positive and negative travel read in
// distinct colors without an SVG mask.

function JCurveView({ data, scenario, width, height, dark }) {
  const [tip, setTip] = tlUseState(null);
  const id = data.productId; // unique clip ids per chart instance

  const ink   = dark ? "#FAF7F3" : "#393433";
  const muted = dark ? "#8A827F" : "#6B6361";
  const grid  = dark ? "#3A3635" : "#C9C4C2";
  const posBand = dark ? "rgba(72, 102, 178, 0.15)"  : "#EEF2FB";
  const negBand = dark ? "rgba(177, 75, 75, 0.18)"   : "#F5E6E6";
  const posLine = "#4866B2";
  const negLine = "#B14B4B";

  const padL = 56, padR = 16, padT = 26, padB = 40;
  const innerW = width - padL - padR;
  const innerH = height - padT - padB;

  // Scale cashFlows to the chosen scenario. The mid case is canonical from
  // buildTimelineData; multiply distribution/exit amounts by the ratio of
  // chosen scenario / mid so the curve flexes with the toggle.
  const midTotal  = data.scenarios.mid.totalReturn;
  const chosenTotal = data.scenarios[scenario].totalReturn;
  const ratio = midTotal > 0 ? chosenTotal / midTotal : 1;
  const scaledFlows = data.cashFlows.map((cf) =>
    cf.type === "call" ? cf : { ...cf, amount: cf.amount * ratio }
  );

  // Determine the time axis range — start of investment to last cash flow.
  const startQ = data.startQuarter;
  const endQ = data.exitQuarter || scaledFlows[scaledFlows.length - 1].quarter;
  const startIdx = tlQuarterIndex(startQ);
  const endIdx   = tlQuarterIndex(endQ);
  const span = Math.max(1, endIdx - startIdx);
  const xFor = (q) => padL + ((tlQuarterIndex(q) - startIdx) / span) * innerW;

  // Build cumulative balance per quarter inclusive of any flows in that quarter.
  const points = [];
  let bal = 0;
  for (let i = 0; i <= span; i++) {
    const t = startIdx + i;
    const flowsHere = scaledFlows.filter((f) => tlQuarterIndex(f.quarter) === t);
    flowsHere.forEach((f) => { bal += f.amount; });
    const year = Math.floor(t / 4);
    const qNum = t - year * 4 + 1;
    points.push({ q: { year, q: qNum }, bal, hasFlow: flowsHere.length > 0 });
  }

  const minBal = Math.min(0, ...points.map((p) => p.bal));
  const maxBal = Math.max(0, ...points.map((p) => p.bal));
  const yScale = innerH / (maxBal - minBal || 1);
  const yFor = (b) => padT + (maxBal - b) * yScale;
  const zeroY = yFor(0);

  // Path string — every quarter's running balance, drawn as a polyline.
  const pathD = points.map((p, i) => {
    const x = xFor(p.q);
    const y = yFor(p.bal);
    return (i === 0 ? "M" : "L") + x + "," + y;
  }).join(" ");

  // Year-labeled tick marks.
  const ticks = [];
  for (let i = 0; i <= span; i++) {
    const t = startIdx + i;
    const year = Math.floor(t / 4);
    const qNum = t - year * 4 + 1;
    const x = padL + (i / span) * innerW;
    const isYear = qNum === 1 || i === 0;
    ticks.push({ x, year, qNum, isYear });
  }

  // Trough (peak deployment) and final balance.
  const trough = points.reduce((m, p) => (p.bal < m.bal ? p : m), points[0]);
  const last   = points[points.length - 1];
  const positiveFinal = last.bal >= 0;

  return (
    <div style={{ position: "relative" }}>
      <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
        <defs>
          <clipPath id={`tl-pos-${id}`}>
            <rect x={padL} y={padT} width={innerW} height={Math.max(0, zeroY - padT)} />
          </clipPath>
          <clipPath id={`tl-neg-${id}`}>
            <rect x={padL} y={zeroY} width={innerW} height={Math.max(0, padT + innerH - zeroY + 2)} />
          </clipPath>
        </defs>

        {/* Above- and below-water tinted bands */}
        <rect x={padL} y={padT}  width={innerW} height={Math.max(0, zeroY - padT)} fill={posBand} />
        <rect x={padL} y={zeroY} width={innerW} height={Math.max(0, padT + innerH - zeroY)} fill={negBand} />

        {/* Y-axis gridlines + dollar labels — skip the zero line because the
            zero baseline is rendered separately with a heavier stroke. */}
        {tlNiceTicks(minBal, maxBal, 5).map((v) => {
          if (v === 0) return null;
          const y = yFor(v);
          return (
            <g key={v}>
              <line x1={padL} y1={y} x2={padL + innerW} y2={y}
                stroke={grid} strokeWidth={1} strokeDasharray="2 4" opacity="0.55" />
              <text x={padL - 6} y={y + 3} textAnchor="end"
                fontFamily="ui-monospace,Menlo,monospace" fontSize={9} fill={muted}>
                {tlFormatTick(v)}
              </text>
            </g>
          );
        })}
        {/* Zero label (paired with the heavier baseline) */}
        <text x={padL - 6} y={zeroY + 3} textAnchor="end"
          fontFamily="ui-monospace,Menlo,monospace" fontSize={9} fill={ink}>
          $0
        </text>

        {/* Cumulative line — drawn twice with different clips */}
        <path d={pathD} stroke={negLine} strokeWidth={2} fill="none"
          clipPath={`url(#tl-neg-${id})`} strokeLinejoin="round" />
        <path d={pathD} stroke={posLine} strokeWidth={2} fill="none"
          clipPath={`url(#tl-pos-${id})`} strokeLinejoin="round" />

        {/* Zero baseline */}
        <line x1={padL} y1={zeroY} x2={padL + innerW} y2={zeroY} stroke={ink} strokeWidth={1} />

        {/* Quarter ticks + year labels */}
        {ticks.map(({ x, year, qNum, isYear }) => (
          <g key={`${year}-${qNum}`}>
            <line x1={x} y1={zeroY - 2} x2={x} y2={zeroY + (isYear ? 6 : 3)} stroke={grid} />
            {isYear && (
              <>
                <text x={x} y={padT + innerH + 14} textAnchor="middle"
                  fontFamily="ui-monospace,Menlo,monospace" fontSize={10} fill={muted}>
                  {`Y${year - startQ.year}`}
                </text>
                <text x={x} y={padT + innerH + 26} textAnchor="middle"
                  fontFamily="ui-monospace,Menlo,monospace" fontSize={9} fill={muted}>
                  {tlQuarterLabel({ year, q: qNum })}
                </text>
              </>
            )}
          </g>
        ))}

        {/* Today marker — lime spine */}
        {(() => {
          const todayX = xFor(data.todayQuarter || startQ);
          return (
            <g>
              <line x1={todayX} y1={padT - 4} x2={todayX} y2={padT + innerH + 4}
                stroke="#EDFE38" strokeWidth={3} />
              <text x={todayX} y={padT - 8} textAnchor="middle"
                fontFamily="ui-monospace,Menlo,monospace" fontSize={9}
                letterSpacing="0.16em" fill={ink}>
                TODAY
              </text>
            </g>
          );
        })()}

        {/* Event dots — slightly larger for major events (calls and exits). */}
        {scaledFlows.map((cf, i) => {
          const t = tlQuarterIndex(cf.quarter);
          const p = points.find((p) => tlQuarterIndex(p.q) === t);
          if (!p) return null;
          const x = xFor(cf.quarter);
          const y = yFor(p.bal);
          const isMajor = cf.type === "exit" || (cf.type === "call" && Math.abs(cf.amount) > 10000);
          const r = isMajor ? 4 : 2.5;
          const fill = cf.amount >= 0 ? posLine : negLine;
          const tipText =
            (cf.type === "call" ? "Capital call" : cf.type === "exit" ? "Exit" : "Distribution") +
            " · " + tlQuarterLabel(cf.quarter) + " · " +
            (cf.amount < 0 ? "−" : "+") + tlFormatShort(cf.amount);
          return (
            <circle key={i}
              cx={x} cy={y} r={r}
              fill={fill}
              stroke={isMajor ? (dark ? "#0D0D0D" : "#fff") : "none"}
              strokeWidth={isMajor ? 1 : 0}
              onMouseEnter={(e) => setTip({ text: tipText, x: e.clientX, y: e.clientY })}
              onMouseMove={(e) => setTip({ text: tipText, x: e.clientX, y: e.clientY })}
              onMouseLeave={() => setTip(null)}
              style={{ cursor: "default" }}
            />
          );
        })}

      </svg>
      {/* Stats strip below the chart — kept out of the SVG so the chart can use
          the full panel width on the narrow single-column layout. */}
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "baseline",
        marginTop: 8, padding: `0 ${padR}px 0 ${padL}px`,
      }}>
        <div>
          <div style={{
            fontFamily: "ui-monospace,Menlo,monospace", fontSize: 9,
            letterSpacing: "0.14em", color: muted,
          }}>
            PEAK DEPLOYED
          </div>
          <div style={{ fontSize: 15, fontWeight: 500, color: negLine, marginTop: 2 }}>
            {tlFormatShort(trough.bal)}
          </div>
        </div>
        <div style={{ textAlign: "right" }}>
          <div style={{
            fontFamily: "ui-monospace,Menlo,monospace", fontSize: 9,
            letterSpacing: "0.14em", color: muted,
          }}>
            FINAL{data.exitQuarter === null ? " (Y6)" : ""}
          </div>
          <div style={{
            fontSize: 15, fontWeight: 500, marginTop: 2,
            color: positiveFinal ? posLine : negLine,
          }}>
            {(positiveFinal ? "+" : "") + tlFormatShort(last.bal)}
          </div>
        </div>
      </div>
      <Tooltip show={!!tip} x={tip ? tip.x : 0} y={tip ? tip.y : 0}>
        {tip ? tip.text : ""}
      </Tooltip>
    </div>
  );
}

// ─── Top-level mount ───

function TimelineStory({ product, amount, dark, width = 780 }) {
  // Scenario is fixed to "mid" — the same mid-of-range that the calculator
  // box above uses, so projected exit and J-curve final always agree.
  const scenario = "mid";

  const data = tlUseMemo(() => {
    if (!product || !window.buildTimelineData) return null;
    try {
      return window.buildTimelineData(product, amount);
    } catch (e) {
      // Unsupported types (e.g., pre-sale) — caller renders nothing.
      console.warn("[timeline-story]", e.message);
      return null;
    }
  }, [product && product.id, amount]);

  if (!data) return null;

  const text = dark ? "#FAF7F3" : "#393433";
  const muted = dark ? "#8A827F" : "#6B6361";
  const border = dark ? "#3A3635" : "#E2E0DF";
  const surface = dark ? "#1B1816" : "#FAF7F3";

  return (
    <div style={{ padding: "18px 28px", borderBottom: `1px solid ${border}` }}>
      <div style={{
        fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: "0.18em",
        textTransform: "uppercase", color: muted, marginBottom: 10,
      }}>
        Cash position over time
      </div>
      <div style={{ background: surface, padding: 12, color: text }}>
        <JCurveView data={data} scenario={scenario} width={width} height={260} dark={dark} />
      </div>
      <div style={{
        marginTop: 8, fontSize: 11, color: muted, lineHeight: 1.5, maxWidth: 600,
      }}>
        Net cash position from your perspective. Goes <span style={{ color: "#B14B4B", fontWeight: 500 }}>negative</span> as capital is called, climbs back through breakeven to <span style={{ color: "#4866B2", fontWeight: 500 }}>positive</span> as distributions and exit proceeds arrive.
        {product && product.type === "long-term-hold" && (
          <>
            {" "}Quarterly rental distributions escalate annually at the project's rent-growth assumption ({(product.rentEscalationPct || 0).toFixed(1)}%), and the terminal jump reflects projected equity appreciation at sale (compounded at portfolio CAGR of {(product.appreciationCagrHistorical || 0).toFixed(1)}%).
          </>
        )}
      </div>
    </div>
  );
}

window.TimelineStory = TimelineStory;
