/**
 * @fileoverview ProjectScanner - Scans project directory and collects context
 * @module morph-spec/scanner/project-scanner
 */

import { readFile, access } from 'fs/promises';
import { join, dirname, relative } from 'path';
import { readdir, stat } from 'fs/promises';
import { execSync } from 'child_process';
import { glob } from 'glob';

/**
 * @typedef {import('../types/index.js').ProjectContext} ProjectContext
 * @typedef {import('../types/index.js').PackageJson} PackageJson
 * @typedef {import('../types/index.js').DirectoryStructure} DirectoryStructure
 * @typedef {import('../types/index.js').InfraFiles} InfraFiles
 */

/**
 * ProjectScanner - Scans project directory and collects context for LLM analysis
 * @class
 */
export class ProjectScanner {
  /**
   * Scan the project directory and collect complete context
   * @param {string} cwd - Current working directory (absolute path)
   * @returns {Promise<ProjectContext>}
   */
  async scan(cwd) {
    const [
      packageJson,
      csprojFiles,
      solutionFile,
      readme,
      claudeMd,
      structure,
      infraFiles,
      gitRemote
    ] = await Promise.all([
      this.readPackageJson(cwd),
      this.findCsprojFiles(cwd),
      this.findSolutionFile(cwd),
      this.readFileIfExists(join(cwd, 'README.md')),
      this.readFileIfExists(join(cwd, 'CLAUDE.md')),
      this.detectDirectoryStructure(cwd),
      this.findInfraFiles(cwd),
      this.getGitRemote(cwd)
    ]);

    return {
      cwd,
      packageJson,
      csprojFiles,
      solutionFile,
      readme,
      claudeMd,
      structure,
      infraFiles,
      gitRemote,
      scannedAt: new Date()
    };
  }

  /**
   * Read and parse package.json
   * @param {string} cwd - Current working directory
   * @returns {Promise<PackageJson|null>}
   */
  async readPackageJson(cwd) {
    try {
      const packageJsonPath = join(cwd, 'package.json');
      const content = await readFile(packageJsonPath, 'utf-8');
      return JSON.parse(content);
    } catch (error) {
      // No package.json or parse error
      return null;
    }
  }

  /**
   * Find all .csproj files recursively
   * @param {string} cwd - Current working directory
   * @returns {Promise<string[]>}
   */
  async findCsprojFiles(cwd) {
    try {
      const pattern = '**/*.csproj';
      const options = {
        cwd,
        ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**', '**/.git/**'],
        absolute: false
      };

      const files = await glob(pattern, options);
      return files;
    } catch (error) {
      return [];
    }
  }

  /**
   * Find solution file (.sln)
   * @param {string} cwd - Current working directory
   * @returns {Promise<string|null>}
   */
  async findSolutionFile(cwd) {
    try {
      const pattern = '**/*.sln';
      const options = {
        cwd,
        ignore: ['**/node_modules/**', '**/.git/**'],
        absolute: false
      };

      const files = await glob(pattern, options);
      return files.length > 0 ? files[0] : null;
    } catch (error) {
      return null;
    }
  }

  /**
   * Detect directory structure and patterns
   * @param {string} cwd - Current working directory
   * @returns {Promise<DirectoryStructure>}
   */
  async detectDirectoryStructure(cwd) {
    try {
      const entries = await readdir(cwd, { withFileTypes: true });
      const dirs = entries
        .filter(entry => entry.isDirectory())
        .map(entry => entry.name)
        .filter(name => !name.startsWith('.') && name !== 'node_modules');

      const hasSrc = dirs.includes('src');
      const hasBackend = dirs.some(d => ['backend', 'server', 'api'].includes(d.toLowerCase()));
      const hasFrontend = dirs.some(d => ['frontend', 'client', 'web', 'app'].includes(d.toLowerCase()));
      const hasTests = dirs.some(d => ['test', 'tests', '__tests__'].includes(d.toLowerCase()));

      // Detect pattern
      let pattern = 'single-project';
      if (hasBackend && hasFrontend) {
        pattern = 'multi-stack';
      } else if (dirs.length > 5 && dirs.some(d => d.includes('packages') || d.includes('apps'))) {
        pattern = 'monorepo';
      }

      return {
        hasSrc,
        hasBackend,
        hasFrontend,
        hasTests,
        topLevelDirs: dirs,
        pattern
      };
    } catch (error) {
      return {
        hasSrc: false,
        hasBackend: false,
        hasFrontend: false,
        hasTests: false,
        topLevelDirs: [],
        pattern: 'single-project'
      };
    }
  }

  /**
   * Find infrastructure files (Docker, Bicep, pipelines)
   * @param {string} cwd - Current working directory
   * @returns {Promise<InfraFiles>}
   */
  async findInfraFiles(cwd) {
    try {
      const [dockerfiles, dockerComposeFiles, bicepFiles, pipelines] = await Promise.all([
        glob('**/Dockerfile*', { cwd, ignore: ['**/node_modules/**', '**/.git/**'], absolute: false }),
        glob('**/docker-compose*.yml', { cwd, ignore: ['**/node_modules/**', '**/.git/**'], absolute: false }),
        glob('**/*.bicep', { cwd, ignore: ['**/node_modules/**', '**/.git/**'], absolute: false }),
        glob('**/{azure-pipelines.yml,.github/workflows/*.yml,.gitlab-ci.yml}', {
          cwd,
          ignore: ['**/node_modules/**'],
          absolute: false
        })
      ]);

      return {
        dockerfiles,
        dockerComposeFiles,
        bicepFiles,
        pipelines,
        hasAzure: bicepFiles.length > 0 || pipelines.some(p => p.includes('azure')),
        hasDocker: dockerfiles.length > 0 || dockerComposeFiles.length > 0,
        hasDevOps: pipelines.length > 0
      };
    } catch (error) {
      return {
        dockerfiles: [],
        dockerComposeFiles: [],
        bicepFiles: [],
        pipelines: [],
        hasAzure: false,
        hasDocker: false,
        hasDevOps: false
      };
    }
  }

  /**
   * Get git remote URL
   * @param {string} cwd - Current working directory
   * @returns {Promise<string|null>}
   */
  async getGitRemote(cwd) {
    try {
      const remote = execSync('git remote get-url origin', {
        cwd,
        encoding: 'utf-8',
        stdio: ['pipe', 'pipe', 'ignore']
      }).trim();

      return remote || null;
    } catch (error) {
      // Not a git repo or no remote
      return null;
    }
  }

  /**
   * Helper: Read file if it exists
   * @param {string} filepath - Absolute file path
   * @returns {Promise<string|null>}
   */
  async readFileIfExists(filepath) {
    try {
      await access(filepath);
      const content = await readFile(filepath, 'utf-8');
      return content;
    } catch (error) {
      return null;
    }
  }
}
