import { readFile } from 'node:fs/promises'
import path from 'node:path'

export type ParsedTask = {
  title: string
  description: string
  sort_order: number
}

export type ParsedStory = {
  ref: string
  title: string
  acceptance_criteria: string
  status: 'DONE' | 'OPEN'
  sort_order: number
  tasks: ParsedTask[]
}

export type ParsedMilestone = {
  key: string
  title: string
  goal: string
  priority: 1 | 2 | 3 | 4
  sprint_status: 'OPEN' | 'CLOSED'
  sort_order: number
  stories: ParsedStory[]
}

const MILESTONE_HEADER = /^### (M[\d.]+|PBI-\d+):\s*(.+?)\s*$/
const TASK_BULLET = /^- \[(x| )\] \*\*(ST-\d+)\*\*\s+(.+?)\s*$/
const SUB_BULLET = /^ {2}- (.+?)\s*$/
const NESTED_LINE = /^ {4,}\S/
const SECTION_BREAK = /^---\s*$/
const BOLD_PREFIX = /^\*\*([^*]+?)\*\*\s*:?\s*(.*)$/

const MILESTONE_PRIORITY: Record<string, 1 | 2 | 3 | 4> = {
  M0: 1,
  M1: 1,
  M2: 1,
  M3: 1,
  'M3.5': 2,
  M4: 2,
  M5: 3,
  M6: 4,
  M7: 4,
  M8: 4,
  M9: 4,
  M10: 4,
  M11: 4,
}

const MILESTONE_GOAL: Record<string, string> = {
  M0: 'Project, database, auth, navigatieshell',
  M1: "Producten, PBI's, gesplitst scherm",
  M2: 'Stories als blokken, dnd-kit, Zustand',
  M3: 'Sprint aanmaken, stories slepen, taken',
  'M3.5': 'Story-claim, persoonlijk Kanban-bord per product',
  M4: 'Alle endpoints, tokenbeheer',
  M5: 'Todo CRUD, promotie naar PBI/story; Data Table + detail-kaart',
  M6: 'Foutafhandeling, toegankelijkheid, CI/CD, beveiliging',
  M7: 'MCP-server voor Claude Code',
  M8: 'Realtime updates voor Solo Paneel',
  M9: 'Actief Product Backlog — persistent gekozen product',
  M10: 'Password-loze inlog via QR-pairing',
  M11: 'Vraag-antwoord-kanaal Claude ↔ user',
}

const MILESTONE_SPRINT_STATUS: Record<string, ParsedMilestone['sprint_status']> = {
  M0: 'CLOSED',
  M1: 'CLOSED',
  M2: 'CLOSED',
  M3: 'CLOSED',
  'M3.5': 'CLOSED',
  M4: 'CLOSED',
  M5: 'CLOSED',
  M6: 'CLOSED',
  M7: 'CLOSED',
  M8: 'CLOSED',
  M9: 'CLOSED',
  M10: 'CLOSED',
  M11: 'CLOSED',
}

const MILESTONE_KEY = /^(?:M[\d.]+|PBI-\d+)$/

type SubBullet = {
  headLine: string
  nestedLines: string[]
  isAcceptance: boolean
}

