/* Sensors - IMU, touch, LiDAR and topic liveness. */

import { store, get, num, clockTime, RAD2DEG } from '../core.js';
import { el, card, pageHead, badge, table, stat, note, emptyState, kv, button } from '../ui.js';
import { timeSeries, attitude } from '../charts.js';

export default {
  id: 'sensors',
  label: 'Sensors',
  icon: 'sensors',

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

    root.appendChild(pageHead(
      'Sensors',
      'Hardware abstraction layer feeds — IMU, touch, LiDAR — and how fresh each topic is.',
    ));

    /* -- IMU -------------------------------------------------------------- */

    const imuHosts = {
      chest: { attitude: el('div.attitude'), readout: el('div') },
      torso: { attitude: el('div.attitude'), readout: el('div') },
    };

    const imuChart = timeSeries([
      { key: 'imu_roll', label: 'Roll', points: [], colorIndex: 0, unit: '°' },
      { key: 'imu_pitch', label: 'Pitch', points: [], colorIndex: 1, unit: '°' },
    ], { height: 176, precision: 2, zeroLine: true });

    root.appendChild(el('div.grid.cols-2', {},
      card('Chest IMU', { sub: '/aima/hal/imu/chest/state · 500 Hz' },
        el('div.row', { style: { gap: '18px', alignItems: 'flex-start' } },
          imuHosts.chest.attitude,
          el('div', { style: { flex: '1', minWidth: '160px' } }, imuHosts.chest.readout),
        ),
      ),
      card('Torso IMU', { sub: '/aima/hal/imu/torso/state · 500 Hz' },
        el('div.row', { style: { gap: '18px', alignItems: 'flex-start' } },
          imuHosts.torso.attitude,
          el('div', { style: { flex: '1', minWidth: '160px' } }, imuHosts.torso.readout),
        ),
      ),
    ));

    root.appendChild(card('Attitude history', { sub: 'Chest IMU, degrees' }, imuChart));

    /* -- Touch + LiDAR ---------------------------------------------------- */

    const touchHost = el('div');
    const odomHost = el('div');

    root.appendChild(el('div.grid.cols-2', {},
      card('Head touch', { sub: '/aima/hal/sensor/touch_head · 8 zones · 100 Hz' }, touchHost),
      card('Odometry', { sub: '/aima/mc/leg_odometry' }, odomHost),
    ));

    /* -- Topic health ----------------------------------------------------- */

    const healthHost = el('div');
    root.appendChild(card('Topic health', {
      sub: 'Measured rate against the documented rate',
      actions: [button('Refresh', () => refreshTopics(), { size: 'sm', style: 'ghost', iconName: 'refresh' })],
    }, healthHost));

    let tracked = [];

    async function refreshTopics() {
      try {
        const data = await get('/api/topics');
        tracked = data.tracked || [];
        paintHealth();
      } catch {
        healthHost.replaceChildren(emptyState('Could not read topic statistics'));
      }
    }

    function paintHealth() {
      const documented = spec?.sensor_topics || [];
      const byTopic = Object.fromEntries(tracked.map((t) => [t.topic, t]));
      const now = Date.now() / 1000;

      const rows = documented.map((doc) => {
        const live = byTopic[doc.topic];
        const age = live ? now - live.last : null;
        const stale = age === null || age > 3;
        const rateRatio = live && doc.rate_hz ? live.hz / doc.rate_hz : null;

        let tone = 'critical';
        let label = 'silent';
        if (live && !stale) {
          if (rateRatio === null || rateRatio > 0.6) { tone = 'good'; label = 'healthy'; }
          else { tone = 'warning'; label = 'slow'; }
        } else if (live) {
          tone = 'warning'; label = 'stale';
        }

        return {
          topic: doc.topic,
          label: doc.label,
          type: doc.type,
          expected: doc.rate_hz ? `${doc.rate_hz} Hz` : '—',
          actual: live ? `${num(live.hz, 1)} Hz` : '—',
          count: live ? live.count.toLocaleString() : '0',
          age: age === null ? '—' : `${num(age, 1)} s`,
          status: badge(label, tone),
        };
      });

      // Anything the bridge saw that is not in the documented list still matters.
      for (const live of tracked) {
        if (documented.some((d) => d.topic === live.topic)) continue;
        rows.push({
          topic: live.topic, label: '—', type: '—',
          expected: '—', actual: `${num(live.hz, 1)} Hz`,
          count: live.count.toLocaleString(),
          age: `${num(now - live.last, 1)} s`,
          status: badge('extra', 'accent'),
        });
      }

      healthHost.replaceChildren(table([
        { key: 'label', label: 'Sensor' },
        { key: 'topic', label: 'Topic' },
        { key: 'type', label: 'Type' },
        { key: 'expected', label: 'Expected', align: 'right' },
        { key: 'actual', label: 'Measured', align: 'right' },
        { key: 'count', label: 'Messages', align: 'right' },
        { key: 'age', label: 'Last seen', align: 'right' },
        { key: 'status', label: 'Status' },
      ], rows, { empty: 'No topics observed yet' }));
    }

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

    function paintImu(key) {
      const data = store.state?.imu?.[key];
      const host = imuHosts[key];
      if (!host) return;

      if (!data) {
        host.attitude.replaceChildren();
        host.readout.replaceChildren(emptyState('No data', 'Nothing received on this IMU topic.'));
        return;
      }

      host.attitude.replaceChildren(attitude(data.roll || 0, data.pitch || 0, { size: 132 }));
      host.readout.replaceChildren(kv([
        ['Roll', `${num((data.roll || 0) * RAD2DEG, 2)}°`],
        ['Pitch', `${num((data.pitch || 0) * RAD2DEG, 2)}°`],
        ['Yaw', `${num((data.yaw || 0) * RAD2DEG, 2)}°`],
        ['Accel X', `${num(data.accel_x, 2)} m/s²`],
        ['Accel Y', `${num(data.accel_y, 2)} m/s²`],
        ['Accel Z', `${num(data.accel_z, 2)} m/s²`],
        ['Gyro Z', `${num(data.gyro_z, 3)} rad/s`],
      ]));
    }

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

      paintImu('chest');
      paintImu('torso');

      const touch = s.touch_head || {};
      const zones = Array.isArray(touch.zones) ? touch.zones : [];
      const raw = Array.isArray(touch.data) ? touch.data : [];

      touchHost.replaceChildren(
        el('div.row', { style: { marginBottom: '12px' } },
          badge(touch.touched ? 'Contact' : 'No contact', touch.touched ? 'good' : 'default'),
          badge(`${zones.filter(Boolean).length} of ${zones.length || 8} active`),
        ),
        zones.length
          ? el('div', {
              style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '6px' },
            },
              ...zones.map((active, index) => el('div', {
                style: {
                  padding: '13px 6px', textAlign: 'center',
                  borderRadius: 'var(--radius-sm)',
                  background: active ? 'var(--accent-soft)' : 'var(--surface-2)',
                  border: `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
                  fontSize: '11px', fontWeight: '600',
                  color: active ? 'var(--accent)' : 'var(--text-3)',
                  transition: 'background .15s, border-color .15s',
                },
              },
                el('div', { text: `Z${index + 1}` }),
                raw.length > index
                  ? el('div', {
                      text: String(raw[index]),
                      style: { fontSize: '9.5px', fontFamily: 'var(--mono)', opacity: '.75' },
                    })
                  : null,
              )),
            )
          : note('No TouchState message received yet.', 'default'),
      );

      const odom = s.odom || {};
      const vel = s.velocity || {};
      odomHost.replaceChildren(
        kv([
          ['Position X', `${num(odom.x, 3)} m`],
          ['Position Y', `${num(odom.y, 3)} m`],
          ['Heading', `${num((odom.yaw || 0) * RAD2DEG, 1)}°`],
          ['Forward speed', `${num(vel.forward, 3)} m/s`],
          ['Lateral speed', `${num(vel.lateral, 3)} m/s`],
          ['Yaw rate', `${num(vel.angular, 3)} rad/s`],
        ]),
        el('div', { style: { marginTop: '12px' } },
          note('Leg odometry publishes while the robot is walking. It sits still in Passive or '
             + 'Damping mode, which is why these values stop updating when the robot is parked.',
             'info'),
        ),
      );
    }

    async function refreshChart() {
      try {
        const data = await get('/api/series?keys=imu_roll,imu_pitch&limit=400');
        const toDeg = (points) => (points || []).map(([t, v]) => [t, v * RAD2DEG]);
        imuChart.update([
          { key: 'imu_roll', label: 'Roll', points: toDeg(data.imu_roll), colorIndex: 0, unit: '°' },
          { key: 'imu_pitch', label: 'Pitch', points: toDeg(data.imu_pitch), colorIndex: 1, unit: '°' },
        ]);
      } catch { /* chart refresh is best-effort */ }
    }

    paint();
    refreshTopics();
    refreshChart();

    let last = 0;
    const unsubscribe = store.on('state', () => {
      const now = Date.now();
      if (now - last < 400) return;
      last = now;
      paint();
    });
    const timer = setInterval(() => { refreshTopics(); refreshChart(); }, 3000);

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