import { planStatusCategory, type CriterionRowProps, type InboxItemDot, type InboxItemProps, type KvIntent, type KvRow, type ScoreCardProps } from '@overdeck/deck-ui'
import type { Item, Panel, Severity } from './collector-types'
import type {
  AccountLimitView,
  Criterion,
  GhciPanelData,
  HarnessPlanRun,
  HarnessPlansPanelData,
  PrometheusHostPanelData,
  RepoScore,
  Verdict,
} from './panel-data'

/** `.score`/`.pbar`/`.fine` fields — RepoScore has no free-text detail, so trend and
 * fine-print are both derived from its real numeric fields only. */
export function scoreCardPropsFor(score: RepoScore): ScoreCardProps {
  const trend: ScoreCardProps['trend'] =
    score.trend7d > 0
      ? { direction: 'up', label: `+${score.trend7d} wk` }
      : score.churn
        ? { direction: 'flat', label: `flat · ${score.commitsSinceLastWorksDelta} commits — churn` }
        : { direction: 'flat', label: 'flat' }

  const detail = score.churn
    ? `${score.commitsSinceLastWorksDelta} commits since last works delta — churn`
    : `${score.commitsSinceLastWorksDelta} commits since last works delta`

  return { value: score.works, max: score.total, trend, detail }
}

export function criterionRowPropsFor(c: Criterion): CriterionRowProps {
  return { id: c.id, text: c.text, verdict: c.verdict, evidence: c.evidence, reachedAt: c.reachedAt }
}

const VERDICT_ORDER: Verdict[] = ['works', 'broken', 'missing', 'unverified']

export function verdictChipsFor(score: RepoScore): { verdict: Verdict; count: number }[] {
  return VERDICT_ORDER.map((verdict) => ({ verdict, count: score.verdicts[verdict] }))
}

const SEVERITY_RANK: Record<Severity, number> = { act: 0, warn: 1, info: 2 }
const DOT_FOR_SEVERITY: Record<Severity, InboxItemDot> = { act: 'red', warn: 'amber', info: 'blue' }

/** Severity (act > warn > info), then most-recent ts first — the "needs you now" order. */
export function sortItemsBySeverity(items: Item[]): Item[] {
  return [...items]
    .sort((a, b) => {
      const rankDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]
      return rankDiff !== 0 ? rankDiff : Date.parse(b.ts) - Date.parse(a.ts)
    })
}

export function topNeedsYouNow(items: Item[], limit = 4): Item[] {
  return sortItemsBySeverity(items).slice(0, limit)
}

export function inboxItemPropsFor(item: Item, isLast: boolean): InboxItemProps {
  return {
    dot: DOT_FOR_SEVERITY[item.severity],
    title: item.title,
    subtitle: item.detail,
    // No collector POST action route exists yet (server.ts only serves GET) — actions
    // render as labels with no onClick rather than wiring a nonexistent endpoint.
    actions: item.actions.map((action) => ({ label: action.label, primary: action.recommended })),
    isLast,
  }
}

export function spendTodayTotal(accounts: AccountLimitView[]): number | null {
  const amounts = accounts
    .map((account) => account.spend?.amount)
    .filter((amount): amount is number => typeof amount === 'number')
  if (amounts.length === 0) return null
  return amounts.reduce((sum, amount) => sum + amount, 0)
}

function runnerIntent(runner: { busy: boolean; status: string }): KvIntent {
  if (runner.busy) return 'warn'
  if (runner.status === 'offline') return 'err'
  if (runner.status === 'online') return 'ok'
  return 'neutral'
}

/** One KV row per repo's latest completed run, plus one per runner, plus a queue row. */
export function ciKvRows(data: GhciPanelData): KvRow[] {
  const rows: KvRow[] = []
  for (const repo of data.repos) {
    const latest = repo.historyComplete ? repo.runs[0] : undefined
    if (!repo.historyComplete) {
      rows.push({ label: `${repo.repo} runs`, value: 'run history incomplete' })
    } else if (latest) {
      const failed = latest.conclusion === 'failure'
      const succeeded = latest.conclusion === 'success'
      rows.push({
        label: `${repo.repo} #${latest.id}`,
        value: failed ? `✗ ${latest.name}` : succeeded ? `✓ ${latest.name}` : latest.status,
        intent: failed ? 'err' : succeeded ? 'ok' : 'neutral',
      })
    }
    for (const runner of repo.runners) {
      rows.push({
        label: runner.name,
        value: runner.busy ? 'busy' : runner.status,
        intent: runnerIntent(runner),
      })
    }
    rows.push({
      label: `${repo.repo} queue`,
      value: repo.queueComplete && repo.queueDepth !== null
        ? `${repo.queueDepth} waiting`
        : 'queue incomplete',
    })
  }
  return rows
}

