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

const rootDir = process.cwd()
const componentsDir = path.join(rootDir, 'src/runtime/components')
const composablesDir = path.join(rootDir, 'src/runtime/composables')
const outMdFile = path.join(rootDir, 'docs/ai-summary.md')
const outJsonFile = path.join(rootDir, 'docs/ai-summary.json')
const outTypeIndexFile = path.join(rootDir, 'docs/type-index.json')

function walkFiles(dir, exts = new Set(['.vue', '.ts'])) {
  const result = []
  if (!fs.existsSync(dir)) return result
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const full = path.join(dir, entry.name)
    if (entry.isDirectory()) {
      result.push(...walkFiles(full, exts))
    } else if (exts.has(path.extname(entry.name))) {
      result.push(full)
    }
  }
  return result.sort((a, b) => a.localeCompare(b))
}

function toPosix(p) {
  return p.split(path.sep).join('/')
}

function extractTopDescription(content) {
  const m = content.match(/\/\*\*([\s\S]*?)\*\//)
  if (!m) return 'No top-level description found.'
  const lines = m[1]
    .split('\n')
    .map((l) => l.replace(/^\s*\*\s?/, '').trim())
    .filter(Boolean)
    .filter((l) => !l.toLowerCase().includes('vue-docgen'))
  return lines[0] || 'No top-level description found.'
}

function compact(str) {
  return str.replace(/\s+/g, ' ').trim()
}

function findInterfaceBody(content, interfaceName) {
  const needle = `interface ${interfaceName}`
  const start = content.indexOf(needle)
  if (start === -1) return null

  const braceStart = content.indexOf('{', start)
  if (braceStart === -1) return null

  let depth = 0
  for (let i = braceStart; i < content.length; i++) {
    const ch = content[i]
    if (ch === '{') depth++
    if (ch === '}') depth--
    if (depth === 0) {
      return content.slice(braceStart + 1, i)
    }
  }
  return null
}

function extractWithDefaults(content) {
  const defaults = {}
  const m = content.match(/withDefaults\s*\(\s*defineProps[\s\S]*?,\s*\{([\s\S]*?)\}\s*\)/)
  if (!m) return defaults

  const lines = m[1].split('\n')
  for (const line of lines) {
    const clean = line.trim()
    if (!clean || clean.startsWith('//')) continue
    const kv = clean.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+?)(?:,\s*)?(?:\/\/\s*(.*))?$/)
    if (!kv) continue
    const key = kv[1]
    const rawValue = kv[2].trim()
    const description = (kv[3] || '').trim()
    defaults[key] = { value: rawValue, description }
  }

  return defaults
}

function inferTypeFromDefault(rawValue) {
  if (rawValue === 'true' || rawValue === 'false') return 'boolean'
  if (rawValue === 'null') return 'null'
  if (/^['"].*['"]$/.test(rawValue)) return 'string'
  if (/^-?\d+(\.\d+)?$/.test(rawValue)) return 'number'
  if (/^\(\)\s*=>\s*\[\s*\]$/.test(rawValue)) return 'any[]'
  if (/^\(\)\s*=>\s*\(\s*\{\s*\}\s*\)$/.test(rawValue)) return 'Record<string, any>'
  if (/^\(\)\s*=>\s*\[/.test(rawValue)) return 'any[]'
  if (/^\(\)\s*=>\s*\(\s*\{/.test(rawValue)) return 'Record<string, any>'
  return 'unknown'
}

function extractProps(content) {
  const props = []
  const ifaceBody = findInterfaceBody(content, 'Props')
  const defaults = extractWithDefaults(content)

  if (ifaceBody) {
    const lines = ifaceBody.split('\n')
    for (const line of lines) {
      const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)(\?)?\s*:\s*([^/;]+(?:\|[^/;]+)*)[;]?\s*(?:\/\/\s*(.+))?\s*$/)
      if (m) {
        const name = m[1]
        const optional = !!m[2]
        const type = compact(m[3].trim())
        const description = (m[4] || '').trim()
        props.push({
          name,
          type,
          required: !optional,
          default: Object.prototype.hasOwnProperty.call(defaults, name) ? defaults[name].value : undefined,
          description: description || (defaults[name]?.description || ''),
        })
      }
    }
  }

  // Fallback for components where Props is empty and typed props come from imported intersections.
  if (!props.length && Object.keys(defaults).length) {
    for (const [name, meta] of Object.entries(defaults)) {
      props.push({
        name,
        type: inferTypeFromDefault(meta.value),
        required: false,
        default: meta.value,
        description: meta.description || 'Inferred from withDefaults()',
      })
    }
  }

  return props
}

function extractEmits(content) {
  const emits = []

  const typed = content.match(/defineEmits<\{([\s\S]*?)\}>\(\)/)
  if (typed) {
    const overloads = typed[1].matchAll(/\(\s*e\s*:\s*'([^']+)'\s*(?:,\s*([^)]*))?\)\s*:\s*void/g)
    for (const m of overloads) {
      emits.push({
        event: m[1],
        payload: m[2] ? compact(m[2]) : undefined,
      })
    }
  }

  const arr = content.match(/defineEmits\s*\(\s*\[([\s\S]*?)\]\s*\)/)
  if (arr) {
    const names = arr[1].matchAll(/'([^']+)'|"([^"]+)"/g)
    for (const m of names) {
      emits.push({ event: m[1] || m[2] })
    }
  }

  return emits
}

function extractTemplate(content) {
  const m = content.match(/<template[^>]*>([\s\S]*)<\/template>/i)
  return m ? m[1] : ''
}

function extractSlots(content) {
  const slots = []
  const seen = new Set()
  const template = extractTemplate(content)
  if (!template) return slots

  const slotRe = /<slot\b([^>]*?)(?:\/?>)/g
  let match
  while ((match = slotRe.exec(template)) !== null) {
    const attrs = match[1] || ''
    const nameMatch = attrs.match(/(?:^|\s)name\s*=\s*"([^"]+)"/)
                   || attrs.match(/(?:^|\s)#([A-Za-z_][A-Za-z0-9_-]*)/)
    const name = nameMatch ? nameMatch[1] : 'default'
    if (seen.has(name)) continue
    seen.add(name)

    const bindings = []
    const bindRe = /:([A-Za-z_][A-Za-z0-9_-]*)\s*=\s*"([^"]*)"/g
    let bm
    while ((bm = bindRe.exec(attrs)) !== null) {
      if (bm[1] === 'name') continue
      bindings.push({ name: bm[1], expression: compact(bm[2]) })
    }
    slots.push({ name, bindings })
  }
  return slots
}

