/* Console - event log, raw ROS access, discovered graph. */

import { store, get, post, toast, clockTime, num } from '../core.js';
import {
  el, card, pageHead, button, note, badge, table, input, textarea, field,
  kv, segmented, emptyState, setChildren,
} from '../ui.js';

export default {
  id: 'console',
  label: 'Console',
  icon: 'console',

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

    root.appendChild(pageHead(
      'Console',
      'Everything the bridge has logged, plus direct access to publish a topic or call a service.',
    ));

    /* ==================================================================
       Event log
       ================================================================== */

    let levelFilter = 'all';
    let autoScroll = true;
    const logHost = el('div.log');

    function paintLog() {
      const events = store.events.filter(
        (e) => levelFilter === 'all' || e.level === levelFilter,
      );

      if (!events.length) {
        logHost.replaceChildren(emptyState('Nothing logged yet'));
        return;
      }

      const wasAtBottom = logHost.scrollHeight - logHost.scrollTop - logHost.clientHeight < 40;

      logHost.replaceChildren(...events.slice(-400).map((event) => el('div.log-row', {},
        el('span.log-time', { text: clockTime(event.ts) }),
        el('span.log-level', { text: event.level, dataset: { level: event.level } }),
        el('span.log-source', { text: event.source, title: event.source }),
        el('span.log-msg', { text: event.message }),
      )));

      if (autoScroll && wasAtBottom) logHost.scrollTop = logHost.scrollHeight;
    }

    const levelTabs = segmented(
      [
        { value: 'all', label: 'All' },
        { value: 'info', label: 'Info' },
        { value: 'warn', label: 'Warnings' },
        { value: 'error', label: 'Errors' },
      ],
      levelFilter,
      (value) => { levelFilter = value; paintLog(); },
    );

    root.appendChild(card('Event log', {
      sub: 'Bridge, control and extension events',
      actions: [
        levelTabs,
        button('Clear view', () => { store.events.length = 0; paintLog(); }, { size: 'sm', style: 'ghost' }),
      ],
      flush: true,
    }, logHost));

    /* ==================================================================
       Raw publish / service call
       ================================================================== */

    const pubTopic = input({ placeholder: '/aima/hal/joint/head/command' });
    const pubType = input({ placeholder: 'aimdk_msgs/msg/JointCommandArray' });
    const pubFields = textarea({ placeholder: '{\n  "field": 1.0\n}', rows: 5 });

    const publishBtn = button('Publish', async (node) => {
      let fields;
      try {
        fields = pubFields.value.trim() ? JSON.parse(pubFields.value) : {};
      } catch (err) {
        toast('Invalid JSON', err.message, 'critical');
        return;
      }
      node.disabled = true;
      try {
        const data = await post('/api/raw/publish', {
          topic: pubTopic.value.trim(),
          type: pubType.value.trim(),
          fields,
        });
        toast('Published', data.message, data.ok ? 'good' : 'critical');
      } catch (err) {
        toast('Publish failed', err.message, 'critical');
      } finally {
        node.disabled = false;
      }
    }, { style: 'primary' });

    const agentHost = el('div');

    root.appendChild(el('div.grid.cols-2', {},
      card('Publish a topic', { sub: 'Raw escape hatch' },
        el('div.stack', {},
          field('Topic', pubTopic),
          field('Message type', pubType, 'Package/kind/Name, e.g. aimdk_msgs/msg/McLocomotionVelocity'),
          field('Fields (JSON)', pubFields,
                'Only top-level fields the message actually defines are set; the rest are ignored.'),
          publishBtn,
          note('This bypasses every guard the Control tab applies. Know what the message does '
             + 'before you send it.', 'warning', '⚠'),
        ),
      ),
      card('Robot agent', { sub: 'The process bridging ROS 2 to this dashboard' }, agentHost),
    ));

    /* ==================================================================
       Discovered graph
       ================================================================== */

    const graphHost = el('div');
    let graphFilter = '';
    const graphSearch = input({ placeholder: 'Filter topics and services…' });
    graphSearch.addEventListener('input', () => {
      graphFilter = graphSearch.value.toLowerCase();
      paintGraph();
    });

    let graph = { topics: [], services: [] };

    async function refreshGraph() {
      try {
        // Enumerating the graph is a round trip to the robot, so it is opt-in
        // rather than part of the routine topic-stats poll.
        const data = await get('/api/topics?graph=true');
        graph = data.graph || { topics: [], services: [], nodes: [] };
        paintGraph();
      } catch { /* best-effort */ }
    }

    function paintAgent() {
      const agent = store.bridge?.agent || store.state?.custom?.agent || null;
      const connection = store.state?.connection || {};
      setChildren(agentHost,
        el('div.row', { style: { marginBottom: '12px' } },
          badge(connection.online ? 'connected' : 'not connected',
                connection.online ? 'good' : 'critical'),
          store.bridge?.simulated ? badge('simulation', 'warning') : null,
        ),
        kv([
          ['Address', connection.host || store.bridge?.host || '—'],
          ['Agent version', agent?.agent_version || '—'],
          ['Robot hostname', agent?.hostname || '—'],
          ['ROS domain', agent?.ros_domain_id ?? '—'],
          ['Link uptime', connection.uptime_s ? `${Math.round(connection.uptime_s)} s` : '—'],
          ['Cameras', (agent?.cameras || []).length || '—'],
        ]),
        connection.error
          ? el('div', { style: { marginTop: '10px' } }, note(connection.error, 'critical', '⚠'))
          : null,
      );
    }

    function paintGraph() {
      const match = (entry) => !graphFilter || entry.name.toLowerCase().includes(graphFilter);
      const topics = (graph.topics || []).filter(match);
      const services = (graph.services || []).filter(match);

      if (!graph.topics?.length && !graph.services?.length) {
        graphHost.replaceChildren(emptyState(
          'No ROS graph available',
          store.bridge?.simulated
            ? 'The simulator has no ROS graph. Connect to a real robot to enumerate topics and services.'
            : 'The bridge has not enumerated the graph yet — it refreshes every 5 seconds.',
        ));
        return;
      }

      graphHost.replaceChildren(
        el('div.grid.cols-2', {},
          el('div', {},
            el('div.row', { style: { marginBottom: '8px' } },
              el('strong', { text: 'Topics', style: { fontSize: '12px' } }),
              badge(String(topics.length)),
            ),
            table([
              { key: 'name', label: 'Name' },
              { key: 'types', label: 'Type', get: (r) => (r.types || []).join(', ') },
            ], topics.slice(0, 300), { empty: 'No matching topics' }),
          ),
          el('div', {},
            el('div.row', { style: { marginBottom: '8px' } },
              el('strong', { text: 'Services', style: { fontSize: '12px' } }),
              badge(String(services.length)),
            ),
            table([
              { key: 'name', label: 'Name' },
              { key: 'types', label: 'Type', get: (r) => (r.types || []).join(', ') },
            ], services.slice(0, 300), { empty: 'No matching services' }),
          ),
        ),
      );
    }

    root.appendChild(card('ROS graph', {
      sub: 'What the robot is advertising right now',
      actions: [
        graphSearch,
        button('Refresh', () => refreshGraph(), { size: 'sm', style: 'ghost', iconName: 'refresh' }),
      ],
    }, graphHost));
    graphSearch.style.maxWidth = '240px';

    /* -- Wiring ----------------------------------------------------------- */

    paintLog();
    paintAgent();
    refreshGraph();

    const unsubscribe = store.on('event', () => paintLog());
    let lastAgent = 0;
    const unsubState = store.on('state', () => {
      const now = Date.now();
      if (now - lastAgent < 2000) return;
      lastAgent = now;
      paintAgent();
    });
    const timer = setInterval(refreshGraph, 15000);

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