import type { CommonOptions, PackageMeta } from '../types'
import { existsSync, promises as fs } from 'node:fs'
import process from 'node:process'
import detectIndent from 'detect-indent'
import { up as findUp } from 'empathic/find'
import { dirname, join, resolve } from 'pathe'
import { glob } from 'tinyglobby'
import { DEFAULT_IGNORE_PATHS } from '../constants'
import { createDependenciesFilter } from '../utils/dependenciesFilter'
import { loadBunWorkspace, writeBunWorkspace } from './bunWorkspaces'
import { isGitHubActionsEnabled, loadGitHubAction, writeGitHubAction } from './githubActions'
import { loadPackageJSON, writePackageJSON } from './packageJson'
import { loadPackageYAML, writePackageYAML } from './packageYaml'
import { loadPnpmWorkspace, writePnpmWorkspace } from './pnpmWorkspaces'
import { loadYarnWorkspace, writeYarnWorkspace } from './yarnWorkspaces'

export async function readJSON(filepath: string) {
  return JSON.parse(await fs.readFile(filepath, 'utf-8'))
}

export async function writeJSON(filepath: string, data: Record<string, unknown>) {
  const actualContent = await fs.readFile(filepath, 'utf-8')
  const fileIndent = detectIndent(actualContent).indent || '  '

  return await fs.writeFile(filepath, `${JSON.stringify(data, null, fileIndent)}\n`, 'utf-8')
}

export async function writePackage(
  pkg: PackageMeta,
  options: CommonOptions,
) {
  switch (pkg.type) {
    case 'package.json':
      return writePackageJSON(pkg, options)
    case 'package.yaml':
      return writePackageYAML(pkg, options)
    case 'pnpm-workspace.yaml':
      return writePnpmWorkspace(pkg, options)
    case 'bun-workspace':
      return writeBunWorkspace(pkg, options)
    case '.yarnrc.yml':
      return writeYarnWorkspace(pkg, options)
    case 'github-action':
      return writeGitHubAction(pkg, options)
    default:
      throw new Error(`Unsupported package type: ${pkg.type}`)
  }
}

const GITHUB_ACTIONS_FILE_RE = /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/
const GITHUB_ACTION_MANIFEST_RE = /(?:^|\/)action\.ya?ml$/

export function isGitHubActionsPath(relative: string): boolean {
  return GITHUB_ACTIONS_FILE_RE.test(relative) || GITHUB_ACTION_MANIFEST_RE.test(relative)
}

async function loadGitHubActionsFiles(options: CommonOptions): Promise<string[]> {
  const ignore = DEFAULT_IGNORE_PATHS.concat(options.ignorePaths || [])
  const patterns = options.recursive
    ? [
        '**/.github/workflows/*.yml',
        '**/.github/workflows/*.yaml',
        '**/.github/actions/**/action.yml',
        '**/.github/actions/**/action.yaml',
        '**/action.yml',
        '**/action.yaml',
      ]
    : [
        '.github/workflows/*.yml',
        '.github/workflows/*.yaml',
        '.github/actions/**/action.yml',
        '.github/actions/**/action.yaml',
        'action.yml',
        'action.yaml',
      ]

  const files = await glob(patterns, {
    cwd: resolve(options.cwd || process.cwd()),
    ignore,
    onlyFiles: true,
    dot: true,
    expandDirectories: false,
  })

  return [...new Set(files)].sort((a, b) => a.localeCompare(b))
}

