import { mkdir, writeFile } from 'node:fs/promises'
import { dirname } from 'node:path'

import type { TypeRegistry } from '../types/index.js'
import { typeDescriptorToString } from '../types/type-descriptor.js'

export interface TypeSnapshotOptions {
  outFile?: string
  namespace?: string
  banner?: string
}

export class TypeDeclarationGenerator {
  constructor(private readonly registry: TypeRegistry) {}

  generate(options: TypeSnapshotOptions = {}): string {
    const { namespace, banner } = options
    const lines: string[] = []
    lines.push('// -----------------------------------------------------------------------------')
    lines.push('//  TypedFetch type snapshot - generated from runtime + schema discovery')
    lines.push('// -----------------------------------------------------------------------------')
    if (banner) {
      lines.push(`// ${banner}`)
    }
    lines.push('')

    const declarations: string[] = []
    const endpoints: string[] = []

    const entries = Object.entries(this.registry)
    if (entries.length === 0) {
      declarations.push('// No endpoints recorded yet. Make a request or run tf.discover().')
    }

    for (const [key, entry] of entries) {
      const [rawMethod = 'GET', ...pathParts] = key.split(' ')
      const path = pathParts.join(' ') || '/'
      const method = rawMethod || 'GET'
      const typeBase = formatName(method, path)
      const requestTypeName = `${typeBase}Request`
      const responseTypeName = `${typeBase}Response`
      const requestDescriptor = entry.request || { type: 'unknown' }
      const responseDescriptor = entry.response || { type: 'unknown' }

      declarations.push(`export type ${requestTypeName} = ${typeDescriptorToString(requestDescriptor)}`)
      declarations.push(`export type ${responseTypeName} = ${typeDescriptorToString(responseDescriptor)}`)
      declarations.push('')

      endpoints.push(
        `  '${method} ${path}': { request: ${requestTypeName}; response: ${responseTypeName}; method: '${method}'; path: '${path}' }`
      )
    }

    if (endpoints.length) {
      declarations.push('export interface TypedFetchGeneratedEndpoints {')
      declarations.push(...endpoints)
      declarations.push('}')
    }

    const content = [...lines, ...declarations].join('\n')
    if (!namespace) {
      return content
    }

    const namespaced = [`declare namespace ${namespace} {`, ...indent(declarations), '}']
    return [...lines, ...namespaced].join('\n')
  }

  async writeToFile(options: TypeSnapshotOptions): Promise<string> {
    const code = this.generate(options)
    if (options.outFile) {
      await mkdir(dirname(options.outFile), { recursive: true })
      await writeFile(options.outFile, code, 'utf8')
    }
    return code
  }
}

function formatName(method: string, path: string): string {
  const methodName = method.toLowerCase().replace(/(^|[-_\s])(\w)/g, (_, __, char: string) => char.toUpperCase())
  const parts = path
    .split('/')
    .filter(Boolean)
    .map((segment) => segment.replace(/[:{}]/g, ''))
    .map((segment) => segment.replace(/(^|[-_\s])(\w)/g, (_, __, char: string) => char.toUpperCase()))

  const suffix = parts.join('') || 'Root'
  return `${methodName}${suffix}`
}

function indent(lines: string[], spaces = 2): string[] {
  const pad = ' '.repeat(spaces)
  return lines.map(line => (line ? `${pad}${line}` : line))
}
