import type {
  AmplificationSparkProps,
  ClusterQueueModel,
  FleetHost,
  OffloadAction,
  OffloadControlModel,
  PrQueueItem,
  PrQueueRepoCompleteness,
  RemoteJob,
  RepoCiSummary,
  RunnerFleetStripProps,
  TrainRepoCompleteness,
  TrainViewModel,
} from '@overdeck/deck-ui'
import type { ActionRef, Item, Panel } from '../../lib/collector-types'
import type { GhciPanelData, GhciRepoPanel } from '../../lib/panel-data'

/** Mirror of collector offload adapter `Panel{id:"offload-control"}.data`. */
export interface OffloadControlPanelData {
  stale?: boolean
  desired: OffloadControlModel['desired']
  observed: OffloadControlModel['observed']
  revision: number
  lease: OffloadControlModel['lease']
  reconciler: OffloadControlModel['reconciler']
  dispatch: OffloadControlModel['dispatch']
  spill?: OffloadControlModel['spill']
  capabilityByHost?: Record<string, { probes: Array<{ name: string; ok: boolean; version?: string }> }>
}

/** Mirror of collector offload adapter `Panel{id:"cluster-queue"}.data`. */
export interface OffloadKpiSamples {
  remote24h?: number
  remoteSuccessPct?: number
  exit127?: { count: number; hosts: string[] }
  epochDiscarded?: number
  longestPullMs?: number
}

export type ClusterQueuePanelData = ClusterQueueModel & {
  stale?: boolean
  kpis?: OffloadKpiSamples
}

export type ClusterQueueWithKpis = ClusterQueueModel & { kpis?: OffloadKpiSamples }

/** Mirror of collector offload adapter `Panel{id:"remote-jobs"}.data`. */
export interface RemoteJobsPanelData {
  stale?: boolean
  jobs: RemoteJob[]
}

/** Mirror of collector offload adapter `Panel{id:"fleet"}.data`. */
export interface FleetPanelData {
  stale?: boolean
  hosts: FleetHost[]
  registryError?: string | null
}

export const X1_OFFLOAD_VERBS = new Set([
  'box-drain',
  'box-restore',
  'host-quarantine',
  'host-unquarantine',
  'admission-reconcile',
  'job-retry',
  'ci-reconcile',
  'recall-spill',
])

function panelData<T>(panels: Panel[], id: string): T | undefined {
  return panels.find((panel) => panel.id === id)?.data as T | undefined
}

export function offloadControlFromPanel(panels: Panel[]): OffloadControlModel | undefined {
  const data = panelData<OffloadControlPanelData>(panels, 'offload-control')
  if (!data || data.stale) return undefined

  // Probes from every registry host, each labelled with the host it was taken on: the same
  // probe name means different things on different boxes, so an unattributed merged list
  // would show a failure without saying where it is.
  const capabilityProbes = Object.entries(data.capabilityByHost ?? {}).flatMap(
    ([host, { probes }]) => probes.map((probe) => ({
      name: `${host} · ${probe.version ? `${probe.name} ${probe.version}` : probe.name}`,
      ok: probe.ok,
    })),
  )

  return {
    desired: data.desired,
    observed: data.observed,
    revision: data.revision,
    lease: data.lease,
    reconciler: data.reconciler,
    dispatch: data.dispatch,
    spill: data.spill,
    capabilityProbes,
    actions: [
      { label: 'Drain box', verb: 'box-drain', danger: true },
      { label: 'Restore box', verb: 'box-restore' },
      { label: 'Quarantine host', verb: 'host-quarantine' },
    ],
  }
}

export function clusterQueueFromPanel(panels: Panel[]): ClusterQueueWithKpis | undefined {
  const data = panelData<ClusterQueuePanelData>(panels, 'cluster-queue')
  if (!data || data.stale) return undefined
  const { stale: _stale, ...queue } = data
  return queue
}

/**
 * Undefined when the panel is absent or stale. A stale panel carries no job list, and
 * rendering an empty table for it would state "no remote jobs" where the truth is "unknown".
 */
