/**
 * lib/detector.ts — SmartStack project structure detection.
 * Ported from SmartStack.cli/src/mcp/lib/detector.ts — MCP logger removed,
 * findSmartStackStructure kept as the primary API for dev CLIs.
 *
 * Given an absolute path to a SmartStack project, this module locates:
 *   - The Clean Architecture layers (Domain, Application, Infrastructure, Api)
 *   - The web/frontend folder (searches common conventions)
 *   - The EF Core migrations folder
 *
 * Configuration overrides can be declared in `.smartstack/config.json` under a
 * `paths` key. Auto-detection fills the gaps.
 */
import path from 'node:path';
import { readdir } from 'node:fs/promises';
import { directoryExists, fileExists, readText } from './fs.js';
import { findCsprojFiles, hasEfCore, findDbContextName, getTargetFramework } from './dotnet.js';
import { isGitRepo, getCurrentBranch } from './git.js';

export interface SmartStackStructure {
  root: string;
  domain?: string;
  application?: string;
  infrastructure?: string;
  /** Primary API (prefers *.Api.Core over *.Api) */
  api?: string;
  /** *.Api.Core project (core controllers) if present */
  apiCore?: string;
  /** *.Api project without Core (extension controllers) if present */
  apiExtensions?: string;
  web?: string;
  migrations?: string;
}

export interface ProjectInfo {
  name: string;
  version: string;
  isGitRepo: boolean;
  currentBranch?: string;
  hasDotNet: boolean;
  hasEfCore: boolean;
  hasReact: boolean;
  csprojFiles: string[];
  dbContextName?: string;
  targetFramework?: string;
}

/**
 * Search for a web/frontend project folder under the SmartStack root.
 * Priority order matches common conventions (never hardcoded to "smartstack-web").
 */
async function findWebProjectFolder(projectPath: string): Promise<string | null> {
  // 1) web/<name>/package.json (any subfolder of web/)
  const webDir = path.join(projectPath, 'web');
  if (await directoryExists(webDir)) {
    try {
      const entries = await readdir(webDir, { withFileTypes: true });
      for (const entry of entries) {
        if (entry.isDirectory()) {
          const folder = path.join(webDir, entry.name);
          if (await fileExists(path.join(folder, 'package.json'))) return folder;
        }
      }
    } catch {
      // Unreadable — fall through
    }
  }

  // 2) web/package.json (monorepo style)
  if (await fileExists(path.join(webDir, 'package.json'))) return webDir;

  // 3) client/ or frontend/ or ui/
  for (const folderName of ['client', 'frontend', 'ui']) {
    const altPath = path.join(projectPath, folderName);
    if (await fileExists(path.join(altPath, 'package.json'))) return altPath;
  }

  // 4) src/web/
  const srcWeb = path.join(projectPath, 'src', 'web');
  if (await fileExists(path.join(srcWeb, 'package.json'))) return srcWeb;

  return null;
}

/**
 * Detect basic metadata about a SmartStack project (git, .NET, React, version).
 */
export async function detectProject(projectPath: string): Promise<ProjectInfo> {
  const info: ProjectInfo = {
    name: path.basename(projectPath),
    version: '0.0.0',
    isGitRepo: false,
    hasDotNet: false,
    hasEfCore: false,
    hasReact: false,
    csprojFiles: [],
  };

  if (!(await directoryExists(projectPath))) return info;

  info.isGitRepo = await isGitRepo(projectPath);
  if (info.isGitRepo) {
    info.currentBranch = (await getCurrentBranch(projectPath)) || undefined;
  }

  info.csprojFiles = await findCsprojFiles(projectPath);
  info.hasDotNet = info.csprojFiles.length > 0;

  if (info.hasDotNet) {
    for (const csproj of info.csprojFiles) {
      if (await hasEfCore(csproj)) {
        info.hasEfCore = true;
        info.dbContextName = (await findDbContextName(path.dirname(csproj))) || undefined;
        break;
      }
    }
  }

  const webFolder = await findWebProjectFolder(projectPath);
  if (webFolder) {
    const packageJsonPath = path.join(webFolder, 'package.json');
    if (await fileExists(packageJsonPath)) {
      try {
        const pkg = JSON.parse(await readText(packageJsonPath)) as {
          dependencies?: Record<string, string>;
          version?: string;
        };
        info.hasReact = !!pkg.dependencies?.react;
        info.version = pkg.version || info.version;
      } catch {
        // parse error — ignore
      }
    }
  }

  if (info.csprojFiles.length > 0) {
    const mainCsproj =
      info.csprojFiles.find((f) => /\.Api\.csproj$/i.test(f)) ||
      info.csprojFiles.find((f) => /Api\.csproj$/i.test(f)) ||
      info.csprojFiles[0];
    const tf = await getTargetFramework(mainCsproj);
    if (tf) info.targetFramework = tf;
  }

  return info;
}

