import {
  type DependencyEdge,
  forecast,
  type GiteaIssue,
  inferLifecycle,
  type LifecycleColumn,
  type LifecycleEvent,
  type LifecycleInference,
  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'

type Timelines = Record<number, LifecycleEvent[]>

/** Infer every issue's lifecycle once; callers index by issue number. */
function inferAll(issues: GiteaIssue[], timelines: Timelines, asOf: Date): Map<number, LifecycleInference> {
  return new Map(issues.map((i) => [i.number, inferLifecycle(i, timelines[i.number] ?? [], asOf)]))
}

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 inference
 * (#5). Steeping / In-review are now populated from the event stream (first
 * commit ref → steeping, first PR ref → review); the `days` badge is the
 * steeping age in working days.
 */
export function issuesToBoardColumns(
  issues: GiteaIssue[],
  timelines: Timelines = {},
  asOf: Date = new Date(),
): BoardColumn[] {
  const inf = inferAll(issues, timelines, asOf)
  return COLUMN_ORDER.map((key) => ({
    id: key,
    label: COLUMN_LABELS[key],
    issues: issues
      .filter((i) => inf.get(i.number)!.column === key)
      .map((i): BoardIssue => {
        const li = inf.get(i.number)!
        return {
          id: i.number,
          title: i.title,
          labels: i.labels,
          who: i.assignee ? initials(i.assignee) : '·',
          days: li.steepingDays != null ? `${li.steepingDays}d` : undefined,
        }
      }),
  }))
}

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

function toFocusIssue(item: ScheduledItem | null, inf?: Map<number, LifecycleInference>): FocusIssue | null {
  if (!item) return null
  const steepingDays = inf?.get(item.number)?.steepingDays ?? null
  return {
    id: item.number,
    title: item.title,
    labels: item.labels,
    rationale: item.rationale,
    steeping: steepingDays != null ? `${steepingDays}d` : undefined,
  }
}

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[],
  timelines: Timelines = {},
  asOf: Date = new Date(),
): FocusView {
  const plan = schedule(toSchedulable(issues), deps)
  const f = selectFocus(plan)
  const inf = inferAll(issues, timelines, asOf)
  return {
    now: toFocusIssue(f.now, inf),
    next: toFocusIssue(f.next, inf),
    later: toFocusIssue(f.later, inf),
  }
}
