import {
  type DependencyEdge,
  forecast,
  type GiteaIssue,
  inferColumnV0,
  type LifecycleColumn,
  schedule,
  type ScheduledItem,
  selectFocus,
} from '@commitea/core'

import { type BoardColumn, type BoardIssue, type FocusIssue } from '../data/fixtures.js'
import { type BurnUpData, buildBurnUpData } from './dates.js'

const COLUMN_LABELS: Record<LifecycleColumn, string> = {
  diagnosis: 'Diagnosis',
  triage: 'Triage',
  steeping: 'Steeping',
  review: 'In review',
  done: 'Done',
}

const COLUMN_ORDER: LifecycleColumn[] = ['diagnosis', 'triage', 'steeping', 'review', 'done']

function initials(login: string): string {
  return login.slice(0, 2).toUpperCase()
}

/**
 * Shape real gitea issues into the Board's five columns via lifecycle-v0.
 * `steeping` / `review` stay empty until event inference (P1-5). `days` / `pr`
 * are likewise event-derived and omitted here.
 */
export function issuesToBoardColumns(issues: GiteaIssue[]): BoardColumn[] {
  return COLUMN_ORDER.map((key) => ({
    id: key,
    label: COLUMN_LABELS[key],
    issues: issues
      .filter((i) => inferColumnV0(i) === key)
      .map(
        (i): BoardIssue => ({
          id: i.number,
          title: i.title,
          labels: i.labels,
          who: i.assignee ? initials(i.assignee) : '·',
        }),
      ),
  }))
}

export interface FocusView {
  now: FocusIssue | null
  next: FocusIssue | null
  later: FocusIssue | null
}

function toFocusIssue(item: ScheduledItem | null): FocusIssue | null {
  if (!item) return null
  return { id: item.number, title: item.title, labels: item.labels, rationale: item.rationale }
}

function toSchedulable(issues: GiteaIssue[]) {
  return issues
    .filter((i) => i.state === 'open')
    .map((i) => ({
      number: i.number,
      title: i.title,
      labels: i.labels,
      estimateDays: i.facts.estimateDays,
      priority: i.facts.priority,
    }))
}

export interface ForecastView {
  scope: number
  cone: BurnUpData
  p80Label: string
  rangeLabel: string
}

/**
 * Monte Carlo forecast over the open backlog, mapped onto a calendar-anchored
 * burn-up cone. Returns null when there's nothing to forecast (no open scope) —
 * the UI then falls back to the demo cone. `today` is injectable for tests.
 */
export function forecastBacklog(
  issues: GiteaIssue[],
  deps: DependencyEdge[],
  today: Date = new Date(),
): ForecastView | null {
  const f = forecast(toSchedulable(issues), deps)
  const cone = buildBurnUpData(f, today)
  if (!cone) return null
  return { scope: f.scope, cone, p80Label: cone.p80Label, rangeLabel: cone.rangeLabel }
}

/**
 * Run the deterministic scheduler over the open backlog and take the top three
 * as Now/Next/Later. Estimates + priority come from label facts; dependency
 * edges come from gitea's native issue dependencies.
 */
export function scheduleFocus(issues: GiteaIssue[], deps: DependencyEdge[]): FocusView {
  const plan = schedule(toSchedulable(issues), deps)
  const f = selectFocus(plan)
  return { now: toFocusIssue(f.now), next: toFocusIssue(f.next), later: toFocusIssue(f.later) }
}