export function remoteJobsFromPanel(panels: Panel[]): RemoteJob[] | undefined {
  const data = panelData<RemoteJobsPanelData>(panels, 'remote-jobs')
  if (!data || data.stale) return undefined
  return data.jobs ?? []
}

export function fleetFromPanel(panels: Panel[]): FleetHost[] {
  const data = panelData<FleetPanelData>(panels, 'fleet')
  return data?.hosts ?? []
}

/**
 * Non-null when the host registry could not be read. An unreadable registry is not an
 * empty fleet: the page must say so rather than present whatever subset it still has.
 */
export function fleetRegistryError(panels: Panel[]): string | null {
  const data = panelData<FleetPanelData>(panels, 'fleet')
  return data?.registryError ?? null
}

export function buildItems(items: Item[]): Item[] {
  return items.filter((item) => item.kind === 'build')
}

export function stalledQueueItem(items: Item[]): Item | undefined {
  return buildItems(items).find((item) => item.id.includes('remote-idle-queue-stalled'))
}

export interface OffloadKpiTile {
  key: string
  label: string
  value: number | null
  sub?: string
}

export function offloadKpiTiles(queue?: ClusterQueueWithKpis, _items: Item[] = []): OffloadKpiTile[] {
  const samples = queue?.kpis
  const slo = queue
    ? queue.sloSeconds % 60 === 0
      ? `${queue.sloSeconds / 60}m`
      : `${queue.sloSeconds}s`
    : undefined
  return [
    { key: 'remote24h', label: 'builds routed remote 24h', value: samples?.remote24h ?? null },
    { key: 'remoteSuccessPct', label: 'remote success', value: samples?.remoteSuccessPct ?? null },
    {
      key: 'oldestQueuedMs',
      label: 'oldest queued',
      value: queue ? queue.oldestAgeSeconds * 1000 : null,
      sub: queue
        ? `SLO ${slo}${queue.oldestAgeSeconds > queue.sloSeconds ? ' breached' : ''} · p95 ${queue.p95AgeSeconds}s`
        : undefined,
    },
    {
      key: 'exit127',
      label: 'exit 127 last 24h',
      value: samples?.exit127?.count ?? null,
      sub: samples?.exit127
        ? `capability drift · ${samples.exit127.hosts.length === 1 ? 'host' : 'hosts'} ${samples.exit127.hosts.join(', ') || 'none'}`
        : undefined,
    },
    { key: 'epochDiscarded', label: 'epoch-discarded jobs', value: samples?.epochDiscarded ?? null },
    { key: 'longestPullMs', label: 'longest pull-back', value: samples?.longestPullMs ?? null },
  ]
}

export function machineCardMeta(
  machine: FleetHost,
  _queue?: ClusterQueueModel,
  control?: OffloadControlModel,
): { statusPill?: string; statusPillIntent?: 'ok' | 'warn' | 'neutral'; warn?: boolean; note?: string; actions: OffloadAction[] } {
  if (machine.registryState !== 'reachable') {
    return {
      statusPill: `declared ${machine.registryState}`,
      statusPillIntent: 'warn',
      warn: true,
      note: `The buildbox registry marks this host ${machine.registryState}; no host actions or log requests are available.`,
      actions: [],
    }
  }

  if (machine.enrolled === false) {
    return {
      statusPill: 'not enrolled',
      statusPillIntent: 'warn',
      warn: true,
      note: `declared in the buildbox registry as ${machine.registryState} · the build controller has not enrolled it, so it reports nothing`,
      actions: [
        { label: 'Enroll', verb: 'box-restore', primary: true },
        { label: 'Probe', verb: 'admission-reconcile' },
      ],
    }
  }

  if (machine.enrolling) {
    return {
      statusPill: 'enrolling',
      actions: [
        { label: 'Enroll', verb: 'box-restore', primary: true },
        { label: 'Probe', verb: 'admission-reconcile' },
      ],
    }
  }

  if (machine.role === 'workstation') {
    const spillStale = control?.spill?.active && control.spill.stale
    return {
      statusPill: spillStale ? 'spill on · stale' : control?.spill?.active ? 'spill on' : undefined,
      statusPillIntent: control?.spill?.active ? 'warn' : 'neutral',
      warn: Boolean(control?.spill?.active),
      note: spillStale ? 'spill on (stale) · should be idle' : undefined,
      actions: [
        { label: 'Recall spill', verb: 'recall-spill', primary: true },
        { label: 'Details', verb: 'admission-reconcile' },
      ],
    }
  }

  const wedged = control?.dispatch.state === 'wedged' && control.dispatch.host === machine.host
  return {
    statusPill: wedged ? 'dispatch wedged' : machine.dispatchAccept ? 'dispatch ok' : undefined,
    statusPillIntent: wedged || machine.dispatchAccept === false ? 'warn' : 'ok',
    warn: wedged || machine.dispatchAccept === false,
    note: wedged ? `dispatch wedged · ${control?.dispatch.detail ?? ''}` : undefined,
    actions: [
      { label: 'Reconcile', verb: 'admission-reconcile', primary: true },
      { label: 'Drain', verb: 'box-drain', danger: true },
      { label: 'Details', verb: 'admission-reconcile' },
    ],
  }
}

