/**
 * Phase 5 — Stage 3: Project Materialization
 *
 * Copies the Phase 4 generated project directory into phase5/project/.
 * Adds runtime wiring:
 * - Contract schema validation from agentics-contracts
 * - Health check endpoint
 * - Telemetry integration
 */
import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join, relative } from 'node:path';
import { createSpan, endSpan, emitSpan } from '../../phase2/telemetry.js';
// ============================================================================
// Constants
// ============================================================================
const DIR_MODE = 0o700;
const FILE_MODE = 0o600;
const GENERATED_HEADER = '// Generated by Phase 5 pipeline — do not edit manually\n';
// ============================================================================
// Helper: Recursively count files in a directory
// ============================================================================
/**
 * Walk a directory tree and return the total number of files found.
 * Returns 0 if the directory does not exist.
 */
function countFilesRecursive(dir) {
    if (!existsSync(dir)) {
        return 0;
    }
    let count = 0;
    const entries = readdirSync(dir, { withFileTypes: true });
    for (const entry of entries) {
        if (entry.isFile()) {
            count += 1;
        }
        else if (entry.isDirectory()) {
            count += countFilesRecursive(join(dir, entry.name));
        }
    }
    return count;
}
// ============================================================================
// Helper: Collect all file paths in a directory tree
// ============================================================================
/**
 * Recursively collect all file paths under a directory.
 * Returns absolute paths.
 */
function collectFilePaths(dir) {
    if (!existsSync(dir)) {
        return [];
    }
    const results = [];
    const entries = readdirSync(dir, { withFileTypes: true });
    for (const entry of entries) {
        const fullPath = join(dir, entry.name);
        if (entry.isFile()) {
            results.push(fullPath);
        }
        else if (entry.isDirectory()) {
            results.push(...collectFilePaths(fullPath));
        }
    }
    return results;
}
// ============================================================================
// Helper: Generate contract validation module
// ============================================================================
/**
 * Generate a contract-validation.ts file that validates ERP deployment
 * artifacts against the agentics-contracts schema.
 *
 * If the agentics-contracts repo is cached, reads the ERPDeploymentArtifact
 * schema and embeds it. Otherwise, generates a stub with runtime schema
 * loading support.
 */
function generateContractValidation(contractsRepoPath) {
    let schemaJson = 'null';
    let schemaSource = 'runtime';
    if (contractsRepoPath) {
        const schemaPath = join(contractsRepoPath, 'schemas', 'erp-deployment-artifact.json');
        if (existsSync(schemaPath)) {
            try {
                const raw = readFileSync(schemaPath, 'utf-8');
                // Validate that it is parseable JSON
                JSON.parse(raw);
                schemaJson = raw.trim();
                schemaSource = 'embedded';
            }
            catch {
                // Malformed schema — fall back to runtime loading
            }
        }
    }
    return [
        GENERATED_HEADER,
        `/**`,
        ` * Contract validation for ERP deployment artifacts.`,
        ` *`,
        ` * Schema source: ${schemaSource}`,
        ` * Validates request/response payloads against the ERPDeploymentArtifact`,
        ` * contract defined in the agentics-contracts repository.`,
        ` */`,
        ``,
        `// eslint-disable-next-line @typescript-eslint/no-explicit-any`,
        `type JsonSchema = Record<string, any>;`,
        ``,
        `/**`,
        ` * Embedded ERPDeploymentArtifact schema.`,
        ` * If null, the schema must be loaded at runtime from the contracts registry.`,
        ` */`,
        `const EMBEDDED_SCHEMA: JsonSchema | null = ${schemaJson};`,
        ``,
        `export interface ValidationResult {`,
        `  readonly valid: boolean;`,
        `  readonly errors: string[];`,
        `}`,
        ``,
        `/**`,
        ` * Validate a payload against the ERPDeploymentArtifact schema.`,
        ` *`,
        ` * @param payload - The object to validate`,
        ` * @param schema - Optional schema override (uses embedded schema if omitted)`,
        ` * @returns Validation result with any errors`,
        ` */`,
        `export function validateDeploymentArtifact(`,
        `  payload: unknown,`,
        `  schema?: JsonSchema,`,
        `): ValidationResult {`,
        `  const activeSchema = schema ?? EMBEDDED_SCHEMA;`,
        `  const errors: string[] = [];`,
        ``,
        `  if (activeSchema === null) {`,
        `    return {`,
        `      valid: true,`,
        `      errors: ['Schema not available — validation skipped'],`,
        `    };`,
        `  }`,
        ``,
        `  if (typeof payload !== 'object' || payload === null) {`,
        `    return {`,
        `      valid: false,`,
        `      errors: ['Payload must be a non-null object'],`,
        `    };`,
        `  }`,
        ``,
        `  const obj = payload as Record<string, unknown>;`,
        ``,
        `  // Validate required top-level fields from the contract`,
        `  const requiredFields = activeSchema['required'] as string[] | undefined;`,
        `  if (Array.isArray(requiredFields)) {`,
        `    for (const field of requiredFields) {`,
        `      if (!(field in obj)) {`,
        `        errors.push(\`Missing required field: \${field}\`);`,
        `      }`,
        `    }`,
        `  }`,
        ``,
        `  // Validate field types from the contract properties`,
        `  const properties = activeSchema['properties'] as Record<string, { type?: string }> | undefined;`,
        `  if (properties) {`,
        `    for (const [key, def] of Object.entries(properties)) {`,
        `      if (key in obj && def.type) {`,
        `        const actualType = typeof obj[key];`,
        `        if (def.type === 'string' && actualType !== 'string') {`,
        `          errors.push(\`Field \${key}: expected string, got \${actualType}\`);`,
        `        }`,
        `        if (def.type === 'number' && actualType !== 'number') {`,
        `          errors.push(\`Field \${key}: expected number, got \${actualType}\`);`,
        `        }`,
        `        if (def.type === 'boolean' && actualType !== 'boolean') {`,
        `          errors.push(\`Field \${key}: expected boolean, got \${actualType}\`);`,
        `        }`,
        `      }`,
        `    }`,
        `  }`,
        ``,
        `  return {`,
        `    valid: errors.length === 0,`,
        `    errors,`,
        `  };`,
        `}`,
        ``,
    ].join('\n');
}
// ============================================================================
// Main: materializeProject
// ============================================================================
/**
 * Stage 3: Materialize the Phase 4 project into the Phase 5 output directory.
 *
 * Copies the entire Phase 4 project directory tree into `phase5/project/`
 * using a recursive copy. Then augments the project with runtime wiring
 * modules:
 *
 * - `contract-validation.ts`: Schema validation against agentics-contracts
 *   ERPDeploymentArtifact (embedded if the contracts repo is cached,
 *   otherwise a runtime-loading stub).
 *
 * @param context - The mutable Phase 5 pipeline context
 * @returns Stage output describing success or failure
 */
