"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTemplateMaterializeCommand = createTemplateMaterializeCommand;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const paths_1 = require("../../../lib/paths");
const runtime_contract_1 = require("../../../lib/runtime/runtime-contract");
const path_resolution_1 = require("../../../lib/templates/path-resolution");
const cli_constants_1 = require("../../../lib/config/cli-constants");
const global_format_1 = require("../global-format");
const path_utils_1 = require("./path-utils");
async function createTemplateMaterializeCommand() {
    const { Command } = await import('commander');
    const command = new Command('materialize');
    command
        .description('Materialize one selected template into one explicit repository destination.')
        .requiredOption('--template <path>', 'Repository-relative template-manifest path to materialize.')
        .requiredOption('--mode <mode>', 'Materialization mode: raw or rendered.')
        .option('--destination <path>', 'Explicit repository-relative write target.')
        .option('--manifest-destination', 'Reuse the selected manifest entry destination when eligible.')
        .option('--placeholder <name=value>', 'Rendered-mode placeholder assignment. Repeat for multiple values.', collectPlaceholderAssignment, [])
        .option('--dry-run', 'Show the intended materialization result without writing the destination.')
        .option('--force', 'Explicitly allow overwriting an existing destination file.')
        .action((_, commandInstance) => {
        const format = (0, output_1.resolveOutputFormat)((0, global_format_1.getGlobalFormat)(commandInstance));
        if (format === null) {
            throw (0, errors_1.createUsageError)('Output format must be one of: text, json.', [
                `Received: ${String((0, global_format_1.getGlobalFormat)(commandInstance))}`,
            ]);
        }
        const repositoryContext = (0, runtime_contract_1.createRepositoryContext)((0, runtime_contract_1.detectRepositoryRoot)(), false);
        const commandOptions = commandInstance.opts();
        const mode = parseMode(commandOptions.mode);
        const destinationSelection = resolveDestinationSelection(commandOptions);
        const templatePathInput = readRequiredStringOption(commandOptions.template, '--template');
        const templatePath = validateTemplatePath(templatePathInput);
        const placeholderAssignments = parsePlaceholderAssignments(commandOptions.placeholder);
        if (mode === 'raw' && placeholderAssignments.length > 0) {
            throw (0, errors_1.createUsageError)('Raw mode does not accept placeholder assignments.', [
                'Use --mode rendered when passing one or more --placeholder name=value assignments.',
            ]);
        }
        const manifestResolution = loadTemplateManifest(repositoryContext.root);
        const templateEntry = manifestResolution.document.templates.find((entry) => entry.path === templatePath);
        if (templateEntry === undefined) {
            throw blockedMaterializationError({
                message: 'Template materialization was rejected.',
                detail: `Template '${templatePath}' is not registered in the active template manifest.`,
                findingPath: manifestResolution.manifestPath,
            });
        }
        const sourceResolution = (0, path_utils_1.resolveTemplateSourceAbsolutePath)({
            repositoryRoot: repositoryContext.root,
            templatePath,
        });
        if (sourceResolution === null) {
            throw blockedMaterializationError({
                message: 'Template materialization was rejected.',
                detail: `Template source '${templatePath}' is unavailable in the workspace and packaged-default template surfaces.`,
                findingPath: templatePath,
            });
        }
        const destinationPath = resolveDestinationPath({
            selection: destinationSelection,
            explicitDestination: commandOptions.destination,
            templateEntry,
            templatePath,
        });
        const destinationAbsolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryContext.root, destinationPath);
        const sourceContent = node_fs_1.default.readFileSync(sourceResolution.absolutePath, 'utf8');
        const renderedContent = mode === 'rendered'
            ? renderTemplateContent({
                templatePath,
                sourceContent,
                placeholderAssignments,
            })
            : sourceContent;
        const destinationExists = node_fs_1.default.existsSync(destinationAbsolutePath);
        const outcome = determineOutcome({
            destinationExists,
            force: commandOptions.force === true,
        });
        if (!commandOptions.dryRun && outcome !== 'keep') {
            node_fs_1.default.mkdirSync(node_path_1.default.dirname(destinationAbsolutePath), { recursive: true });
            node_fs_1.default.writeFileSync(destinationAbsolutePath, renderedContent, 'utf8');
        }
        const result = {
            templatePath,
            sourceOrigin: sourceResolution.source,
            manifestSource: manifestResolution.source,
            destinationPath,
            destinationSelection,
            mode,
            placeholderAssignments,
            outcome,
            writeApplied: !commandOptions.dryRun && outcome !== 'keep',
            forced: commandOptions.force === true,
            dryRun: commandOptions.dryRun === true,
            templateKind: templateEntry.kind,
            templateArtifactType: templateEntry.artifact_type,
        };
        (0, output_1.emitSuccess)({
            command: 'template materialize',
            format,
            text: renderTemplateMaterializeText(result),
            data: {
                repositoryRoot: repositoryContext.root,
                detectionSource: repositoryContext.detectionSource,
                templatePath: result.templatePath,
                sourceOrigin: result.sourceOrigin,
                manifestSource: result.manifestSource,
                destinationPath: result.destinationPath,
                destinationSelection: result.destinationSelection,
                mode: result.mode,
                placeholderAssignments: result.placeholderAssignments,
                outcome: result.outcome,
                writeApplied: result.writeApplied,
                forced: result.forced,
                dryRun: result.dryRun,
                templateKind: result.templateKind,
                templateArtifactType: result.templateArtifactType,
            },
        });
    });
    return command;
}
function collectPlaceholderAssignment(value, previous) {
    previous.push(value);
    return previous;
}
function parseMode(value) {
    if (value === 'raw' || value === 'rendered') {
        return value;
    }
    throw (0, errors_1.createUsageError)('--mode must be one of: raw, rendered.', [`Received: ${String(value)}`]);
}
function resolveDestinationSelection(commandOptions) {
    const hasExplicitDestination = typeof commandOptions.destination === 'string';
    const hasManifestDestination = commandOptions.manifestDestination === true;
    if (hasExplicitDestination === hasManifestDestination) {
        throw (0, errors_1.createUsageError)('Choose exactly one destination lane.', [
            'Pass either --destination <path> or --manifest-destination.',
        ]);
    }
    return hasExplicitDestination ? 'explicit' : 'manifest';
}
function readRequiredStringOption(value, flagName) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        throw (0, errors_1.createUsageError)(`${flagName} requires a non-empty value.`);
    }
    return value;
}
function validateTemplatePath(input) {
    const parsed = (0, paths_1.parseRepositoryRelativePath)(input);
    if (input !== parsed.normalizedPath) {
        throw (0, errors_1.createUsageError)(`--template must use the canonical repository-relative path '${parsed.normalizedPath}'.`);
    }
    if (!parsed.normalizedPath.startsWith(`${path_resolution_1.CANONICAL_TEMPLATE_ROOT}/`)) {
        throw blockedMaterializationError({
            message: 'Template materialization was rejected.',
            detail: `Template '${parsed.normalizedPath}' is outside the template-family source root '${path_resolution_1.CANONICAL_TEMPLATE_ROOT}/'.`,
            findingPath: parsed.normalizedPath,
        });
    }
    return parsed.normalizedPath;
}
function parsePlaceholderAssignments(values) {
    if (values === undefined) {
        return [];
    }
    const assignments = [];
    for (const value of values) {
        const separatorIndex = value.indexOf('=');
        if (separatorIndex <= 0) {
            throw (0, errors_1.createUsageError)('Placeholder assignments must use the form name=value.', [`Received: ${value}`]);
        }
        const name = value.slice(0, separatorIndex);
        const replacement = value.slice(separatorIndex + 1);
        if (!/^[a-z_][a-z0-9_]*$/.test(name)) {
            throw (0, errors_1.createUsageError)('Placeholder names must use canonical lowercase snake_case.', [`Received: ${name}`]);
        }
        assignments.push({ name, value: replacement });
    }
    return assignments;
}
function loadTemplateManifest(repositoryRoot) {
    const manifestPath = node_path_1.default.posix.join(path_resolution_1.CANONICAL_TEMPLATE_ROOT, 'manifest.json');
    const workspaceManifestAbsolutePath = (0, path_resolution_1.resolveWorkspaceTemplateAbsolutePath)({
        repositoryRoot,
        templatePath: manifestPath,
    });
    if (node_fs_1.default.existsSync(workspaceManifestAbsolutePath)) {
        return {
            manifestPath,
            source: 'workspace',
            document: parseTemplateManifestDocument(workspaceManifestAbsolutePath, manifestPath),
        };
    }
    const packageSourceResolution = (0, path_utils_1.resolveTemplateSourceAbsolutePath)({
        repositoryRoot,
        templatePath: manifestPath,
    });
    const packageManifestAbsolutePath = packageSourceResolution?.source === 'package'
        ? packageSourceResolution.absolutePath
        : workspaceManifestAbsolutePath;
    return {
        manifestPath,
        source: 'package',
        document: parseTemplateManifestDocument(packageManifestAbsolutePath, manifestPath),
    };
}
function parseTemplateManifestDocument(absolutePath, manifestPath) {
    try {
        const parsed = JSON.parse(node_fs_1.default.readFileSync(absolutePath, 'utf8'));
        if (!Array.isArray(parsed.templates)) {
            throw new TypeError(`Template manifest '${manifestPath}' is missing a templates[] collection.`);
        }
        return {
            templates: parsed.templates,
        };
    }
    catch (error) {
        const detail = error instanceof Error ? error.message : `Template manifest '${manifestPath}' could not be read.`;
        throw blockedMaterializationError({
            message: 'Template materialization was rejected.',
            detail,
            findingPath: manifestPath,
        });
    }
}
function resolveDestinationPath(options) {
    if (options.selection === 'manifest') {
        if (options.templateEntry.kind !== 'scaffold' || typeof options.templateEntry.destination !== 'string') {
            throw blockedMaterializationError({
                message: 'Template materialization was rejected.',
                detail: `Template '${options.templatePath}' does not declare an eligible manifest destination.`,
                findingPath: options.templatePath,
            });
        }
        const manifestDestination = (0, paths_1.validateWriteDestination)(options.templateEntry.destination);
        if (!manifestDestination.ok) {
            throw blockedMaterializationError({
                message: 'Template materialization was rejected.',
                detail: manifestDestination.rejection.reason,
                findingPath: options.templateEntry.destination,
            });
        }
        return manifestDestination.value.repositoryRelativePath.normalizedPath;
    }
    const explicitDestination = readRequiredStringOption(options.explicitDestination, '--destination');
    const destinationResult = (0, paths_1.validateWriteDestination)(explicitDestination);
    if (!destinationResult.ok) {
        throw blockedMaterializationError({
            message: 'Template materialization was rejected.',
            detail: destinationResult.rejection.reason,
            findingPath: explicitDestination,
        });
    }
    if (destinationResult.value.repositoryRelativePath.normalizedPath === options.templatePath) {
        throw blockedMaterializationError({
            message: 'Template materialization was rejected.',
            detail: `Destination '${options.templatePath}' must not overwrite the selected template source path.`,
            findingPath: options.templatePath,
        });
    }
    return destinationResult.value.repositoryRelativePath.normalizedPath;
}
function renderTemplateContent(options) {
    const placeholderNamesInTemplate = collectCanonicalPlaceholderNames(options.sourceContent);
    const placeholderMap = new Map();
    for (const assignment of options.placeholderAssignments) {
        placeholderMap.set(assignment.name, assignment.value);
    }
    const unknownAssignments = options.placeholderAssignments
        .map((assignment) => assignment.name)
        .filter((name, index, allNames) => allNames.indexOf(name) === index)
        .filter((name) => !placeholderNamesInTemplate.has(name));
    if (unknownAssignments.length > 0) {
        throw blockedMaterializationError({
            message: 'Template materialization was rejected.',
            detail: `Rendered mode received placeholder assignments that are absent from '${options.templatePath}': ${unknownAssignments.join(', ')}.`,
            findingPath: options.templatePath,
        });
    }
    const renderedContent = options.sourceContent.replace(cli_constants_1.TEMPLATE_CANONICAL_PLACEHOLDER_PATTERN, (fullMatch, name) => (placeholderMap.has(name) ? (placeholderMap.get(name) ?? '') : fullMatch));
    const unresolvedPlaceholders = [...collectCanonicalPlaceholderNames(renderedContent)];
    if (unresolvedPlaceholders.length > 0) {
        throw blockedMaterializationError({
            message: 'Template materialization was rejected.',
            detail: `Rendered output for '${options.templatePath}' still contains unresolved placeholders: ${unresolvedPlaceholders.join(', ')}.`,
            findingPath: options.templatePath,
        });
    }
    return renderedContent;
}
function collectCanonicalPlaceholderNames(content) {
    const names = new Set();
    for (const match of content.matchAll(cli_constants_1.TEMPLATE_CANONICAL_PLACEHOLDER_PATTERN)) {
        const name = match[1];
        if (typeof name === 'string') {
            names.add(name);
        }
    }
    return names;
}
function determineOutcome(options) {
    if (!options.destinationExists) {
        return 'create';
    }
    if (options.force) {
        return 'overwrite';
    }
    return 'keep';
}
function renderTemplateMaterializeText(result) {
    const lines = [
        `Template source: ${result.templatePath}`,
        `Source origin: ${result.sourceOrigin}`,
        `Manifest source: ${result.manifestSource}`,
        `Destination: ${result.destinationPath}`,
        `Destination selection: ${result.destinationSelection}`,
        `Mode: ${result.mode}`,
        `Outcome: ${result.outcome}`,
        `Write applied: ${result.writeApplied ? 'yes' : 'no'}`,
        `Force requested: ${result.forced ? 'yes' : 'no'}`,
        `Dry-run: ${result.dryRun ? 'yes' : 'no'}`,
        `Template kind: ${result.templateKind}`,
        `Template artifact type: ${result.templateArtifactType}`,
    ];
    if (result.placeholderAssignments.length > 0) {
        lines.push(`Placeholders: ${result.placeholderAssignments.map((assignment) => `${assignment.name}=${assignment.value}`).join(', ')}`);
    }
    return lines.join('\n');
}
function blockedMaterializationError(options) {
    const finding = {
        code: 'TEMPLATE_MATERIALIZE_BLOCKED',
        message: options.detail,
        severity: 'error',
        path: options.findingPath,
    };
    return (0, errors_1.createInvalidConfigError)(options.message, [options.detail], [finding]);
}
