import * as fs from 'fs'
import * as path from 'path'

const REPO_ROOT = path.resolve(__dirname, '..')
const RENDERER_DIR = path.join(REPO_ROOT, 'src/renderer')
const CHECK_EXTENSIONS = new Set(['.css', '.ts', '.tsx'])
const IGNORED_DIR_NAMES = new Set(['node_modules', 'dist', 'out'])
const IGNORED_FILE_PATTERNS = [/\.test\.(ts|tsx)$/, /\.spec\.(ts|tsx)$/, /\.snap$/]
const IGNORED_FILES = new Set([
  path.join(RENDERER_DIR, 'assets/styles/tailwind.css'),
  path.join(RENDERER_DIR, 'assets/styles/color.css'),
  path.join(RENDERER_DIR, 'assets/styles/legacy-vars.css')
])

export const LEGACY_VARS = [
  '--color-text-1',
  '--color-text-2',
  '--color-text-3',
  '--color-text',
  '--color-text-secondary',
  '--color-text-soft',
  '--color-text-light',
  '--color-background-soft',
  '--color-background-mute',
  '--color-background-opacity',
  '--color-border-soft',
  '--color-border-mute',
  '--color-error',
  '--color-link',
  '--color-primary-bg',
  '--color-fill-secondary',
  '--color-fill-2',
  '--color-bg-base',
  '--color-bg-1',
  '--color-code-background',
  '--color-inline-code-background',
  '--color-inline-code-text',
  '--color-hover',
  '--color-active',
  '--color-frame-border',
  '--color-group-background',
  '--color-reference',
  '--color-reference-text',
  '--color-reference-background',
  '--color-list-item',
  '--color-list-item-hover',
  '--color-highlight',
  '--color-background-highlight',
  '--color-background-highlight-accent',
  '--navbar-background-mac',
  '--navbar-background',
  '--modal-background',
  '--chat-background',
  '--chat-background-user',
  '--chat-background-assistant',
  '--chat-text-user',
  '--list-item-border-radius',
  '--color-gray-1',
  '--color-gray-2',
  '--color-gray-3',
  '--color-icon-white',
  '--color-primary-1',
  '--color-primary-6',
  '--color-status-success',
  '--color-status-error',
  '--color-status-warning'
] as const

const LEGACY_VAR_SET = new Set(LEGACY_VARS)
const OCCURRENCE_PATTERN = new RegExp(`(${LEGACY_VARS.map(escapeRegExp).join('|')})(?![\\w-])`, 'g')
const AUTO_FIX_REPLACEMENTS: Partial<Record<(typeof LEGACY_VARS)[number], string>> = {
  '--color-text-1': '--color-foreground',
  '--color-text-2': '--color-foreground-secondary',
  '--color-text-3': '--color-foreground-muted',
  '--color-text': '--color-foreground',
  '--color-text-secondary': '--color-foreground-secondary',
  '--color-text-soft': '--color-foreground-secondary',
  '--color-text-light': '--color-foreground',
  '--color-background-soft': '--color-muted',
  '--color-background-mute': '--color-accent',
  '--color-background-opacity': '--color-background',
  '--color-border-soft': '--color-border',
  '--color-border-mute': '--color-border',
  '--color-error': '--color-error-base',
  '--color-link': '--color-primary',
  '--color-primary-bg': '--color-primary-soft',
  '--color-fill-secondary': '--color-muted',
  '--color-fill-2': '--color-muted',
  '--color-bg-base': '--color-background',
  '--color-bg-1': '--color-muted',
  '--color-hover': '--color-accent',
  '--color-active': '--color-muted',
  '--color-frame-border': '--color-border',
  '--color-group-background': '--color-muted',
  '--color-reference': '--color-primary-soft',
  '--color-reference-text': '--color-primary',
  '--color-reference-background': '--color-primary-soft',
  '--color-list-item': '--color-background',
  '--color-list-item-hover': '--color-accent',
  '--navbar-background': '--color-background',
  '--modal-background': '--color-card',
  '--chat-background-user': '--color-muted',
  '--chat-text-user': '--color-foreground',
  '--list-item-border-radius': '--radius-lg',
  '--color-primary-1': '--color-primary-soft',
  '--color-primary-6': '--color-primary',
  '--color-status-success': '--color-success',
  '--color-status-error': '--color-error-base',
  '--color-status-warning': '--color-warning'
}

type WritableStream = Pick<typeof process.stdout, 'write'>

interface RunCliOptions {
  env?: NodeJS.ProcessEnv
  stdout?: WritableStream
  stderr?: WritableStream
}

export interface Finding {
  file: string
  line: number
  variable: string
  lineText: string
}

export interface FixSummary {
  filesChanged: number
  replacements: number
}

function escapeRegExp(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

export function shouldIgnoreFile(filePath: string): boolean {
  if (IGNORED_FILES.has(filePath)) return true
  const fileName = path.basename(filePath)
  return IGNORED_FILE_PATTERNS.some((pattern) => pattern.test(fileName))
}

function collectFiles(dir: string): string[] {
  const entries = fs.readdirSync(dir, { withFileTypes: true })
  const files: string[] = []

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name)

    if (entry.isDirectory()) {
      if (!IGNORED_DIR_NAMES.has(entry.name)) {
        files.push(...collectFiles(fullPath))
      }
      continue
    }

    if (!CHECK_EXTENSIONS.has(path.extname(entry.name))) continue
    if (shouldIgnoreFile(fullPath)) continue

    files.push(fullPath)
  }

  return files
}