function buildTask(b: SubBullet, sort_order: number): ParsedTask {
  const nested = b.nestedLines.join('\n')
  const m = b.headLine.match(BOLD_PREFIX)
  if (m) {
    const title = m[1].replace(/`/g, '').replace(/:$/, '').trim()
    const description = [m[2].trim(), nested].filter(Boolean).join('\n').trim()
    return { title, description, sort_order }
  }
  // No bold prefix: derive a short title from the first clause; full bullet stays in description.
  const head = b.headLine.replace(/`/g, '').trim()
  const firstClause = head.split(/[;.]\s/, 1)[0].trim()
  const title = firstClause.length > 80 ? firstClause.slice(0, 79).trim() + '…' : firstClause
  const description = [b.headLine, nested].filter(Boolean).join('\n').trim()
  return { title, description, sort_order }
}

function buildAcceptance(b: SubBullet): string {
  const head = b.headLine.replace(/^Done when:\s*/i, '').trim()
  const nested = b.nestedLines.join('\n').trim()
  return [head, nested].filter(Boolean).join('\n').trim()
}

export async function loadBacklog(
  repoRoot: string,
  options: { strict?: boolean } = {},
): Promise<ParsedMilestone[]> {
  const { strict = true } = options
  const file = path.join(repoRoot, 'docs/scrum4me-backlog.md')
  const md = await readFile(file, 'utf8')

  const milestones: ParsedMilestone[] = []
  let current: ParsedMilestone | null = null
  let pending: {
    story: ParsedStory
    bullets: SubBullet[]
    activeBullet: SubBullet | null
  } | null = null

  const flushPending = () => {
    if (!pending) return
    if (pending.activeBullet) {
      pending.bullets.push(pending.activeBullet)
      pending.activeBullet = null
    }
    const taskBullets = pending.bullets.filter((b) => !b.isAcceptance)
    const acceptanceBullets = pending.bullets.filter((b) => b.isAcceptance)
    pending.story.tasks = taskBullets.map((b, i) => buildTask(b, i + 1))
    pending.story.acceptance_criteria = acceptanceBullets
      .map(buildAcceptance)
      .filter(Boolean)
      .join('\n')
    pending = null
  }

  for (const raw of md.split('\n')) {
    const headerMatch = raw.match(MILESTONE_HEADER)
    if (headerMatch && MILESTONE_KEY.test(headerMatch[1])) {
      flushPending()
      const key = headerMatch[1]
      const title = headerMatch[2]
      current = {
        key,
        title,
        goal: MILESTONE_GOAL[key] ?? title,
        priority: MILESTONE_PRIORITY[key] ?? 4,
        sprint_status: MILESTONE_SPRINT_STATUS[key] ?? 'CLOSED',
        sort_order: milestones.length + 1,
        stories: [],
      }
      milestones.push(current)
      continue
    }

    if (SECTION_BREAK.test(raw)) {
      flushPending()
      continue
    }

    if (!current) continue

    const taskMatch = raw.match(TASK_BULLET)
    if (taskMatch) {
      flushPending()
      const story: ParsedStory = {
        ref: taskMatch[2],
        title: taskMatch[3],
        acceptance_criteria: '',
        status: taskMatch[1] === 'x' ? 'DONE' : 'OPEN',
        sort_order: current.stories.length + 1,
        tasks: [],
      }
      current.stories.push(story)
      pending = { story, bullets: [], activeBullet: null }
      continue
    }

    if (!pending) continue

    const subMatch = raw.match(SUB_BULLET)
    if (subMatch) {
      if (pending.activeBullet) {
        pending.bullets.push(pending.activeBullet)
      }
      const content = subMatch[1]
      pending.activeBullet = {
        headLine: content.replace(/^Done when:\s*/i, ''),
        nestedLines: [],
        isAcceptance: /^Done when:/i.test(content),
      }
      continue
    }

    if (NESTED_LINE.test(raw) && pending.activeBullet) {
      pending.activeBullet.nestedLines.push(raw.trim())
      continue
    }

    if (raw.trim() === '') continue

    flushPending()
  }

  flushPending()

  if (strict) {
    if (milestones.length < 8) {
      throw new Error(
        `Backlog parser found only ${milestones.length} milestones (expected 8). Format may have drifted in ${file}.`,
      )
    }
    const totalStories = milestones.reduce((acc, m) => acc + m.stories.length, 0)
    if (totalStories < 60) {
      throw new Error(
        `Backlog parser found only ${totalStories} stories (expected ≥ 60). Format may have drifted in ${file}.`,
      )
    }
  }

  return milestones
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..')
  loadBacklog(repoRoot)
    .then((milestones) => {
      const total = milestones.reduce((acc, m) => acc + m.stories.length, 0)
      const done = milestones.reduce(
        (acc, m) => acc + m.stories.filter((s) => s.status === 'DONE').length,
        0,
      )
      const open = total - done
      const totalTasks = milestones.reduce(
        (acc, m) => acc + m.stories.reduce((a, s) => a + s.tasks.length, 0),
        0,
      )
      console.log(
        `Parsed ${milestones.length} milestones, ${total} stories (${done} DONE, ${open} OPEN), ${totalTasks} tasks`,
      )
      for (const m of milestones) {
        const ms = m.stories.reduce((a, s) => a + s.tasks.length, 0)
        console.log(
          `  ${m.key.padEnd(5)} ${m.title.padEnd(36)} priority=${m.priority} sprint=${m.sprint_status} stories=${m.stories.length} tasks=${ms}`,
        )
      }
    })
    .catch((err) => {
      console.error(err)
      process.exit(1)
    })
}
