import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { dirname, isAbsolute, join, resolve } from 'node:path';

export type ProjectScope = 'single-app' | 'workspace-app';

export interface WorkspaceInfo {
  cwd: string;
  workspaceRoot: string;
  appRoot: string;
  projectScope: ProjectScope;
  appCandidates: string[];
  requiresProjectSelection: boolean;
}

interface PackageJson {
  workspaces?: string[] | { packages?: string[] };
  dependencies?: Record<string, string>;
  devDependencies?: Record<string, string>;
}

function readPackageJson(dir: string): PackageJson | null {
  const path = join(dir, 'package.json');
  if (!existsSync(path)) return null;
  try {
    return JSON.parse(readFileSync(path, 'utf-8')) as PackageJson;
  } catch {
    return null;
  }
}

function hasWorkspaceMarker(dir: string): boolean {
  if (
    existsSync(join(dir, 'pnpm-workspace.yaml')) ||
    existsSync(join(dir, 'turbo.json')) ||
    existsSync(join(dir, 'nx.json'))
  ) {
    return true;
  }
  const pkg = readPackageJson(dir);
  return Boolean(pkg?.workspaces);
}

function findWorkspaceRoot(startDir: string): string | null {
  let current = resolve(startDir);
  while (true) {
    if (hasWorkspaceMarker(current)) return current;
    const parent = dirname(current);
    if (parent === current) return null;
    current = parent;
  }
}

function looksLikeApp(
  dir: string,
  options: { allowSourceDirs?: boolean; allowPackageDeps?: boolean } = {},
): boolean {
  const allowSourceDirs = options.allowSourceDirs ?? true;
  const allowPackageDeps = options.allowPackageDeps ?? true;
  const pkg = readPackageJson(dir);
  const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
  const hasFrontendDependency = Boolean(
    deps.react ||
      deps['react-dom'] ||
      deps.next ||
      deps.vue ||
      deps.svelte ||
      deps['@angular/core'] ||
      deps.astro ||
      deps.nuxt,
  );
  const hasServerOnlyDependency = Boolean(
    deps.hono || deps.express || deps.fastify || deps.koa || deps['@hapi/hapi'],
  );
  if (
    existsSync(join(dir, 'next.config.js')) ||
    existsSync(join(dir, 'next.config.ts')) ||
    existsSync(join(dir, 'next.config.mjs')) ||
    existsSync(join(dir, 'vite.config.ts')) ||
    existsSync(join(dir, 'vite.config.js')) ||
    existsSync(join(dir, 'angular.json')) ||
    existsSync(join(dir, 'svelte.config.js')) ||
    existsSync(join(dir, 'svelte.config.ts')) ||
    existsSync(join(dir, 'astro.config.mjs'))
  ) {
    return true;
  }

  if (
    allowSourceDirs &&
    (existsSync(join(dir, 'src')) || existsSync(join(dir, 'app')) || existsSync(join(dir, 'pages')))
  ) {
    if (hasFrontendDependency) return true;
    if (hasServerOnlyDependency) return false;
    return true;
  }

  if (!allowPackageDeps) return false;

  return hasFrontendDependency;
}

function listWorkspaceApps(workspaceRoot: string): string[] {
  const candidates: string[] = [];
  for (const base of ['apps', 'packages']) {
    const baseDir = join(workspaceRoot, base);
    if (!existsSync(baseDir)) continue;
    for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
      if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
      const candidate = join(baseDir, entry.name);
      if (
        looksLikeApp(candidate, {
          allowSourceDirs: base === 'apps',
          allowPackageDeps: base === 'apps',
        })
      ) {
        candidates.push(`${base}/${entry.name}`);
      }
    }
  }
  return candidates.sort();
}

export function listWorkspaceAppCandidates(workspaceRoot: string): string[] {
  return listWorkspaceApps(resolve(workspaceRoot));
}

export function resolveWorkspaceInfo(cwd: string, projectArg?: string): WorkspaceInfo {
  const absoluteCwd = resolve(cwd);
  const appRoot = projectArg
    ? resolve(
        isAbsolute(projectArg)
          ? projectArg
          : join(findWorkspaceRoot(absoluteCwd) ?? absoluteCwd, projectArg),
      )
    : absoluteCwd;
  const workspaceRoot =
    projectArg && isAbsolute(projectArg)
      ? (findWorkspaceRoot(appRoot) ?? appRoot)
      : (findWorkspaceRoot(absoluteCwd) ?? absoluteCwd);
  const appCandidates = listWorkspaceApps(workspaceRoot);
  const projectScope: ProjectScope =
    workspaceRoot !== appRoot || appCandidates.length > 0 ? 'workspace-app' : 'single-app';
  const requiresProjectSelection =
    !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 0;

  return {
    cwd: absoluteCwd,
    workspaceRoot,
    appRoot,
    projectScope,
    appCandidates,
    requiresProjectSelection,
  };
}
