/* Quantum Arcade — charts.
 *
 * Hand-built SVG rather than a charting library. The whole set is under 300
 * lines and adds nothing to load time, where Chart.js would cost more than the
 * 3D engine. Everything is built with createElementNS, so no untrusted value
 * ever reaches innerHTML.
 *
 * Colour follows the rest of the interface: green is neutral information,
 * amber is money you gained, red is money you lost. Nothing is coloured
 * decoratively. */

const NS = 'http://www.w3.org/2000/svg';

const GREEN = '#00ff9c';
const GREEN_DIM = '#0a7a52';
const AMBER = '#ffb000';
const RED = '#ff3355';
const MAGENTA = '#ff2e88';

function svg(tag, attrs) {
  const el = document.createElementNS(NS, tag);
  for (const [k, v] of Object.entries(attrs || {})) el.setAttribute(k, v);
  return el;
}

function clear(node) {
  while (node.firstChild) node.removeChild(node.firstChild);
}

/* Charts scale to their container: the viewBox is fixed and CSS handles the
 * rest, so one implementation serves phone and desktop. */
function frame(host, w, h) {
  clear(host);
  const root = svg('svg', {
    viewBox: `0 0 ${w} ${h}`,
    preserveAspectRatio: 'none',
    width: '100%',
    height: '100%',
  });
  host.appendChild(root);
  return root;
}

function emptyState(host, message) {
  clear(host);
  const p = document.createElement('p');
  p.className = 'muted small chart-empty';
  p.textContent = message;
  host.appendChild(p);
}

/* ---------------- balance over time ---------------- */

/* An area chart of balance after each transaction, oldest to newest.
 * The fill is split at the starting balance so being up reads amber and being
 * down reads red without needing a legend. */
export function balanceChart(host, entries) {
  if (!entries || entries.length < 2) {
    emptyState(host, 'Play a few rounds and your balance history appears here.');
    return;
  }

  const W = 300, H = 110, PAD = 4;
  const root = frame(host, W, H);

  const values = entries.map((e) => e.BalanceAfter / 1000); // sats
  const min = Math.min(...values);
  const max = Math.max(...values);
  const span = max - min || 1;
  const start = values[0];

  const x = (i) => PAD + (i / (values.length - 1)) * (W - PAD * 2);
  const y = (v) => H - PAD - ((v - min) / span) * (H - PAD * 2);

  // Baseline at the starting balance, so the shape reads against where you began.
  const baseY = y(start);
  root.appendChild(svg('line', {
    x1: 0, y1: baseY, x2: W, y2: baseY,
    stroke: GREEN_DIM, 'stroke-width': 1, 'stroke-dasharray': '3 3', opacity: 0.6,
  }));

  const points = values.map((v, i) => `${x(i)},${y(v)}`).join(' ');
  const up = values[values.length - 1] >= start;
  const colour = up ? AMBER : RED;

  root.appendChild(svg('polygon', {
    points: `${x(0)},${baseY} ${points} ${x(values.length - 1)},${baseY}`,
    fill: colour, opacity: 0.16,
  }));
  root.appendChild(svg('polyline', {
    points, fill: 'none', stroke: colour,
    'stroke-width': 1.6, 'stroke-linejoin': 'round',
  }));

  // Mark the latest point.
  root.appendChild(svg('circle', {
    cx: x(values.length - 1), cy: y(values[values.length - 1]),
    r: 2.6, fill: colour,
  }));
}

/* ---------------- recent crash points ---------------- */

/* Bars of where recent rounds ended. Bars at or above 2x are amber, below are
 * dim — so the streaks you actually care about are visible at a glance. */
export function crashHistoryChart(host, crashes) {
  if (!crashes || crashes.length === 0) {
    emptyState(host, 'No settled rounds yet.');
    return;
  }

  const W = 300, H = 90, PAD = 3;
  const root = frame(host, W, H);

  const shown = crashes.slice(-40);
  // Log scale: crash points are heavy-tailed, and a linear axis makes every
  // round below 10x look identical.
  const scale = (v) => Math.log(Math.max(1, v)) / Math.log(60);
  const bw = (W - PAD * 2) / shown.length;

  shown.forEach((v, i) => {
    const hgt = Math.max(2, scale(v) * (H - PAD * 2));
    root.appendChild(svg('rect', {
      x: PAD + i * bw + bw * 0.15,
      y: H - PAD - hgt,
      width: bw * 0.7,
      height: hgt,
      fill: v >= 10 ? MAGENTA : v >= 2 ? AMBER : GREEN_DIM,
      opacity: v >= 2 ? 0.95 : 0.65,
    }));
  });

  // 2x reference line — the break-even point for the most common target.
  const y2 = H - PAD - scale(2) * (H - PAD * 2);
  root.appendChild(svg('line', {
    x1: 0, y1: y2, x2: W, y2: y2,
    stroke: AMBER, 'stroke-width': 0.8, 'stroke-dasharray': '4 4', opacity: 0.55,
  }));
}

/* ---------------- win / loss split ---------------- */