/**
 * Find the physical folders that make up a SmartStack project.
 * Reads optional overrides from `.smartstack/config.json` → `paths`, then
 * auto-detects from .csproj files to fill the gaps.
 */
export async function findSmartStackStructure(projectPath: string): Promise<SmartStackStructure> {
  const structure: SmartStackStructure = { root: projectPath };

  // Config override
  const configPath = path.join(projectPath, '.smartstack', 'config.json');
  if (await fileExists(configPath)) {
    try {
      const cfg = JSON.parse(await readText(configPath)) as {
        paths?: Partial<Record<'api' | 'domain' | 'application' | 'infrastructure' | 'web', string>>;
      };
      const paths = cfg.paths;
      if (paths) {
        if (paths.api) structure.api = path.resolve(projectPath, paths.api);
        if (paths.domain) structure.domain = path.resolve(projectPath, paths.domain);
        if (paths.application) structure.application = path.resolve(projectPath, paths.application);
        if (paths.infrastructure)
          structure.infrastructure = path.resolve(projectPath, paths.infrastructure);
        if (paths.web) structure.web = path.resolve(projectPath, paths.web);
      }
    } catch {
      // parse error — fall through to auto-detect
    }
  }

  // Auto-detect from .csproj files
  const csprojFiles = await findCsprojFiles(projectPath);

  for (const csproj of csprojFiles) {
    const projectName = path.basename(csproj, '.csproj').toLowerCase();
    const projectDir = path.dirname(csproj);

    if (!structure.domain && projectName.includes('domain')) {
      structure.domain = projectDir;
    } else if (!structure.application && projectName.includes('application')) {
      structure.application = projectDir;
    } else if (!structure.infrastructure && projectName.includes('infrastructure')) {
      structure.infrastructure = projectDir;
    } else if (projectName.includes('api.core')) {
      if (!structure.apiCore) structure.apiCore = projectDir;
      if (!structure.api) structure.api = projectDir;
    } else if (projectName.includes('api') && !projectName.includes('api.core')) {
      if (!structure.apiExtensions) structure.apiExtensions = projectDir;
      if (!structure.api) structure.api = projectDir;
    }
  }

  // Migrations folder
  if (structure.infrastructure) {
    const migrationsPath = path.join(structure.infrastructure, 'Persistence', 'Migrations');
    if (await directoryExists(migrationsPath)) structure.migrations = migrationsPath;
  }

  // Web folder
  if (!structure.web) {
    const webFolder = await findWebProjectFolder(projectPath);
    if (webFolder) structure.web = webFolder;
  }

  return structure;
}

/**
 * Find all entity files in the domain layer (classes/records with an Id property).
 */
export async function findEntityFiles(domainPath: string): Promise<string[]> {
  const { findFiles } = await import('./fs.js');
  const entityFiles = await findFiles('**/*.cs', { cwd: domainPath });
  const entities: string[] = [];
  for (const file of entityFiles) {
    const content = await readText(file);
    if (
      content.match(/public\s+(?:class|record)\s+\w+/) &&
      content.match(/public\s+(?:int|long|Guid|string)\s+Id\s*\{/)
    ) {
      entities.push(file);
    }
  }
  return entities;
}

/** Find all service interfaces in the application layer. */
export async function findServiceInterfaces(applicationPath: string): Promise<string[]> {
  const { findFiles } = await import('./fs.js');
  return findFiles('**/I*Service.cs', { cwd: applicationPath });
}

/** Find all controller files in the API layer. */
export async function findControllerFiles(apiPath: string): Promise<string[]> {
  const { findFiles } = await import('./fs.js');
  return findFiles('**/*Controller.cs', { cwd: apiPath });
}
