/* Power - battery detail, rails, thermals. */

import { store, get, num, batteryTone, tempTone, duration } from '../core.js';
import { el, card, pageHead, stat, meter, badge, table, note, emptyState, kv } from '../ui.js';
import { timeSeries, sparkline } from '../charts.js';

export default {
  id: 'power',
  label: 'Power',
  icon: 'power',

  async render() {
    const spec = store.spec;
    const root = el('div.stack');

    root.appendChild(pageHead(
      'Power',
      'Power management unit telemetry. Published at 0.2 Hz, so these values move slowly by design.',
    ));

    /* -- Headline --------------------------------------------------------- */

    const tiles = el('div.grid.cols-4');
    const refs = {};
    for (const [key, label] of [
      ['level', 'Charge'],
      ['voltage', 'Pack voltage'],
      ['current', 'Pack current'],
      ['cell_temp', 'Cell temp'],
      ['pmu_temp', 'PMU temp'],
      ['fan', 'Fan'],
      ['cycles', 'Cycles'],
      ['runtime', 'Est. remaining'],
    ]) {
      const node = stat(label, '—', { sub: ' ' });
      refs[key] = node;
      tiles.appendChild(node);
    }
    root.appendChild(tiles);

    function setTile(key, value, sub, tone = 'default', unit = '') {
      const node = refs[key];
      if (!node) return;
      node.dataset.tone = tone;
      const valueNode = node.querySelector('.stat-value');
      valueNode.textContent = String(value);
      if (unit) valueNode.appendChild(el('span.unit', { text: unit }));
      node.querySelector('.stat-sub').textContent = sub || '';
    }

    /* -- Charts ----------------------------------------------------------- */

    const chargeChart = timeSeries(
      [{ key: 'battery_pct', label: 'Charge', points: [], colorIndex: 0, unit: '%' }],
      { height: 190, precision: 1, yMin: 0, yMax: 100 },
    );

    const electricalChart = timeSeries([
      { key: 'battery_voltage', label: 'Voltage', points: [], colorIndex: 0, unit: ' V' },
    ], { height: 190, precision: 2 });

    const currentChart = timeSeries([
      { key: 'battery_current', label: 'Current', points: [], colorIndex: 1, unit: ' A' },
    ], { height: 190, precision: 2, zeroLine: true });

    const thermalChart = timeSeries([
      { key: 'battery_temp', label: 'Battery cell', points: [], colorIndex: 1, unit: ' °C' },
      { key: 'pmu_temp', label: 'PMU', points: [], colorIndex: 3, unit: ' °C' },
      { key: 'fan_pct', label: 'Fan duty', points: [], colorIndex: 2, unit: ' %' },
    ], { height: 190, precision: 1 });

    root.appendChild(el('div.grid.cols-2', {},
      card('State of charge', { sub: 'Last 2 minutes' }, chargeChart),
      card('Thermals and cooling', { sub: 'Battery, PMU, fan duty' }, thermalChart),
    ));

    root.appendChild(el('div.grid.cols-2', {},
      card('Pack voltage', {}, electricalChart),
      card('Pack current', { sub: 'Positive is charging' }, currentChart),
    ));

    /* -- Rails ------------------------------------------------------------ */

    const railHost = el('div');
    root.appendChild(card('Power rails', { sub: 'Per-rail voltage and draw' }, railHost));

    /* -- Hardware identity ------------------------------------------------ */

    const infoHost = el('div');
    root.appendChild(card('Battery and PMU hardware', {
      sub: 'Reported by the pack itself',
    }, infoHost));

    /* -- Raw -------------------------------------------------------------- */

    const rawHost = el('div');
    root.appendChild(card('Raw PmuState', {
      sub: 'Every field the message carried, unmapped',
    }, rawHost));

    /* -- Painting --------------------------------------------------------- */

    function paint() {
      const s = store.state;
      if (!s) return;

      const pct = s.battery_pct;
      const tone = batteryTone(pct);

      setTile('level', pct == null ? '—' : num(pct, 1), s.charging ? 'charging' : 'discharging', tone, '%');
      setTile('voltage', num(s.battery_voltage, 2), 'pack terminal', 'default', ' V');
      setTile('current', num(s.battery_current, 2),
              s.battery_current > 0 ? 'into pack' : 'out of pack', 'default', ' A');
      setTile('cell_temp', num(s.battery_temp, 1), 'cell', tempTone(s.battery_temp, 45, 55), ' °C');
      setTile('pmu_temp', num(s.pmu_temp, 1), 'controller', tempTone(s.pmu_temp, 60, 75), ' °C');
      setTile('fan', num(s.fan_pct, 0), s.fan_rpm ? `${num(s.fan_rpm, 0)} rpm` : '', 'default', '%');
      setTile('cycles', s.battery_cycles ?? '—', 'charge cycles');

      // Runtime projection from the observed drain rate.
      const runtime = estimateRuntime(pct);
      setTile('runtime', runtime.label, runtime.sub, runtime.tone);

      /* Rails */
      const rails = s.rails || {};
      const railRows = (spec?.pmu_rails || []).map((definition) => {
        const live = rails[definition.key];
        const voltage = live?.voltage;
        const nominal = definition.nominal;
        const deviation = voltage != null && nominal ? Math.abs(voltage - nominal) / nominal : null;
        const ok = live?.ok !== false && (deviation === null || deviation < 0.12);

        return {
          label: definition.label,
          nominal: `${num(nominal, 1)} V`,
          voltage: voltage == null ? '—' : `${num(voltage, 2)} V`,
          current: live?.current == null ? '—' : `${num(live.current, 2)} A`,
          power: voltage != null && live?.current != null
            ? `${num(voltage * live.current, 1)} W` : '—',
          status: live ? badge(ok ? 'nominal' : 'out of range', ok ? 'good' : 'critical')
                       : badge('no data', 'default'),
        };
      });

      railHost.replaceChildren(
        railRows.length
          ? table([
              { key: 'label', label: 'Rail' },
              { key: 'nominal', label: 'Nominal', align: 'right' },
              { key: 'voltage', label: 'Measured', align: 'right' },
              { key: 'current', label: 'Current', align: 'right' },
              { key: 'power', label: 'Power', align: 'right' },
              { key: 'status', label: 'Status' },
            ], railRows)
          : emptyState('No rail data', 'The PMU message has not been received yet.'),
      );

      /* Hardware identity */
      const info = s.custom?.pmu_info || {};
      const infoPairs = (spec?.pmu_info_fields || [])
        .map(([field, label]) => [label, info[field]])
        .filter(([, value]) => value);
      infoHost.replaceChildren(
        infoPairs.length
          ? el('div', { style: { columnWidth: '260px', columnGap: '28px' } }, kv(infoPairs))
          : emptyState('No hardware identity reported',
                       'The PMU message has not arrived yet.'),
      );

      /* Raw fields */
      const raw = s.custom?.pmu_raw;
      if (raw && Object.keys(raw).length) {
        const pairs = Object.entries(raw)
          .filter(([, v]) => typeof v !== 'object')
          .map(([k, v]) => [k, typeof v === 'number' ? num(v, 3) : String(v)]);
        rawHost.replaceChildren(el('div', { style: { columnWidth: '260px', columnGap: '28px' } }, kv(pairs)));
      } else {
        rawHost.replaceChildren(emptyState(
          'No raw message captured',
          store.bridge?.simulated
            ? 'The simulator publishes mapped values only; connect a real robot to see the full PmuState.'
            : 'Waiting for the first /aima/hal/pmu/state message.',
        ));
      }
    }

    function estimateRuntime(pct) {
      const history = seriesCache.battery_pct || [];
      if (pct == null || history.length < 20) {
        return { label: '—', sub: 'needs more history', tone: 'default' };
      }
      const [t0, v0] = history[0];
      const [t1, v1] = history.at(-1);
      const elapsed = t1 - t0;
      const drop = v0 - v1;
      if (elapsed < 10 || drop <= 0.005) {
        return { label: '—', sub: 'not discharging', tone: 'default' };
      }
      const minutes = (pct / (drop / elapsed)) / 60;
      return {
        label: duration(minutes * 60),
        sub: `at ${num((drop / elapsed) * 3600, 1)} %/h`,
        tone: minutes < 15 ? 'critical' : minutes < 40 ? 'warning' : 'good',
      };
    }

    const seriesCache = {};

    async function refreshCharts() {
      try {
        const keys = 'battery_pct,battery_voltage,battery_current,battery_temp,pmu_temp,fan_pct';
        const data = await get(`/api/series?keys=${keys}&limit=600`);
        Object.assign(seriesCache, data);

        chargeChart.update([
          { key: 'battery_pct', label: 'Charge', points: data.battery_pct || [], colorIndex: 0, unit: '%' },
        ]);
        electricalChart.update([
          { key: 'battery_voltage', label: 'Voltage', points: data.battery_voltage || [], colorIndex: 0, unit: ' V' },
        ]);
        currentChart.update([
          { key: 'battery_current', label: 'Current', points: data.battery_current || [], colorIndex: 1, unit: ' A' },
        ]);
        thermalChart.update([
          { key: 'battery_temp', label: 'Battery cell', points: data.battery_temp || [], colorIndex: 1, unit: ' °C' },
          { key: 'pmu_temp', label: 'PMU', points: data.pmu_temp || [], colorIndex: 3, unit: ' °C' },
          { key: 'fan_pct', label: 'Fan duty', points: data.fan_pct || [], colorIndex: 2, unit: ' %' },
        ]);
      } catch { /* best-effort */ }
    }

    paint();
    await refreshCharts();
    paint();

    let last = 0;
    const unsubscribe = store.on('state', () => {
      const now = Date.now();
      if (now - last < 1000) return;
      last = now;
      paint();
    });
    const timer = setInterval(() => { refreshCharts().then(paint); }, 4000);

    return { node: root, dispose: () => { unsubscribe(); clearInterval(timer); } };
  },
};