export function collectTargetFiles(targetPath = RENDERER_DIR): string[] {
  const stats = fs.statSync(targetPath)

  if (stats.isDirectory()) {
    return collectFiles(targetPath)
  }

  if (!stats.isFile()) return []
  if (!CHECK_EXTENSIONS.has(path.extname(targetPath))) return []
  if (shouldIgnoreFile(targetPath)) return []

  return [targetPath]
}

function isVariableDefinitionLine(line: string, variable: string): boolean {
  return new RegExp(`^\\s*${escapeRegExp(variable)}\\s*:`).test(line)
}

export function isCommentLine(line: string): boolean {
  const trimmed = line.trim()
  return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed.startsWith('*/')
}

export function findLegacyVarsInLine(line: string): string[] {
  if (isCommentLine(line)) return []

  const matches = line.matchAll(OCCURRENCE_PATTERN)
  const variables: string[] = []

  for (const match of matches) {
    const variable = match[1]
    if (!variable || !LEGACY_VAR_SET.has(variable as (typeof LEGACY_VARS)[number])) continue
    if (isVariableDefinitionLine(line, variable)) continue
    variables.push(variable)
  }

  return variables
}

export function findLegacyVarHitsInContent(content: string, filePath: string): Finding[] {
  const lines = content.split(/\r?\n/)
  const findings: Finding[] = []

  for (let index = 0; index < lines.length; index++) {
    const line = lines[index]
    for (const variable of findLegacyVarsInLine(line)) {
      findings.push({
        file: filePath,
        line: index + 1,
        variable,
        lineText: line.trim()
      })
    }
  }

  return findings
}

export function fixLegacyVarsInContent(content: string): { content: string; replacements: number } {
  const lines = content.split(/\r?\n/)
  let replacements = 0

  const nextLines = lines.map((line) => {
    const variables = new Set(findLegacyVarsInLine(line))
    let nextLine = line

    for (const variable of variables) {
      const replacement = AUTO_FIX_REPLACEMENTS[variable as (typeof LEGACY_VARS)[number]]
      if (!replacement) continue

      const pattern = new RegExp(`${escapeRegExp(variable)}(?![\\w-])`, 'g')
      nextLine = nextLine.replace(pattern, () => {
        replacements += 1
        return replacement
      })
    }

    return nextLine
  })

  return { content: nextLines.join('\n'), replacements }
}

function findLegacyVarHits(filePath: string): Finding[] {
  const content = fs.readFileSync(filePath, 'utf8')
  return findLegacyVarHitsInContent(content, filePath)
}

function fixLegacyVarHits(filePath: string): number {
  const content = fs.readFileSync(filePath, 'utf8')
  const result = fixLegacyVarsInContent(content)
  if (result.replacements > 0) {
    fs.writeFileSync(filePath, result.content)
  }
  return result.replacements
}

function toRepoRelative(filePath: string): string {
  return path.relative(REPO_ROOT, filePath)
}

function printResults(findings: Finding[], stdout: WritableStream, stderr: WritableStream): void {
  if (findings.length === 0) {
    stdout.write('No legacy renderer CSS variable usages found.\n')
    return
  }

  const byVariable = new Map<string, number>()

  for (const finding of findings) {
    byVariable.set(finding.variable, (byVariable.get(finding.variable) ?? 0) + 1)
  }

  stderr.write('Legacy renderer CSS variable usages detected:\n')
  stderr.write('\n')

  for (const finding of findings) {
    stderr.write(`  ${toRepoRelative(finding.file)}:${finding.line}\n`)
    stderr.write(`    ${finding.variable}\n`)
    stderr.write(`    ${finding.lineText}\n`)
  }

  stderr.write('\n')
  stderr.write('Usage summary:\n')

  for (const [variable, count] of [...byVariable.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
    stderr.write(`  ${variable}: ${count}\n`)
  }

  stderr.write('\n')
  stderr.write(
    'Prefer @cherrystudio/ui theme contract variables and Tailwind semantic utilities instead of adding new legacy var usages.\n'
  )
}

function printUsage(stderr: WritableStream): void {
  stderr.write('Usage: pnpm styles:legacy-vars [path] [--strict] [--fix]\n')
}

function printFixSummary(summary: FixSummary, stdout: WritableStream): void {
  stdout.write(
    `Legacy renderer CSS variable auto-fix: changed ${summary.filesChanged} files, replaced ${summary.replacements} usages.\n`
  )
}

export function runCli(argv = process.argv.slice(2), options: RunCliOptions = {}): number {
  const stdout = options.stdout ?? process.stdout
  const stderr = options.stderr ?? process.stderr
  const env = options.env ?? process.env
  const strict = argv.includes('--strict') || env.LEGACY_CSS_VARS_STRICT === 'true'
  const fix = argv.includes('--fix')
  const pathArgs = argv.filter((arg) => arg !== '--strict' && arg !== '--fix')

  if (pathArgs.length > 1) {
    printUsage(stderr)
    return 1
  }

  const targetInput = pathArgs[0]
  const targetPath = targetInput ? path.resolve(REPO_ROOT, targetInput) : RENDERER_DIR

  if (!fs.existsSync(targetPath)) {
    stderr.write(`Path does not exist: ${targetInput}\n`)
    return 1
  }

  const files = collectTargetFiles(targetPath)

  if (fix) {
    const fixSummary: FixSummary = {
      filesChanged: 0,
      replacements: 0
    }

    for (const file of files) {
      const replacements = fixLegacyVarHits(file)
      if (replacements === 0) continue
      fixSummary.filesChanged += 1
      fixSummary.replacements += replacements
    }

    printFixSummary(fixSummary, stdout)
  }

  const findings = files.flatMap(findLegacyVarHits)

  printResults(findings, stdout, stderr)

  return strict && findings.length > 0 ? 1 : 0
}

if (require.main === module) {
  process.exitCode = runCli()
}