function extractExposed(content) {
  const exposed = []
  const m = content.match(/defineExpose\s*\(\s*\{([\s\S]*?)\}\s*\)/)
  if (!m) return exposed
  const body = m[1]
  // Match top-level identifiers; ignore values, nested braces handled by depth.
  let depth = 0
  let token = ''
  const tokens = []
  for (let i = 0; i < body.length; i++) {
    const ch = body[i]
    if (ch === '{' || ch === '(' || ch === '[') depth++
    else if (ch === '}' || ch === ')' || ch === ']') depth--
    else if (ch === ',' && depth === 0) {
      tokens.push(token); token = ''
      continue
    }
    token += ch
  }
  if (token.trim()) tokens.push(token)

  for (const raw of tokens) {
    const piece = raw.trim().replace(/^\s*\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '').trim()
    if (!piece) continue
    // `name`, `name: value`, `name(...) {}` — take the first identifier.
    const idMatch = piece.match(/^([A-Za-z_$][A-Za-z0-9_$]*)/)
    if (!idMatch) continue
    const name = idMatch[1]
    if (exposed.find(e => e.name === name)) continue
    exposed.push({ name })
  }
  return exposed
}

function extractExports(content) {
  const functions = []
  const constants = []
  const interfaces = []
  const types = []
  const enums = []

  const fnMatches = content.matchAll(/export\s+function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([\s\S]*?)\)\s*(?::\s*([^({;\n]+(?:<[\s\S]*?>)?))?\s*\{/g)
  for (const m of fnMatches) {
    functions.push({
      name: m[1],
      params: compact(m[2]),
      returns: m[3] ? compact(m[3]) : undefined,
      signature: compact(`function ${m[1]}(${m[2]})${m[3] ? `: ${m[3]}` : ''}`),
    })
  }

  const constMatches = content.matchAll(/export\s+const\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*([^=;\n]+))?\s*=/g)
  for (const m of constMatches) {
    constants.push({
      name: m[1],
      type: m[2] ? compact(m[2]) : undefined,
    })
  }

  const interfaceMatches = content.matchAll(/export\s+interface\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\n\}/g)
  for (const m of interfaceMatches) {
    interfaces.push({
      name: m[1],
      shape: compact(m[2]),
    })
  }

  const typeMatches = content.matchAll(/export\s+type\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^;\n]+(?:\n[^;\n]+)*)/g)
  for (const m of typeMatches) {
    types.push({
      name: m[1],
      definition: compact(m[2].replace(/\n/g, ' ')),
    })
  }

  const enumMatches = content.matchAll(/export\s+enum\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/g)
  for (const m of enumMatches) {
    const members = m[2]
      .split('\n')
      .map((line) => line.trim())
      .filter(Boolean)
      .map((line) => line.replace(/,$/, ''))
    enums.push({
      name: m[1],
      members,
    })
  }

  return { functions, constants, interfaces, types, enums }
}

