// Composes and renders a full schedule deck via Marp.
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync, statSync, cpSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { join, resolve, relative, dirname, extname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { buildConfig } from './config.js'
import { splitFrontmatter } from './frontmatter.js'
import { findMarkers } from './marker.js'
import { resolveSlug } from './resolve.js'
import { composeItem, finalizeHtml } from './item.js'
import { buildSchedule, expandScheduleIncludes } from './schedule.js'

const _pkgDir = dirname(dirname(fileURLToPath(import.meta.url)))

async function buildItemDecks(expandedBody, root, isDraft) {
  const topicDirs = new Set()
  const itemDecks = new Map()
  for (const marker of findMarkers(expandedBody)) {
    if (marker.parsed.kind !== 'content') continue
    let resolved
    try {
      resolved = resolveSlug(marker.parsed.slug, { root })
    } catch (err) {
      if (!isDraft) throw err
      continue
    }
    if (resolved.type === 'activity') continue
    const srcDir = dirname(resolved.path)
    topicDirs.add(srcDir)
    const rel = relative(resolve(root, 'content'), srcDir)
    itemDecks.set(marker.parsed.slug, resolve(root, 'decks', rel))
    const code = await composeItem(marker.parsed.slug, [], { root })
    if (code !== 0) throw new Error(`item build failed: ${marker.parsed.slug} (exit ${code})`)
  }
  return { topicDirs, itemDecks }
}

export async function composeSchedule(slug, _rest = [], { root = process.cwd(), draft: isDraft = false, deckMeta = {} } = {}) {
  const schedSrc = resolve(root, `content/schedules/${slug}/index.md`)
  if (!existsSync(schedSrc)) throw new Error(`schedule not found: ${slug}`)
  const { body: rawBody } = splitFrontmatter(readFileSync(schedSrc, 'utf8'))
  const { expandedBody } = expandScheduleIncludes(rawBody, root, new Set([slug]))

  const { topicDirs, itemDecks } = await buildItemDecks(expandedBody, root, isDraft)

  const schedOut = resolve(root, `decks/schedules/${slug}/index.html`)
  if (existsSync(schedOut)) {
    const schedOutMtime = statSync(schedOut).mtimeMs
    if (schedOutMtime >= statSync(schedSrc).mtimeMs) {
      const allTopicsFresh = [...topicDirs].every(srcDir => {
        const rel = relative(resolve(root, 'content'), srcDir)
        const topicOut = resolve(root, 'decks', rel, 'index.html')
        return !existsSync(topicOut) || statSync(topicOut).mtimeMs <= schedOutMtime
      })
      if (allTopicsFresh) return { status: 0, topicDirs }
    }
  }

  const { frontmatter, markdown, outline } = buildSchedule(slug, { root, draft: isDraft, deckMeta })

  const destDir = resolve(root, `decks/schedules/${slug}`)
  rmSync(destDir, { recursive: true, force: true })
  mkdirSync(destDir, { recursive: true })

  for (const [itemSlug, builtDeckDir] of itemDecks) {
    const SKIP = new Set(['.html', '.md', '.excalidraw'])
    cpSync(builtDeckDir, join(destDir, itemSlug), {
      recursive: true,
      filter: src => !SKIP.has(extname(src)),
    })
  }

  const composedPath = join(destDir, 'schedule.md')
  writeFileSync(composedPath, `---\n${frontmatter}\n---\n\n${markdown}`)

  const marpBin = resolve(_pkgDir, 'node_modules/.bin/marp')
  const marpConfig = buildConfig(root)
  const marpRun = spawnSync(marpBin, ['--no-stdin', '--config', marpConfig, '--output', 'index.html', 'schedule.md'], {
    cwd: destDir,
    stdio: 'inherit',
  })
  if (marpRun.status !== 0) return { status: marpRun.status ?? 1, topicDirs }

  const outPath = join(destDir, 'index.html')
  const html = readFileSync(outPath, 'utf8')
  writeFileSync(outPath, finalizeHtml(html, { root, expanded: markdown, outline }))
  process.stdout.write(`→ ${relative(root, outPath)}\n`)

  return { status: 0, topicDirs }
}
