import fs from "node:fs/promises"
import path from "node:path"

import type * as TF from "type-fest"

const symlink = async (pathTarget: string, pathSymlink: string) => {
  const stats = await fs.lstat(pathSymlink).catch(() => null)
  if (!stats) return

  if (stats.isSymbolicLink()) {
    const symlinkRealpath = await fs.realpath(pathSymlink).catch(() => null)
    if (pathTarget === symlinkRealpath) return
  }

  if (stats.isDirectory()) await fs.rm(pathSymlink, { recursive: true })
  else await fs.unlink(pathSymlink)

  await fs.symlink(pathTarget, pathSymlink)
}

export const checkIsMonorepo = async (pathPkg: string) => {
  const pkgJson = await import(path.join(pathPkg, `package.json`))
  return Boolean(pkgJson.workspaces)
}

export const linkPackages = async (cwd: string, packagesToLink: string[]) => {
  const linkedPaths: string[] = []

  const linkPackage = async (pathPkg: string) => {
    const pkgJson: TF.PackageJson = await import(path.join(pathPkg, `package.json`)).catch(
      () => null,
    )
    if (!pkgJson) return

    const [orgName, pkgName] = pkgJson.name!.split(`/`)

    const pathSymlink = path.join(cwd, `node_modules`, orgName, pkgName)
    await symlink(pathPkg, pathSymlink)
    linkedPaths.push(pathPkg)
  }

  await Promise.all(
    packagesToLink.map(async (relativePath) => {
      const pathPkg = path.join(cwd, relativePath)

      // relative path might point to a non-existing directory for a particular developer; skip linking that package in that case
      const isMonorepo = await checkIsMonorepo(pathPkg).catch(() => null)
      if (!isMonorepo) return linkPackage(pathPkg)

      const files = await fs.readdir(relativePath, { withFileTypes: true }).catch(() => null)
      if (!files) return

      const directories = files.filter((v) => v.isDirectory()).map((v) => v.name)
      await Promise.all(directories.map((dir) => linkPackage(path.join(cwd, relativePath, dir))))
    }),
  )

  console.info(`Successfully linked local packages!`)

  return linkedPaths
}

const DEFAULT_SINGLETONS = [`@types/react`, `@types/react-dom`]

const readPkgVersion = async (pathPkg: string): Promise<string | null> => {
  const pkgJson: TF.PackageJson = await import(path.join(pathPkg, `package.json`)).catch(() => null)
  return pkgJson?.version ?? null
}

const majorOf = (version: string) => version.split(`.`)[0]

/**
 * Collapse React type singletons to one physical copy for locally-linked graphs.
 *
 * tsgo resolves symlinked deps at their realpath and ignores the consumer's tsconfig
 * `paths`, so multiple physical `@types/react` copies become distinct type identities.
 * Pointing every copy at the workspace-root copy replicates a hoisted monorepo (one type
 * identity). Best-effort and idempotent: a plain `bun install` re-materializes real dirs and
 * the next `prepare` re-establishes the symlinks.
 */
export const dedupeSingletons = async (
  cwd: string,
  packagesToLink: string[],
  linkedPaths: string[],
  singletons: string[] = DEFAULT_SINGLETONS,
) => {
  // siblings share cwd's parent; the formula reduces to path.dirname(cwd) for `../pkg` entries
  const roots = new Set(packagesToLink.map((rel) => path.resolve(cwd, rel, `..`)))
  const workspaceRoot = roots.size === 1 ? [...roots][0]! : path.dirname(cwd)

  for (const pkg of singletons) {
    const canonical = path.join(workspaceRoot, `node_modules`, pkg)
    const canonicalVersion = await readPkgVersion(canonical)
    if (!canonicalVersion) {
      console.warn(`Dedupe ${pkg}: canonical copy not found at ${canonical}; skipping`)
      continue
    }
    const canonicalReal = await fs.realpath(canonical).catch(() => canonical)

    const targets = [
      path.join(cwd, `node_modules`, pkg),
      ...linkedPaths.map((pathPkg) => path.join(pathPkg, `node_modules`, pkg)),
    ]

    let relinked = 0
    let skipped = 0

    for (const target of targets) {
      if (path.resolve(target) === path.resolve(canonical)) continue

      const stats = await fs.lstat(target).catch(() => null)
      if (!stats) continue

      if (stats.isSymbolicLink()) {
        const real = await fs.realpath(target).catch(() => null)
        if (real === canonicalReal) continue // already deduped
      }

      const targetVersion = await readPkgVersion(target)
      if (!targetVersion) continue

      if (majorOf(targetVersion) !== majorOf(canonicalVersion)) {
        skipped++
        console.warn(
          `Dedupe ${pkg}: skipping ${target} (v${targetVersion}); major differs from canonical v${canonicalVersion}`,
        )
        continue
      }

      await symlink(canonicalReal, target)
      relinked++
    }

    console.info(
      `Dedupe ${pkg}@${canonicalVersion}: ${relinked} copy(ies) relinked, ${skipped} skipped`,
    )
  }
}