const componentFiles = walkFiles(componentsDir, new Set(['.vue']))
const composableFiles = walkFiles(composablesDir, new Set(['.ts']))

const generatedAt = new Date().toISOString()
const docModel = {
  generatedAt,
  scope: {
    componentCount: componentFiles.length,
    composableCount: composableFiles.length,
  },
  components: [],
  composables: [],
}

const lines = []
lines.push('# AI Summary')
lines.push('')
lines.push('AI-first summary generated from source files with explicit type metadata.')
lines.push('')
lines.push(`Generated at: ${generatedAt}`)
lines.push('')
lines.push('## Scope')
lines.push('')
lines.push(`- Components: ${componentFiles.length}`)
lines.push(`- Composables: ${composableFiles.length}`)
lines.push('')
lines.push('## Components')
lines.push('')

for (const file of componentFiles) {
  const rel = toPosix(path.relative(rootDir, file))
  const content = fs.readFileSync(file, 'utf8')
  const summary = extractTopDescription(content)
  const props = extractProps(content)
  const emits = extractEmits(content)
  const slots = extractSlots(content)
  const exposed = extractExposed(content)
  const componentEntry = {
    path: rel,
    summary,
    props,
    emits,
    slots,
    exposed,
  }
  docModel.components.push(componentEntry)

  lines.push(`### \`${rel}\``)
  lines.push(`- Summary: ${summary}`)
  if (props.length) {
    lines.push(`- Props (${props.length}):`)
    for (const prop of props) {
      const chunks = [
        `type=${prop.type}`,
        prop.required ? 'required=true' : 'required=false',
      ]
      if (prop.default !== undefined) chunks.push(`default=${prop.default}`)
      if (prop.description) chunks.push(`desc=${prop.description}`)
      lines.push(`  - \`${prop.name}\` { ${chunks.join(' | ')} }`)
    }
  } else {
    lines.push('- Props: None declared in a local `Props` interface.')
  }
  if (emits.length) {
    lines.push(`- Emits (${emits.length}):`)
    for (const e of emits) {
      lines.push(`  - \`${e.event}\`${e.payload ? ` payload: ${e.payload}` : ''}`)
    }
  } else {
    lines.push('- Emits: None detected.')
  }
  if (slots.length) {
    lines.push(`- Slots (${slots.length}):`)
    for (const s of slots) {
      const bindings = s.bindings.length ? ` bindings: ${s.bindings.map(b => `${b.name}=${b.expression}`).join(', ')}` : ''
      lines.push(`  - \`${s.name}\`${bindings}`)
    }
  } else {
    lines.push('- Slots: None detected.')
  }
  if (exposed.length) {
    lines.push(`- Exposed (${exposed.length}):`)
    for (const x of exposed) {
      lines.push(`  - \`${x.name}\``)
    }
  } else {
    lines.push('- Exposed: None detected.')
  }
  lines.push('')
}

lines.push('## Composables')
lines.push('')

for (const file of composableFiles) {
  const rel = toPosix(path.relative(rootDir, file))
  const content = fs.readFileSync(file, 'utf8')
  const summary = extractTopDescription(content)
  const exports = extractExports(content)
  const composableEntry = {
    path: rel,
    summary,
    exports,
  }
  docModel.composables.push(composableEntry)

  lines.push(`### \`${rel}\``)
  lines.push(`- Summary: ${summary}`)
  lines.push(`- Export functions (${exports.functions.length}): ${exports.functions.map((f) => `\`${f.signature}\``).join(', ') || 'none'}`)
  lines.push(`- Export interfaces (${exports.interfaces.length}): ${exports.interfaces.map((i) => `\`${i.name}\``).join(', ') || 'none'}`)
  lines.push(`- Export types (${exports.types.length}): ${exports.types.map((t) => `\`${t.name}\``).join(', ') || 'none'}`)
  lines.push(`- Export consts (${exports.constants.length}): ${exports.constants.map((c) => `\`${c.name}\``).join(', ') || 'none'}`)
  lines.push(`- Export enums (${exports.enums.length}): ${exports.enums.map((e) => `\`${e.name}\``).join(', ') || 'none'}`)
  lines.push('')
}

fs.mkdirSync(path.dirname(outMdFile), { recursive: true })
fs.writeFileSync(outMdFile, lines.join('\n'))
fs.writeFileSync(outJsonFile, JSON.stringify(docModel, null, 2))
fs.writeFileSync(outTypeIndexFile, JSON.stringify(docModel, null, 2))

console.log(`Generated ${toPosix(path.relative(rootDir, outMdFile))}`)
console.log(`Generated ${toPosix(path.relative(rootDir, outJsonFile))}`)
console.log(`Generated ${toPosix(path.relative(rootDir, outTypeIndexFile))}`)
