// Coverage: the coverage surface. For each cluster, one block. For each
// stream, a strip chart of event cadence over time. Solid = activity,
// hatched = gap, faint = idle, void = outside the observed window.
import { api } from '../api.js';
import { el, asList, stripRow, fmtTime, emptyState } from '../util.js';

export async function viewCoverage(mount) {
  mount.appendChild(el('h1', {}, [
    el('span', { className: 'eyebrow', textContent: 'coverage' }),
    el('span', { textContent: 'Stream coverage over time' }),
  ]));
  mount.appendChild(el('hr', { className: 'rule' }));

  const [c, s] = await Promise.all([api.clusters().catch(() => []), api.streams().catch(() => [])]);
  const clusters = asList(c);
  const streams = asList(s);

  if (!clusters.length && !streams.length) {
    mount.appendChild(emptyState(
      'No clusters or streams recorded yet.',
      'Start a recording with: krply record --namespace shop --resource deployments --resource configmaps --store krply.db',
    ));
    return;
  }

  const events = asList(await api.events({ limit: 2000 }).catch(() => []));

  const byCluster = new Map();
  for (const st of streams) {
    const list = byCluster.get(st.cluster_id) || [];
    list.push(st);
    byCluster.set(st.cluster_id, list);
  }
  for (const cl of clusters) if (!byCluster.has(cl.id)) byCluster.set(cl.id, []);
  const clusterIds = clusters.length ? clusters.map((cl) => cl.id) : [...byCluster.keys()];

  for (const id of clusterIds) {
    const sts = byCluster.get(id) || [];
    const cl = clusters.find((x) => x.id === id) || { id };
    const block = el('section', { className: 'cluster-block' });

    const span = observedSpan(events, id);
    const head = el('header', { className: 'cluster-head' }, [
      el('span', { className: 'cname', textContent: cl.context || cl.id }),
      el('span', { className: 'cmeta' }, [
        `${sts.length} stream${sts.length === 1 ? '' : 's'}`,
        sts.length ? ` · first ${fmtTime(earliestFirst(sts))}` : '',
        sts.length ? ` · last ${fmtTime(latestLast(sts))}` : '',
      ]),
    ]);
    block.appendChild(head);

    if (!sts.length) {
      block.appendChild(el('div', { className: 'strip-rows' }, [
        el('div', { className: 'strip-row', style: 'padding:14px 16px' }, [
          el('span', { className: 'muted', textContent: 'no streams known for this cluster yet' }),
        ]),
      ]));
      mount.appendChild(block);
      continue;
    }

    const rows = el('div', { className: 'strip-rows' });
    for (const st of sts) rows.appendChild(stripRow(st, span, events.filter((e) => e.stream_id === st.id)));
    block.appendChild(rows);
    block.appendChild(stripLegend());
    mount.appendChild(block);
  }

}

function stripLegend() {
  const legend = el('div', { className: 'strip-legend' });
  const item = (cls, label) =>
    el('span', {}, [el('i', { className: `sq ${cls}` }), el('span', { textContent: label })]);
  legend.appendChild(item('ok', 'events'));
  legend.appendChild(item('degraded', 'gap'));
  legend.appendChild(item('idle', 'idle'));
  legend.appendChild(item('void', 'outside window'));
  return legend;
}

function observedSpan(events, clusterId) {
  let min = null;
  let max = null;
  for (const ev of events) {
    if (ev.cluster_id && ev.cluster_id !== clusterId) continue;
    const t = ev.observed_at ? new Date(ev.observed_at).getTime() : NaN;
    if (Number.isNaN(t)) continue;
    if (min == null || t < min) min = t;
    if (max == null || t > max) max = t;
  }
  const now = Date.now();
  return { start: new Date(min != null ? min : now), end: new Date(max != null ? max : now) };
}

function earliestFirst(sts) {
  let min = null;
  for (const st of sts) {
    const t = st.first_observed_at ? new Date(st.first_observed_at).getTime() : NaN;
    if (!Number.isNaN(t) && (min == null || t < min)) min = t;
  }
  return min != null ? new Date(min).toISOString() : '';
}

function latestLast(sts) {
  let max = null;
  for (const st of sts) {
    const t = st.last_observed_at ? new Date(st.last_observed_at).getTime() : NaN;
    if (!Number.isNaN(t) && (max == null || t > max)) max = t;
  }
  return max != null ? new Date(max).toISOString() : '';
}
