import { readFileSync, existsSync, readdirSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

const _pkgDir = dirname(dirname(fileURLToPath(import.meta.url)))
const _navJs = join(_pkgDir, 'src', 'client', 'nav.js')
const _builtInsDir = join(_pkgDir, 'src', 'client', 'transitions')

function readDir(dir) {
  return readdirSync(dir)
    .filter(f => f.endsWith('.js'))
    .sort()
    .map(f => readFileSync(join(dir, f), 'utf8'))
    .join('\n')
}

function resolveTransition(name, contentRoot) {
  const userFile = join(contentRoot, 'transitions', `${name}.js`)
  if (existsSync(userFile)) return readFileSync(userFile, 'utf8')
  const userDir = join(contentRoot, 'transitions', name)
  if (existsSync(userDir)) return readDir(userDir)
  const builtInFile = join(_builtInsDir, `${name}.js`)
  if (existsSync(builtInFile)) return readFileSync(builtInFile, 'utf8')
  const builtInDir = join(_builtInsDir, name)
  if (existsSync(builtInDir)) return readDir(builtInDir)
  throw new Error(
    `unknown transition: "${name}" — not found in ${contentRoot}/transitions/ or built-in transitions`
  )
}

function findTransitionNames(html) {
  const names = new Set()
  for (const m of html.matchAll(/data-transition(?:-back)?="([^"]+)"/g)) {
    const raw = m[1].replace(/&quot;/g, '"')
    try { names.add(JSON.parse(raw).name) } catch {
      console.warn(`marp-compose: ignoring malformed transition attribute: ${raw}`)
    }
  }
  return names
}

function buildMorphCss(html) {
  const ids = new Set()
  for (const m of html.matchAll(/data-morph-id="([^"]+)"/g)) ids.add(m[1])
  const elementIds = new Set()
  for (const m of html.matchAll(/data-element-id="([^"]+)"/g)) elementIds.add(m[1])
  const dur = 'var(--marp-bespoke-transition-animation-duration,.5s)'
  const rules = [
    '@keyframes marp-transition-morph{from{opacity:1}to{opacity:0}}',
    '@keyframes marp-morph-in{from{opacity:0}to{opacity:1}}',
    `::view-transition-old(root){animation:marp-transition-morph ${dur} both linear!important;mix-blend-mode:plus-lighter!important}`,
    `::view-transition-new(root){animation:marp-morph-in ${dur} both linear!important;mix-blend-mode:plus-lighter!important}`,
  ]
  for (const id of ids) {
    rules.push(`.bespoke-marp-active [data-morph-id="${id}"]{view-transition-name:${id}}`)
  }
  // Override Marp's wildcard fallback animations for proxy pseudo-elements.
  // Named selectors have higher specificity than (*), so these win without !important.
  for (const id of elementIds) {
    const name = `svgp-${id}`
    rules.push(`::view-transition-old(${name}){animation:marp-transition-morph ${dur} both linear}`)
    rules.push(`::view-transition-new(${name}){animation:marp-morph-in ${dur} both linear}`)
  }
  return rules.join('\n')
}

export function injectTransition(html, { contentRoot }) {
  const names = findTransitionNames(html)
  if (!names.size) return html

  const injections = []

  if (names.has('morph')) {
    const css = buildMorphCss(html)
    if (css) injections.push(`<style>\n${css}\n</style>`)
  }

  if (names.size) {
    const parts = [readFileSync(_navJs, 'utf8')]
    for (const name of names) parts.push(resolveTransition(name, contentRoot))
    injections.push(`<script>\n${parts.join('\n')}\n</script>`)
  }

  return html.replace('</body>', `${injections.join('\n')}\n</body>`)
}
