import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'

export function normalizeBundledSkillPath(value) {
  return String(value || '').replaceAll('\\', '/').replace(/^\.\//, '').replace(/\/{2,}/g, '/')
}

export function isBundledSkillPathExcluded(value) {
  const rel = normalizeBundledSkillPath(value)
  const parts = rel.split('/')
  if (parts.some(part => ['.git', 'node_modules', '__pycache__'].includes(part))) return true
  if (parts.some(part => part === '.DS_Store' || part.endsWith('.pyc'))) return true

  const base = parts.at(-1) || ''
  if (base === '.env' || (/^\.env\./.test(base) && !['.env.example', '.env.schema', '.env.sample', '.env.template'].includes(base))) return true
  if (/\.(key|pem|p12|pfx|crt|sqlite|sqlite-shm|sqlite-wal|db|db-shm|db-wal|log)$/i.test(base)) return true
  return false
}

function unquote(value) {
  const text = String(value || '').trim()
  if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
    return text.slice(1, -1)
  }
  return text
}

export function parseSkillMetadata(content, skillFilePath) {
  const rel = normalizeBundledSkillPath(skillFilePath)
  const sourcePath = rel.replace(/\/SKILL\.md$/i, '')
  const system = sourcePath.startsWith('.system/')
  const fallbackName = sourcePath.split('/').at(-1) || 'skill'
  const frontmatter = String(content || '').match(/^---\s*\n([\s\S]*?)\n---(?:\s*\n|$)/)?.[1] || ''
  const values = {}
  const lines = frontmatter.split(/\r?\n/)
  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index]
    const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/)
    if (!match) continue
    const [, key, rawValue] = match
    if (rawValue === '>' || rawValue === '|') {
      const parts = []
      while (index + 1 < lines.length && /^\s+/.test(lines[index + 1])) {
        parts.push(lines[index + 1].trim())
        index += 1
      }
      values[key] = parts.join(rawValue === '>' ? ' ' : '\n').trim()
    } else {
      values[key] = unquote(rawValue)
    }
  }
  const rawName = values.name || fallbackName
  return {
    name: system ? `系统 · ${rawName}` : rawName,
    description: values.description || `本机 Skill：${rawName}`,
    system,
    sourcePath,
  }
}

export function bundledSkillId(sourcePath) {
  const digest = crypto.createHash('sha256').update(normalizeBundledSkillPath(sourcePath)).digest('hex').slice(0, 12)
  return `skill_bundle_${digest}`
}

function countIncludedFiles(dir, rootDir) {
  let count = 0
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const abs = path.join(dir, entry.name)
    const rel = normalizeBundledSkillPath(path.relative(rootDir, abs))
    if (isBundledSkillPathExcluded(rel)) continue
    if (entry.isDirectory()) count += countIncludedFiles(abs, rootDir)
    else if (entry.isFile()) count += 1
  }
  return count
}

function findSkillFiles(dir, rootDir, output = []) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const abs = path.join(dir, entry.name)
    const rel = normalizeBundledSkillPath(path.relative(rootDir, abs))
    if (isBundledSkillPathExcluded(rel)) continue
    if (entry.isDirectory()) findSkillFiles(abs, rootDir, output)
    else if (entry.isFile() && entry.name.toLowerCase() === 'skill.md') output.push({ abs, rel })
  }
  return output
}

export function buildBundledSkillManifest(rootDir, { createdAt = new Date().toISOString() } = {}) {
  const skills = findSkillFiles(rootDir, rootDir)
    .map(({ abs, rel }) => {
      const metadata = parseSkillMetadata(fs.readFileSync(abs, 'utf8'), rel)
      const packageRoot = metadata.sourcePath
      return {
        id: bundledSkillId(metadata.sourcePath),
        name: metadata.name,
        description: metadata.description,
        system: metadata.system,
        sourcePath: metadata.sourcePath,
        packageRoot,
        files: countIncludedFiles(path.join(rootDir, packageRoot), rootDir),
      }
    })
    .sort((a, b) => a.sourcePath.localeCompare(b.sourcePath, 'en'))

  return {
    schemaVersion: 1,
    createdAt,
    skillCount: skills.length,
    skills,
  }
}
