import { readdirSync } from 'node:fs'
import { splitFrontmatter } from './frontmatter.js'
import { maskCode } from './slides.js'

export function parseBuildDirective(text) {
  const m = text.match(
    /^\s*build\s+(?<slug>[^\s;]+)(?:\s+(?<range>[^\s;]+))?\s*(?:;\s*(?<alt>.+?))?\s*$/
  )
  if (!m) throw new Error(`invalid build directive: ${text.trim()}`)
  return {
    slug: m.groups.slug,
    rangeStr: m.groups.range ?? null,
    alt: m.groups.alt ?? null,
  }
}

export function parseRange(rangeStr, count) {
  if (rangeStr === null || rangeStr === '..') {
    return Array.from({ length: count }, (_, i) => i + 1)
  }
  const rangeMatch = rangeStr.match(/^(-?\d+)?\.\.(-?\d+)?$/)
  if (rangeMatch) {
    const start = rangeMatch[1] !== undefined ? resolveIndex(rangeMatch[1], count) : 1
    const end = rangeMatch[2] !== undefined ? resolveIndex(rangeMatch[2], count) : count
    if (start < 1 || start > count) throw new Error(`range out of bounds: ${rangeStr} (count=${count})`)
    if (end < 1 || end > count) throw new Error(`range out of bounds: ${rangeStr} (count=${count})`)
    if (start > end) throw new Error(`range start > end: ${rangeStr}`)
    return Array.from({ length: end - start + 1 }, (_, i) => start + i)
  }
  if (/^-?\d+$/.test(rangeStr)) {
    const idx = resolveIndex(rangeStr, count)
    if (idx < 1 || idx > count) throw new Error(`index out of bounds: ${rangeStr} (count=${count})`)
    return [idx]
  }
  throw new Error(`invalid range: ${rangeStr}`)
}

function resolveIndex(str, count) {
  const n = Number(str)
  return n < 0 ? count + n + 1 : n
}

export function resolveSvgs(slug, destDir) {
  const stagesDir = `${destDir}/${slug}__stages`
  let entries
  try {
    entries = readdirSync(stagesDir)
  } catch {
    entries = []
  }
  const staged = entries.filter(e => e.endsWith('.svg')).sort()
  if (staged.length > 0) return staged
  throw new Error(`no SVGs found for slug "${slug}" in ${stagesDir}`)
}

const SLIDE_SEP = /^[ \t]*---[ \t]*$/m

export function expandBuildMarkers(markdown, imgDir) {
  const { frontmatter, body } = splitFrontmatter(markdown)
  const masked = maskCode(body)

  // Fast path: return original unchanged when no build directives exist.
  if (!/^\{\{\s*build\s/m.test(masked)) return markdown

  const originalSlides = body.split(SLIDE_SEP)
  const maskedSlides = masked.split(SLIDE_SEP)

  const expandedSlides = []
  for (let si = 0; si < originalSlides.length; si++) {
    const orig = originalSlides[si]
    const mask = maskedSlides[si]

    const buildMatches = [...mask.matchAll(/^\{\{([\s\S]*?)\}\}/gm)].filter(m =>
      m[1].trim().startsWith('build')
    )

    if (buildMatches.length > 1) {
      throw new Error(
        `slide ${si + 1} has multiple {{ build }} directives (only one per slide is allowed)`
      )
    }

    if (buildMatches.length === 0) {
      expandedSlides.push(orig)
      continue
    }

    const match = buildMatches[0]
    const { slug, rangeStr, alt } = parseBuildDirective(match[1])
    const svgs = resolveSvgs(slug, imgDir)
    const indices = parseRange(rangeStr, svgs.length)
    const n = indices.length

    // maskCode preserves line count: line numbers are identical between mask and orig.
    const lineNum = mask.slice(0, match.index).split('\n').length - 1
    const origLines = orig.split('\n')

    for (let mi = 0; mi < n; mi++) {
      const svgFile = svgs[indices[mi] - 1]
      const imgTag = `![${alt ?? ''}](${slug}__stages/${svgFile})`
      const lines = [...origLines]
      lines[lineNum] = imgTag
      expandedSlides.push(`<!-- build ${mi + 1} of ${n} -->\n${lines.join('\n')}`)
    }
  }

  const expandedBody = expandedSlides.join('\n---\n')
  return frontmatter ? `---\n${frontmatter}\n---\n${expandedBody}` : expandedBody
}