export function materializeProject(context) {
    const startTime = Date.now();
    const rootSpan = createSpan('project-materialization', 'materialize-project', {
        phase5Dir: context.phase5Dir,
        phase4Dir: context.phase4Dir,
    });
    try {
        // -------------------------------------------------------------------------
        // Step 1: Locate the Phase 4 project directory
        // -------------------------------------------------------------------------
        const locateSpan = createSpan('project-materialization', 'locate-phase4-project', { phase4Dir: context.phase4Dir }, rootSpan.spanId);
        const phase4ProjectDir = context.phase4Artifacts.projectDir;
        // If the project directory does not exist, fall back to the phase4Dir root.
        // This handles pipelines where Phase 4 places all files at the root level
        // rather than in a project/ subdirectory.
        const sourceDir = existsSync(phase4ProjectDir)
            ? phase4ProjectDir
            : context.phase4Dir;
        const sourceFileCount = countFilesRecursive(sourceDir);
        const finalLocateSpan = endSpan(locateSpan, 'ok', {
            sourceDir,
            sourceFileCount,
            usedProjectSubdir: existsSync(phase4ProjectDir),
        });
        emitSpan(finalLocateSpan);
        context.telemetrySpans.push(finalLocateSpan);
        // -------------------------------------------------------------------------
        // Step 2: Copy Phase 4 project into phase5/project/
        // -------------------------------------------------------------------------
        const copySpan = createSpan('project-materialization', 'copy-project-tree', { sourceDir, sourceFileCount }, rootSpan.spanId);
        const destDir = join(context.phase5Dir, 'project');
        mkdirSync(destDir, { recursive: true, mode: DIR_MODE });
        cpSync(sourceDir, destDir, {
            recursive: true,
            force: true,
            errorOnExist: false,
        });
        const copiedFileCount = countFilesRecursive(destDir);
        const finalCopySpan = endSpan(copySpan, 'ok', {
            destDir,
            copiedFileCount,
        });
        emitSpan(finalCopySpan);
        context.telemetrySpans.push(finalCopySpan);
        // -------------------------------------------------------------------------
        // Step 3: Generate contract-validation.ts (agentics-contracts wiring)
        // -------------------------------------------------------------------------
        const contractSpan = createSpan('project-materialization', 'generate-contract-validation', {}, rootSpan.spanId);
        const contractsRepoPath = context.platformRepoPaths['agentics-contracts'];
        const contractValidationContent = generateContractValidation(contractsRepoPath);
        // Place the contract validation module alongside the project source
        const contractValidationDir = join(destDir, 'src', 'contracts');
        mkdirSync(contractValidationDir, { recursive: true, mode: DIR_MODE });
        const contractValidationPath = join(contractValidationDir, 'contract-validation.ts');
        writeFileSync(contractValidationPath, contractValidationContent, {
            encoding: 'utf-8',
            mode: FILE_MODE,
        });
        const finalContractSpan = endSpan(contractSpan, 'ok', {
            hasEmbeddedSchema: contractsRepoPath !== undefined,
            contractValidationPath: relative(context.phase5Dir, contractValidationPath),
        });
        emitSpan(finalContractSpan);
        context.telemetrySpans.push(finalContractSpan);
        // -------------------------------------------------------------------------
        // Step 4: Count total materialized files
        // -------------------------------------------------------------------------
        const totalFiles = countFilesRecursive(destDir);
        const allFilePaths = collectFilePaths(destDir);
        // -------------------------------------------------------------------------
        // Step 5: Build and return stage output
        // -------------------------------------------------------------------------
        const finalRootSpan = endSpan(rootSpan, 'ok', {
            sourceDir,
            destDir,
            sourceFileCount,
            copiedFileCount,
            totalFiles,
            contractValidationGenerated: true,
        });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = Date.now() - startTime;
        const output = {
            stage: 'project-materialization',
            status: 'completed',
            data: {
                sourceDir,
                destDir,
                sourceFileCount,
                totalFiles,
                contractValidationPath,
            },
            artifacts: allFilePaths,
            timing,
        };
        context.stageOutputs.set('project-materialization', output);
        return output;
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        const finalRootSpan = endSpan(rootSpan, 'error', { error: errorMessage });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = Date.now() - startTime;
        const output = {
            stage: 'project-materialization',
            status: 'failed',
            data: { error: errorMessage },
            artifacts: [],
            timing,
        };
        context.stageOutputs.set('project-materialization', output);
        return output;
    }
}
//# sourceMappingURL=project-materializer.js.map