import fs from 'node:fs';
import path from 'node:path';
import type { AdapterCommand, FrameworkAdapter } from './types.js';
import {
  detectJsPackageManager,
  hasDependency,
  installCommandFor,
  readPackageJson,
  runScriptCommandFor,
  type JsPackageManager,
  type PackageJson,
} from './packageManager.js';

function fileExists(root: string, names: string[]): boolean {
  return names.some((name) => fs.existsSync(path.join(root, name)));
}

/**
 * Vite + Vue SFC projects (non-Nuxt).
 */
export class VueAdapter implements FrameworkAdapter {
  readonly kind = 'vue' as const;
  readonly label: string;
  readonly packageManager: JsPackageManager;
  private readonly pkg: PackageJson | null;

  constructor(readonly root: string) {
    this.pkg = readPackageJson(root);
    this.packageManager = detectJsPackageManager(root);
    this.label = 'Vue';
  }

  installCommand(): AdapterCommand {
    const { command, args } = installCommandFor(this.packageManager);
    return { command, args, cwd: this.root };
  }

  devCommand(): AdapterCommand {
    return this.requireScript(['dev', 'start']);
  }

  buildCommand(): AdapterCommand | null {
    return this.scriptOrNull(['build']);
  }

  testCommand(extraArgs: string[] = []): AdapterCommand | null {
    return this.scriptOrNull(['test'], extraArgs);
  }

  lintCommand(fix = false): AdapterCommand | null {
    return this.scriptOrNull(fix ? ['format', 'lint:fix'] : ['lint']);
  }

  readyPattern(): RegExp {
    return /Local:\s+(https?:\/\/\S+)/i;
  }

  describe(): Record<string, unknown> {
    return {
      kind: this.kind,
      label: this.label,
      root: this.root,
      packageManager: this.packageManager,
      vueVersion: this.pkg?.dependencies?.vue ?? this.pkg?.devDependencies?.vue ?? null,
      typescript: hasDependency(this.pkg, 'typescript') || fileExists(this.root, ['tsconfig.json']),
      vite: fileExists(this.root, ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']),
      scripts: this.pkg?.scripts ?? {},
    };
  }

  private requireScript(candidates: string[]): AdapterCommand {
    const cmd = this.scriptOrNull(candidates);
    if (!cmd) {
      throw new Error(`No "${candidates.join('"/"')}" script found in package.json at "${this.root}".`);
    }
    return cmd;
  }

  private scriptOrNull(candidates: string[], extraArgs: string[] = []): AdapterCommand | null {
    const script = candidates.find((name) => this.pkg?.scripts?.[name]);
    if (!script) return null;
    const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs);
    return { command, args, cwd: this.root };
  }
}

/**
 * Nuxt (Vue meta-framework) projects.
 */
export class NuxtAdapter implements FrameworkAdapter {
  readonly kind = 'nuxt' as const;
  readonly label = 'Nuxt';
  readonly packageManager: JsPackageManager;
  private readonly pkg: PackageJson | null;

  constructor(readonly root: string) {
    this.pkg = readPackageJson(root);
    this.packageManager = detectJsPackageManager(root);
  }

  installCommand(): AdapterCommand {
    const { command, args } = installCommandFor(this.packageManager);
    return { command, args, cwd: this.root };
  }

  devCommand(): AdapterCommand {
    return this.requireScript(['dev', 'start']);
  }

  buildCommand(): AdapterCommand | null {
    return this.scriptOrNull(['build', 'generate']);
  }

  testCommand(extraArgs: string[] = []): AdapterCommand | null {
    return this.scriptOrNull(['test'], extraArgs);
  }

  lintCommand(fix = false): AdapterCommand | null {
    return this.scriptOrNull(fix ? ['format', 'lint:fix'] : ['lint']);
  }

  readyPattern(): RegExp {
    return /Local:\s+(https?:\/\/\S+)|Nuxt\s+.+ready/i;
  }

  describe(): Record<string, unknown> {
    return {
      kind: this.kind,
      label: this.label,
      root: this.root,
      packageManager: this.packageManager,
      nuxtVersion: this.pkg?.dependencies?.nuxt ?? this.pkg?.devDependencies?.nuxt ?? null,
      typescript: hasDependency(this.pkg, 'typescript') || fileExists(this.root, ['tsconfig.json']),
      scripts: this.pkg?.scripts ?? {},
    };
  }

  private requireScript(candidates: string[]): AdapterCommand {
    const cmd = this.scriptOrNull(candidates);
    if (!cmd) {
      throw new Error(`No "${candidates.join('"/"')}" script found in package.json at "${this.root}".`);
    }
    return cmd;
  }

  private scriptOrNull(candidates: string[], extraArgs: string[] = []): AdapterCommand | null {
    const script = candidates.find((name) => this.pkg?.scripts?.[name]);
    if (!script) return null;
    const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs);
    return { command, args, cwd: this.root };
  }
}