/* A donut, because the only question it answers is a ratio. */
export function winLossDonut(host, wins, losses) {
  const total = wins + losses;
  if (total === 0) {
    emptyState(host, 'No completed rounds yet.');
    return;
  }

  const S = 120, R = 44, CX = S / 2, CY = S / 2;
  const root = frame(host, S, S);
  const circ = 2 * Math.PI * R;
  const winFrac = wins / total;

  root.appendChild(svg('circle', {
    cx: CX, cy: CY, r: R, fill: 'none',
    stroke: RED, 'stroke-width': 12, opacity: 0.55,
  }));
  root.appendChild(svg('circle', {
    cx: CX, cy: CY, r: R, fill: 'none',
    stroke: AMBER, 'stroke-width': 12,
    'stroke-dasharray': `${circ * winFrac} ${circ}`,
    transform: `rotate(-90 ${CX} ${CY})`,
    'stroke-linecap': 'butt',
  }));

  const pct = svg('text', {
    x: CX, y: CY + 2, 'text-anchor': 'middle',
    fill: AMBER, 'font-size': 20, 'font-family': 'ui-monospace, monospace',
    'font-weight': 700,
  });
  pct.textContent = `${Math.round(winFrac * 100)}%`;
  root.appendChild(pct);

  const label = svg('text', {
    x: CX, y: CY + 18, 'text-anchor': 'middle',
    fill: GREEN_DIM, 'font-size': 8.5, 'font-family': 'ui-monospace, monospace',
    'letter-spacing': 1.5,
  });
  label.textContent = 'CASHED OUT';
  root.appendChild(label);
}

/* ---------------- multiplier distribution ---------------- */

/* A histogram of where rounds ended, with the theoretical curve drawn over it.
 * This is the honest version of a "hot numbers" board: instead of implying a
 * pattern, it shows observed frequency against what the published maths
 * predicts, so a player can see for themselves that they agree. */
export function distributionChart(host, crashes) {
  if (!crashes || crashes.length < 5) {
    emptyState(host, 'Needs a few more rounds before the shape is meaningful.');
    return;
  }

  const W = 300, H = 110, PAD = 4;
  const root = frame(host, W, H);

  const buckets = [
    { label: '1-1.5', lo: 1, hi: 1.5 },
    { label: '1.5-2', lo: 1.5, hi: 2 },
    { label: '2-3', lo: 2, hi: 3 },
    { label: '3-5', lo: 3, hi: 5 },
    { label: '5-10', lo: 5, hi: 10 },
    { label: '10+', lo: 10, hi: Infinity },
  ];

  const counts = buckets.map((b) => crashes.filter((c) => c >= b.lo && c < b.hi).length);
  const maxCount = Math.max(...counts, 1);
  const bw = (W - PAD * 2) / buckets.length;

  // Expected share for each bucket: P(crash >= x) = 0.99 / x.
  const expected = buckets.map((b) => {
    const hi = b.hi === Infinity ? 0 : 0.99 / b.hi;
    return 0.99 / b.lo - hi;
  });

  counts.forEach((n, i) => {
    const hgt = (n / maxCount) * (H - PAD * 2 - 12);
    root.appendChild(svg('rect', {
      x: PAD + i * bw + bw * 0.18,
      y: H - PAD - 12 - hgt,
      width: bw * 0.64,
      height: Math.max(1, hgt),
      fill: GREEN, opacity: 0.5,
    }));

    const label = svg('text', {
      x: PAD + i * bw + bw / 2, y: H - 3,
      'text-anchor': 'middle', fill: GREEN_DIM,
      'font-size': 7, 'font-family': 'ui-monospace, monospace',
    });
    label.textContent = buckets[i].label;
    root.appendChild(label);
  });

  // The predicted shape, scaled to the same axis.
  const maxExpected = Math.max(...expected);
  const curve = expected.map((e, i) => {
    const hgt = (e / maxExpected) * (H - PAD * 2 - 12) * (maxCount / crashes.length) /
      (maxCount / crashes.length);
    return `${PAD + i * bw + bw / 2},${H - PAD - 12 - hgt}`;
  }).join(' ');

  root.appendChild(svg('polyline', {
    points: curve, fill: 'none', stroke: MAGENTA,
    'stroke-width': 1.3, 'stroke-dasharray': '3 2', opacity: 0.9,
  }));
}

/* ---------------- sparkline ---------------- */

/* A tiny inline trend, for stat tiles. */
export function sparkline(host, values) {
  if (!values || values.length < 2) {
    clear(host);
    return;
  }
  const W = 80, H = 22;
  const root = frame(host, W, H);

  const min = Math.min(...values);
  const max = Math.max(...values);
  const span = max - min || 1;
  const points = values.map((v, i) =>
    `${(i / (values.length - 1)) * W},${H - 2 - ((v - min) / span) * (H - 4)}`).join(' ');

  root.appendChild(svg('polyline', {
    points, fill: 'none',
    stroke: values[values.length - 1] >= values[0] ? AMBER : RED,
    'stroke-width': 1.4, 'stroke-linejoin': 'round',
  }));
}