export function machineModalActions(machine: FleetHost): OffloadAction[] {
  if (machine.registryState !== 'reachable') return []
  if (machine.enrolling) {
    return [
      { label: 'Enroll', verb: 'box-restore', primary: true },
      { label: 'Probe', verb: 'admission-reconcile' },
    ]
  }
  return [
    { label: 'Reconcile dispatch', verb: 'admission-reconcile', primary: true },
    { label: 'Drain', verb: 'box-drain', danger: true },
    { label: 'Quarantine', verb: 'host-quarantine' },
    { label: 'Logs', verb: 'host-logs' },
  ]
}

export function hostChipSummary(hosts: FleetHost[], control?: OffloadControlModel): string[] {
  const chips: string[] = []
  for (const host of hosts) {
    if (host.enrolling || host.load === null) continue
    const loadClass = host.load >= (host.cores ?? 8) ? 'warn' : 'ok'
    chips.push(`${host.host} ● load ${host.load.toFixed(1)} (${loadClass})`)
  }
  if (control) {
    const lease = control.lease.active ? 'active' : 'none'
    chips.push(`fallback-lease ${lease}`)
  }
  return chips
}

export function incidentAlertActions(item: Item): OffloadAction[] {
  return item.actions.map((action) => ({
    label: action.label,
    verb: action.verb,
    primary: action.recommended,
  }))
}

export function inboxActionsFromItem(item: Item): Array<{ label: string; primary?: boolean; verb: string }> {
  return item.actions.map((action) => ({
    label: action.label,
    primary: action.recommended,
    verb: action.verb,
  }))
}

export function offloadRevision(panels: Panel[]): number {
  return panelData<OffloadControlPanelData>(panels, 'offload-control')?.revision ?? 0
}

export function panelById<T>(panels: Panel[], id: string): T | undefined {
  return panelData<T>(panels, id)
}

export function actionRefToOffload(action: ActionRef): OffloadAction {
  return { label: action.label, verb: action.verb, primary: action.recommended }
}

export function repoCiSummaries(data: GhciPanelData): RepoCiSummary[] {
  return data.repos.map((repo) => ({
    repo: repo.repo,
    queueComplete: repo.queueComplete,
    historyComplete: repo.historyComplete,
    trainRunsComplete: repo.trainRunsComplete,
    runnersComplete: repo.runnersComplete,
    refsComplete: repo.refsComplete,
    prsComplete: repo.prsComplete,
    trainsComplete: repo.trainsComplete,
    prQueueDepth: repo.prQueueDepth,
    queueDepth: repo.queueDepth,
    runnersBusy: repo.runnersBusy,
    runnersTotal: repo.runnersTotal,
    landsToday: repo.lag.landsToday,
    oldestQueuedAgeH: repo.oldestQueuedAgeH,
    degraded: repo.degraded,
    pollDeferred: repo.pollDeferred,
  }))
}

