/** Per-host counts classified either by the local recorder or on the owning host. */

import type { AgentSessionRow, BoxResidentSession, HostFacts, SessionsPanelData } from './session-types'
import { isRunning } from './session-types'

export type HostCoverage =
  | 'recorder' | 'probed' | 'probe-pending' | 'probe-failed' | 'not-reachable' | 'unregistered' | 'unstamped'

export interface HostCliCount { cli: string; count: number }

export interface HostBreakdownRow {
  host: string | null
  label: string
  coverage: HostCoverage
  facts: HostFacts | null
  probe: SessionsPanelData['hostProbes'][number] | null
  live: number
  idle: number
  reconnectable: number
  runningByCli: HostCliCount[]
  ledgerEntries: number
  /** Sessions living in a persistent box container — no ledger entry, so not part of the
   * live/idle/reconnectable counts above. */
  boxSessions: BoxResidentSession[]
}

export const COVERAGE_NOTE: Record<HostCoverage, string> = {
  recorder: 'The classifier ran on the collector host, so these are direct process observations.',
  probed: 'The classifier ran on this host over SSH. The probe timestamp states how fresh these counts are.',
  'probe-failed': 'The classifier could not run on this host. The probe error is shown verbatim.',
  'probe-pending': 'The first probe of this host has not returned yet, so these counts come from the local ledger alone.',
  'not-reachable': 'The buildbox registry forbids contacting this host in its current state.',
  unregistered: 'The ledger names this host but the buildbox registry does not list it, so it is never contacted and only its enrolled sessions are counted.',
  unstamped: 'These sessions are in the ledger but carry no host, so they cannot be attributed to a machine.',
}

export const COVERAGE_LABEL: Record<HostCoverage, string> = {
  recorder: 'recorder host',
  probed: 'remote probe',
  'probe-pending': 'probe pending',
  'probe-failed': 'probe failed',
  'not-reachable': 'not reachable',
  unregistered: 'not in registry',
  unstamped: 'host not recorded',
}

export const COVERAGE_CHIP_STATUS: Record<HostCoverage, string> = {
  recorder: 'info',
  probed: 'info',
  'probe-pending': 'info',
  'probe-failed': 'warn',
  'not-reachable': 'warn',
  unregistered: 'warn',
  unstamped: 'warn',
}

function countsFor(rows: AgentSessionRow[]) {
  return {
    live: rows.filter((row) => row.state === 'live').length,
    idle: rows.filter((row) => row.state === 'idle').length,
    reconnectable: rows.filter((row) => row.attach.kind !== 'none').length,
  }
}

function runningByCli(rows: AgentSessionRow[]): HostCliCount[] {
  const counts = new Map<string, number>()
  for (const row of rows) {
    if (!isRunning(row.state)) continue
    counts.set(row.cli, (counts.get(row.cli) ?? 0) + 1)
  }
  return [...counts.entries()].map(([cli, count]) => ({ cli, count }))
    .sort((left, right) => right.count - left.count || left.cli.localeCompare(right.cli))
}

export function hostBreakdown(
  rows: AgentSessionRow[],
  panel: Pick<SessionsPanelData, 'hosts' | 'localHost' | 'hostProbes' | 'boxResident'>,
): HostBreakdownRow[] {
  const byHost = new Map<string | null, AgentSessionRow[]>()
  for (const row of rows) {
    const bucket = byHost.get(row.host)
    if (bucket) bucket.push(row)
    else byHost.set(row.host, [row])
  }

  const names: string[] = []
  const seen = new Set<string>()
  const push = (name: string): void => { if (!seen.has(name)) { seen.add(name); names.push(name) } }
  push(panel.localHost)
  for (const host of panel.hosts) push(host.name)
  for (const host of byHost.keys()) if (host !== null) push(host)

  const built = names.map((name): HostBreakdownRow => {
    const hostRows = byHost.get(name) ?? []
    const counts = countsFor(hostRows)
    const probe = panel.hostProbes.find((entry) => entry.host === name) ?? null
    const coverage: HostCoverage = name === panel.localHost
      ? 'recorder'
      : probe === null
        ? (panel.hosts.some((host) => host.name === name) ? 'probe-pending' : 'unregistered')
        : probe.status === 'ok'
          ? 'probed'
          : probe.status === 'pending'
            ? 'probe-pending'
            : probe.status === 'not-reachable'
              ? 'not-reachable'
              : 'probe-failed'
    return {
      host: name,
      label: name,
      coverage,
      facts: panel.hosts.find((host) => host.name === name) ?? null,
      probe,
      ...counts,
      runningByCli: runningByCli(hostRows),
      ledgerEntries: hostRows.length,
      boxSessions: panel.boxResident.filter((session) => session.host === name),
    }
  })

  const unstamped = byHost.get(null) ?? []
  if (unstamped.length > 0) {
    built.push({
      host: null,
      label: 'host not recorded',
      coverage: 'unstamped',
      facts: null,
      probe: null,
      ...countsFor(unstamped),
      runningByCli: runningByCli(unstamped),
      ledgerEntries: unstamped.length,
      boxSessions: [],
    })
  }
  return built
}