const HALT_STATUSES = new Set(['failed', 'degraded'])
const TERMINAL_PLAN_STATUSES = new Set(['failed', 'degraded', 'killed', 'succeeded', 'completed', 'done'])

function planActivityMs(run: HarnessPlanRun): number {
  if (!run.updatedAt) return -Infinity
  const parsed = Date.parse(run.updatedAt)
  return Number.isFinite(parsed) ? parsed : -Infinity
}

/** Harness can leave status=running after every task finished — treat those as inactive. */
export function isActivePlanRun(run: HarnessPlanRun): boolean {
  if (run.pendingDecisions > 0) return true
  if (TERMINAL_PLAN_STATUSES.has(run.status)) return false
  const category = planStatusCategory(run.status)
  if (category === 'failed' || category === 'completed') return false
  if (run.tasksTotal > 0 && run.tasksCompleted >= run.tasksTotal) return false
  return category === 'running' || category === 'queued' || category === 'needs-you'
}

/** Active/in-progress plans first, then most-recent activity — the overview Plans panel order. */
export function sortPlanRunsForOverview(runs: HarnessPlanRun[]): HarnessPlanRun[] {
  return [...runs].sort((left, right) => {
    const leftActive = isActivePlanRun(left) ? 1 : 0
    const rightActive = isActivePlanRun(right) ? 1 : 0
    if (leftActive !== rightActive) return rightActive - leftActive
    return planActivityMs(right) - planActivityMs(left)
  })
}

/** One KV row per harness run — wave progress (from the run's own DAG), or a HALT verdict. */
export function planKvRows(data: HarnessPlansPanelData): KvRow[] {
  return sortPlanRunsForOverview(data.runs).map((run) => {
    if (HALT_STATUSES.has(run.status)) {
      return { label: run.title, value: `HALT · ${run.status}`, intent: 'warn' }
    }
    const wave = run.currentTask
      ? run.waves.find((w) => w.tasks.some((task) => task.id === run.currentTask))
      : undefined
    const value = wave ? `wave ${wave.wave}/${run.waves.length} ▶` : `${run.tasksCompleted}/${run.tasksTotal} tasks`
    return { label: run.title, value, intent: 'info' }
  })
}

// Mirrors prometheus.ts's own MEM_RUNWAY_ALERT_SECONDS (30 * 60) — not a new threshold.
const MEM_RUNWAY_ALERT_SECONDS = 30 * 60

/**
 * Mockup's "prevent-band" and "slopgate debt" rows have no corresponding field on
 * PrometheusHostPanelData (or any other adapter) and are intentionally dropped —
 * only PSI cpu some / mem runway / PROCHOT are backed by real data.
 */
export function hostGatesKvRows(data: PrometheusHostPanelData): KvRow[] {
  return [
    {
      label: 'PSI cpu some',
      value: data.cpuPsiSomePercent === null ? '—' : `${data.cpuPsiSomePercent.toFixed(1)}%`,
      intent: data.cpuPsiSomePercent !== null && data.cpuPsiSomePercent > 20 ? 'warn' : 'ok',
    },
    {
      label: 'mem runway',
      value: data.memRunwayEtaSeconds === null ? '—' : `> ${(data.memRunwayEtaSeconds / 3600).toFixed(1)}h`,
      intent: data.memRunwayEtaSeconds !== null && data.memRunwayEtaSeconds < MEM_RUNWAY_ALERT_SECONDS ? 'err' : 'ok',
    },
    {
      label: 'PROCHOT',
      value: data.prochot ? '1' : '0',
      intent: data.prochot ? 'err' : 'ok',
    },
  ]
}

export function pickForensicsPanel(panels: Panel[]): Panel | undefined {
  return [...panels].filter((p) => p.id.startsWith('forensics:')).sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts))[0]
}

export function runIdFromForensicsPanelId(panelId: string): string {
  return panelId.slice('forensics:'.length)
}

export function forensicsTitleFor(runId: string, plansPanel: Panel | undefined): string {
  const data = plansPanel?.data as HarnessPlansPanelData | undefined
  return data?.runs.find((run) => run.runId === runId)?.title ?? runId
}
