import path from 'node:path';
import type { AdapterCommand, FrameworkAdapter, FrameworkKind } from './types.js';
import { readComposerJson, vendorBinExists, type ComposerJson } from './composer.js';

/**
 * Shared behaviour for Composer-based PHP frameworks (Laravel, CodeIgniter).
 * Subclasses only need to override the bits that actually differ: the CLI
 * used for scaffolding/migrations (artisan vs spark), the dev-server "ready"
 * line, and which test runner is used.
 */
export abstract class PhpFrameworkAdapter implements FrameworkAdapter {
  abstract readonly kind: FrameworkKind;
  abstract readonly label: string;
  readonly packageManager = 'composer';
  protected readonly composerJson: ComposerJson | null;

  constructor(readonly root: string) {
    this.composerJson = readComposerJson(root);
  }

  installCommand(): AdapterCommand {
    return { command: 'composer', args: ['install'], cwd: this.root };
  }

  buildCommand(): AdapterCommand | null {
    return { command: 'composer', args: ['install', '--no-dev', '--optimize-autoloader'], cwd: this.root };
  }

  testCommand(extraArgs: string[] = []): AdapterCommand | null {
    if (vendorBinExists(this.root, 'pest')) {
      return { command: this.vendorBin('pest'), args: extraArgs, cwd: this.root };
    }
    if (vendorBinExists(this.root, 'phpunit')) {
      return { command: this.vendorBin('phpunit'), args: extraArgs, cwd: this.root };
    }
    return null;
  }

  lintCommand(fix = false): AdapterCommand | null {
    if (vendorBinExists(this.root, 'pint')) {
      return { command: this.vendorBin('pint'), args: fix ? [] : ['--test'], cwd: this.root };
    }
    if (vendorBinExists(this.root, 'phpcs')) {
      return { command: this.vendorBin(fix ? 'phpcbf' : 'phpcs'), args: [], cwd: this.root };
    }
    return null;
  }

  protected vendorBin(bin: string): string {
    const suffix = process.platform === 'win32' ? '.bat' : '';
    return path.join(this.root, 'vendor', 'bin', `${bin}${suffix}`);
  }

  abstract devCommand(): AdapterCommand;
  abstract readyPattern(): RegExp;
  abstract describe(): Record<string, unknown>;
}
