"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 global_format_1 = require("../global-format");
const path_utils_1 = require("./path-utils");
const CANONICAL_PLACEHOLDER_PATTERN = /{{([a-z_][a-z0-9_]*)}}/g;
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(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(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]);
}