export function trainModels(repos: GhciRepoPanel[]): TrainViewModel[] {
  return repos.flatMap((repo) => repo.trains.map((train) => ({
    repo: repo.repo,
    prNumber: train.prNumber,
    prUrl: train.prUrl,
    branch: train.branch,
    createdAt: train.createdAt,
    members: train.members,
    state: train.state,
    stateGap: train.stateGap,
    gateRun: train.gateRun === null ? null : {
      id: train.gateRun.id,
      attempt: train.gateRun.attempt,
      status: train.gateRun.status,
      conclusion: train.gateRun.conclusion,
      jobsComplete: train.gateRun.jobsComplete,
      jobs: train.gateRun.jobs.map((job) => ({
        id: job.id,
        name: job.name,
        status: job.status,
        conclusion: job.conclusion,
        runnerId: job.runnerId,
        runner: job.runner,
        startedAt: job.startedAt,
        completedAt: job.completedAt,
        infraFlag: job.infraFlag,
      })),
    },
    blockedBy: train.blockedBy,
    infraFlag: train.infraFlag,
    refsComplete: repo.refsComplete,
    prsComplete: repo.prsComplete,
    trainRunsComplete: repo.trainRunsComplete,
    trainsComplete: repo.trainsComplete,
  })))
}

export function trainRepoCompleteness(repos: GhciRepoPanel[]): TrainRepoCompleteness[] {
  return repos.map((repo) => ({
    repo: repo.repo,
    queueComplete: repo.queueComplete,
    historyComplete: repo.historyComplete,
    trainRunsComplete: repo.trainRunsComplete,
    runnersComplete: repo.runnersComplete,
    refsComplete: repo.refsComplete,
    prsComplete: repo.prsComplete,
    trainsComplete: repo.trainsComplete,
    pollDeferred: repo.pollDeferred,
    degraded: repo.degraded,
  }))
}

export function prQueueItems(repos: GhciRepoPanel[]): PrQueueItem[] {
  return repos.flatMap((repo) => repo.prs.map((pr) => ({
    repo: repo.repo,
    number: pr.number,
    title: pr.title,
    url: pr.url,
    branch: pr.branch,
    headRefOid: pr.headRefOid,
    createdAt: pr.createdAt,
    ageH: pr.ageH,
    mergeable: pr.mergeable,
    staleVsMain: pr.staleVsMain,
    checksComplete: pr.checksComplete,
    checks: pr.checks.map((check) => ({ name: check.name, bucket: check.bucket })),
    trainPr: pr.trainPr,
  })))
}

export function prQueueRepoCompleteness(repos: GhciRepoPanel[]): PrQueueRepoCompleteness[] {
  return repos.map((repo) => ({
    repo: repo.repo,
    prsComplete: repo.prsComplete,
    pollDeferred: repo.pollDeferred,
    degraded: repo.degraded,
  }))
}

export function runnerFleetProps(repo: GhciRepoPanel): RunnerFleetStripProps {
  return {
    runners: repo.runners.map((runner) => ({
      id: runner.id,
      name: runner.name,
      status: runner.status,
      busy: runner.busy,
      scope: runner.scope ?? repo.repo,
      currentJob: runner.currentJob,
      currentRepo: runner.currentRepo,
      currentKnown: runner.currentKnown,
    })),
    runnersComplete: repo.runnersComplete,
    oldestQueuedAgeH: repo.oldestQueuedAgeH,
  }
}

export function runnerFleetFromPanel(data: GhciPanelData | undefined): RunnerFleetStripProps {
  if (!data) {
    return { runners: [], runnersComplete: false, oldestQueuedAgeH: null }
  }
  return {
    runners: (data.runners ?? []).map((runner) => ({
      id: runner.id,
      name: runner.name,
      status: runner.status,
      busy: runner.busy,
      scope: runner.scope ?? null,
      currentJob: runner.currentJob,
      currentRepo: runner.currentRepo,
      currentKnown: runner.currentKnown,
    })),
    runnersComplete: data.runnersComplete ?? false,
    oldestQueuedAgeH: data.oldestQueuedAgeH ?? null,
  }
}

export function amplificationProps(repo: GhciRepoPanel): AmplificationSparkProps {
  return {
    repo: repo.repo,
    gates: repo.lag.gates.map((gate) => ({ ...gate })),
    historyComplete: repo.historyComplete,
    warmedAt: repo.lag.warmedAt,
  }
}
