import { builtinModules } from 'node:module'
import { dirname, join, resolve } from 'node:path'
import { stringifyImports } from 'unimport'
import type { Import } from 'unimport'
import type { Nuxt } from '@nuxt/schema'
import { relative } from 'pathe'
import { createResolver } from '@nuxt/kit'
import type { ESLintConfigGenAddon } from '../../types'
import type { ConfigGenOptions, ModuleOptions } from '../../module'
import { getDirs } from './utils'

const r = createResolver(import.meta.url)

export async function generateESLintConfig(
  options: ModuleOptions,
  nuxt: Nuxt,
  addons: ESLintConfigGenAddon[],
) {
  const importLines: Import[] = []
  const configItems: string[] = []

  const config: ConfigGenOptions = {
    standalone: true,
    ...typeof options.config !== 'boolean' ? options.config || {} : {},
  }

  let {
    configFile = join(nuxt.options.buildDir, 'eslint.config.mjs'),
  } = config

  configFile = resolve(nuxt.options.rootDir, configFile)
  const configDir = dirname(configFile)

  importLines.push(
    {
      from: 'eslint-typegen',
      name: 'default',
      as: 'typegen',
    },
    {
      from: '@nuxt/eslint-config/flat',
      name: 'createConfigForNuxt',
    },
    {
      from: '@nuxt/eslint-config/flat',
      name: 'defineFlatConfigs',
    },
    {
      from: '@nuxt/eslint-config/flat',
      name: 'resolveOptions',
    },
    {
      from: 'url',
      name: 'fileURLToPath',
    },
  )

  const dirs = getDirs(nuxt, options) || {}

  for (const addon of addons) {
    const resolved = await addon.getConfigs()
    if (resolved?.imports)
      importLines.push(...resolved.imports)
    if (resolved?.configs)
      configItems.push(...resolved.configs)
  }

  function relativeWithDot(path: string) {
    const r = relative(configDir, path)
    return r.startsWith('.') ? r : './' + r
  }

  const imports = await Promise.all(importLines.map(async (line): Promise<Import> => {
    return {
      ...line,
      from: builtinModules.includes(line.from)
        ? line.from.replace(/^(node:)?/, 'node:')
        : line.from.match(/^\w+:/)
          ? line.from
          : relativeWithDot(await r.resolvePath(line.from)),
    }
  }))

  const code = [
    '// ESLint config generated by Nuxt',
    '/// <reference path="./eslint-typegen.d.ts" />',
    '/* eslint-disable */',
    '// @ts-nocheck',
    '',
    stringifyImports(imports, false),
    '',
    'const r = (...args) => fileURLToPath(new URL(...args, import.meta.url))',
    '',
    'export { defineFlatConfigs }',
    '',
    `export const options = resolveOptions({`,
    `  features: ${JSON.stringify(config, null, 2)},`,
    `  dirs: {`,
    ...Object
      .entries(dirs)
      .map(([key, value]) => {
        return `    ${key}: [${value.map(v =>
          key === 'root'
            ? `r(${JSON.stringify(relativeWithDot(v))})`
            : JSON.stringify(v),
        ).join(', ')}],`
      }),
    `}`,
    `})`,
    '',
    `export const configs = createConfigForNuxt(options)`,

    ...(configItems.length
      ? [
          '',
          `configs.append(`,
          configItems.join(',\n\n'),
          `)`,
          '',
        ]
      : []),

    'export function withNuxt(...customs) {',
    '  return configs',
    '    .clone()',
    '    .append(...customs)',
    '    .onResolved(configs => typegen(configs, { dtsPath: r("./eslint-typegen.d.ts"), augmentFlatConfigUtils: true }))',
    '}',
    '',
    'export default withNuxt',
  ].join('\n')

  const [
    pathToFlatConfigUtils,
    pathToESLintConfigFlat,
  ] = await Promise.all([
    r.resolvePath('eslint-flat-config-utils').then(r => relativeWithDot(r)),
    r.resolvePath('@nuxt/eslint-config/flat').then(r => relativeWithDot(r)),
  ])

  const codeDts = [
    'import type { FlatConfigComposer } from ' + JSON.stringify(pathToFlatConfigUtils),
    'import { defineFlatConfigs } from ' + JSON.stringify(pathToESLintConfigFlat),
    'import type { NuxtESLintConfigOptionsResolved } from ' + JSON.stringify(pathToESLintConfigFlat),
    '',
    'declare const configs: FlatConfigComposer',
    'declare const options: NuxtESLintConfigOptionsResolved',
    'declare const withNuxt: typeof defineFlatConfigs',
    'export default withNuxt',
    'export { withNuxt, defineFlatConfigs, configs, options }',
  ].join('\n')

  return {
    code,
    codeDts,
    configFile,
  }
}
