/**
 * validate.ts — Input validation for the create-plan-development CLI.
 *
 * Stage 1: Zod structural validation.
 * Stage 2: Business validation (app directories exist, modules found).
 */
import path from 'node:path';
import { readdir, stat } from 'node:fs/promises';
import { directoryExists, fileExists } from '../../../../lib/fs.js';
import { PlanDevelopmentInputSchema } from './types.js';
import type { PlanDevelopmentInput, ValidationResult } from './types.js';

/**
 * Validate the raw input and discover the BA tree structure.
 * Returns the parsed spec + discovered modules per app.
 */
export async function validate(raw: unknown): Promise<ValidationResult> {
  const errors: string[] = [];
  const warnings: string[] = [];

  // --- Stage 1: Zod ---
  const parsed = PlanDevelopmentInputSchema.safeParse(raw);
  if (!parsed.success) {
    const zodErrors = parsed.error.issues.map(
      (i) => `${i.path.join('.')}: ${i.message}`,
    );
    return { valid: false, errors: zodErrors, warnings };
  }

  const spec = parsed.data;

  // --- Stage 2: Business validation ---

  // Check baRoot exists
  const baRootExists = await directoryExists(spec.baRoot);
  if (!baRootExists) {
    errors.push(`BA root directory not found: ${spec.baRoot}`);
    return { valid: false, errors, warnings };
  }

  // Check each app directory
  const discoveredModules = new Map<string, string[]>();
  let foundApps = 0;

  for (const appCode of spec.apps) {
    const appDir = path.join(spec.baRoot, appCode);
    const exists = await directoryExists(appDir);

    if (!exists) {
      errors.push(`Application directory not found: ${appDir} (app code: ${appCode})`);
      continue;
    }

    foundApps++;

    // Discover module directories (immediate UPPERCASE children with index.md)
    const moduleCodes = await discoverModules(appDir);

    if (moduleCodes.length === 0) {
      warnings.push(`Application ${appCode} has no modules`);
    }

    discoveredModules.set(appCode, moduleCodes);
  }

  if (foundApps === 0) {
    errors.push('No application directories found in the BA tree');
    return { valid: false, errors, warnings };
  }

  // At least one module total
  const totalModules = [...discoveredModules.values()].reduce(
    (sum, mods) => sum + mods.length,
    0,
  );
  if (totalModules === 0) {
    errors.push('No modules found across the selected applications');
    return { valid: false, errors, warnings };
  }

  return { valid: true, spec, errors, warnings, discoveredModules };
}

/**
 * Discover module directories under an application folder.
 * A module is an UPPERCASE directory containing an index.md.
 */
async function discoverModules(appDir: string): Promise<string[]> {
  const modules: string[] = [];

  let entries;
  try {
    entries = await readdir(appDir, { withFileTypes: true });
  } catch {
    return modules;
  }

  for (const entry of entries) {
    // Skip non-directories and special folders
    if (!entry.isDirectory()) continue;
    if (entry.name.startsWith('_')) continue;
    if (entry.name.startsWith('.')) continue;

    // Must be UPPERCASE (module convention)
    if (!/^[A-Z][A-Z0-9_-]*$/.test(entry.name)) continue;

    // Must contain index.md
    const indexPath = path.join(appDir, entry.name, 'index.md');
    const hasIndex = await fileExists(indexPath);
    if (hasIndex) {
      modules.push(entry.name);
    }
  }

  // Sort for deterministic output
  modules.sort();
  return modules;
}