export async function loadPackage(
  relative: string,
  options: CommonOptions,
  shouldUpdate: (name: string) => boolean,
): Promise<PackageMeta[]> {
  if (isGitHubActionsPath(relative))
    return loadGitHubAction(relative, options, shouldUpdate)

  if (relative.endsWith('pnpm-workspace.yaml'))
    return loadPnpmWorkspace(relative, options, shouldUpdate)

  if (relative.endsWith('.yarnrc.yml'))
    return loadYarnWorkspace(relative, options, shouldUpdate)

  if (relative.endsWith('package.yaml'))
    return loadPackageYAML(relative, options, shouldUpdate)

  // Check if this package.json contains Bun workspaces with catalogs
  if (relative.endsWith('package.json')) {
    const filepath = resolve(options.cwd ?? '', relative)
    try {
      const raw = await readJSON(filepath)
      const workspaces = raw?.workspaces

      // Only process Bun catalogs if we detect Bun is being used
      if (workspaces && (workspaces.catalog || workspaces.catalogs)) {
        const cwd = resolve(options.cwd || process.cwd())
        const hasBunLock = existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock'))

        if (hasBunLock) {
          // Pass the same raw object to both loaders so writes don't clobber each other
          const bunWorkspaces = await loadBunWorkspace(relative, options, shouldUpdate, raw)
          const packageJson = await loadPackageJSON(relative, options, shouldUpdate, raw)
          return [...bunWorkspaces, ...packageJson]
        }
      }

      // Reuse already-read raw for non-bun case
      return loadPackageJSON(relative, options, shouldUpdate, raw)
    }
    catch {
      // Safe guard: If we can't read the file, fall back to normal package.json loading
    }
  }

  return loadPackageJSON(relative, options, shouldUpdate)
}

export async function loadPackages(options: CommonOptions): Promise<PackageMeta[]> {
  let packagesNames: string[] = []

  const cwd = resolve(options.cwd || process.cwd())
  const filter = createDependenciesFilter(options.include, options.exclude)

  if (options.recursive) {
    // Look for both package.yaml and package.json files
    const yamlPackages = await glob('**/package.yaml', {
      ignore: DEFAULT_IGNORE_PATHS.concat(options.ignorePaths || []),
      cwd: options.cwd,
      onlyFiles: true,
      dot: false,
      expandDirectories: false,
    })

    const jsonPackages = await glob('**/package.json', {
      ignore: DEFAULT_IGNORE_PATHS.concat(options.ignorePaths || []),
      cwd: options.cwd,
      onlyFiles: true,
      dot: false,
      expandDirectories: false,
    })

    // Prioritize package.yaml over package.json in the same directory
    const packageDirs = new Set<string>()

    // Add all package.yaml files first (higher priority)
    for (const yamlPkg of yamlPackages) {
      packagesNames.push(yamlPkg)
      const dir = dirname(yamlPkg)
      packageDirs.add(dir)
    }

    // Add package.json files only if no package.yaml exists in the same directory
    for (const jsonPkg of jsonPackages) {
      const dir = dirname(jsonPkg)
      if (!packageDirs.has(dir)) {
        packagesNames.push(jsonPkg)
      }
    }
  }
  else {
    packagesNames = await glob('package.{yaml,json}', { cwd })
  }

  packagesNames = packagesNames.sort((a, b) => a.localeCompare(b))

  if (options.ignoreOtherWorkspaces) {
    packagesNames = (await Promise.all(
      packagesNames.map(async (packagePath) => {
        if (!packagePath.includes('/'))
          return [packagePath]

        const absolute = join(cwd, packagePath)
        const gitDir = findUp('.git', { cwd: absolute, last: cwd })
        if (gitDir && dirname(gitDir) !== cwd)
          return []
        const pnpmWorkspace = findUp('pnpm-workspace.yaml', { cwd: absolute, last: cwd })
        if (pnpmWorkspace && dirname(pnpmWorkspace) !== cwd)
          return []
        const yarnWorkspace = findUp('.yarnrc.yml', { cwd: absolute, last: cwd })
        if (yarnWorkspace && dirname(yarnWorkspace) !== cwd)
          return []
        const bunLock = findUp('bun.lockb', { cwd: absolute, last: cwd })
          || findUp('bun.lock', { cwd: absolute, last: cwd })
        if (bunLock && dirname(bunLock) !== cwd)
          return []
        return [packagePath]
      }),
    )).flat()
  }

  if (existsSync(join(cwd, 'pnpm-workspace.yaml'))) {
    packagesNames.unshift('pnpm-workspace.yaml')
  }

  if (existsSync(join(cwd, '.yarnrc.yml'))) {
    packagesNames.unshift('.yarnrc.yml')
  }

  if (isGitHubActionsEnabled(options)) {
    const actionFiles = await loadGitHubActionsFiles(options)
    packagesNames.push(...actionFiles)
  }

  const packages = (await Promise.all(
    packagesNames.map(
      relative => loadPackage(relative, options, filter),
    ),
  )).flat()

  return packages
}
