"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateMissingModuleContracts = generateMissingModuleContracts;
const path = __importStar(require("node:path"));
const node_fs_1 = require("node:fs");
const architectureInit_1 = require("./architectureInit");
const architectureMap_1 = require("./architectureMap");
const moduleDiscovery_1 = require("./moduleDiscovery");
const stackAdapters_1 = require("./stackAdapters");
const managedArtifacts_1 = require("./managedArtifacts");
function normalizePath(value) {
    return value.replace(/\\/g, '/');
}
function toAbsolutePath(workspaceRoot, relativePath) {
    return path.join(workspaceRoot, ...relativePath.split('/'));
}
async function pathExists(targetPath) {
    try {
        await node_fs_1.promises.access(targetPath);
        return true;
    }
    catch {
        return false;
    }
}
function sortUnique(values) {
    return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
function toSafeContractModuleFileStem(moduleName) {
    return normalizePath(moduleName)
        .split('/')
        .map((segment) => segment.trim())
        .filter((segment) => segment.length > 0)
        .join('.');
}
function wildcardToRegex(pattern) {
    const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
    return new RegExp(`^${escaped.replace(/\*/g, '.*')}$`, 'u');
}
function getModuleRegistry(contract) {
    const modules = contract.modules;
    if (!modules || typeof modules !== 'object' || Array.isArray(modules)) {
        return {};
    }
    return modules;
}
async function inferModulePublicEntrypoints(workspaceRoot, moduleSourcePath, adapter) {
    const normalizedSourcePath = normalizePath(moduleSourcePath);
    if (/\.[a-z0-9]+$/iu.test(normalizedSourcePath)) {
        const absoluteSourcePath = toAbsolutePath(workspaceRoot, normalizedSourcePath);
        if (await pathExists(absoluteSourcePath)) {
            return [normalizedSourcePath];
        }
    }
    const candidateFiles = adapter.defaultEntrypointHints
        .map((hint) => normalizePath(hint))
        .filter((hint) => !hint.includes('/'));
    const exactCandidates = candidateFiles.filter((hint) => !hint.includes('*'));
    const wildcardCandidates = candidateFiles.filter((hint) => hint.includes('*'));
    const resolvedWildcardCandidates = [];
    const absoluteModuleRoot = toAbsolutePath(workspaceRoot, normalizedSourcePath);
    if (wildcardCandidates.length > 0) {
        try {
            const entries = await node_fs_1.promises.readdir(absoluteModuleRoot, { withFileTypes: true });
            const fileNames = entries
                .filter((entry) => entry.isFile())
                .map((entry) => entry.name)
                .sort((left, right) => left.localeCompare(right));
            for (const wildcardPattern of wildcardCandidates) {
                const pattern = wildcardToRegex(wildcardPattern);
                const match = fileNames.find((fileName) => pattern.test(fileName));
                if (match) {
                    resolvedWildcardCandidates.push(match);
                }
            }
        }
        catch {
            // Ignore directory listing failures and keep fallback behavior.
        }
    }
    const orderedCandidates = sortUnique([...exactCandidates, ...resolvedWildcardCandidates]);
    const inferred = [];
    for (const fileName of orderedCandidates) {
        const candidateRelative = `${normalizedSourcePath}/${fileName}`;
        if (await pathExists(toAbsolutePath(workspaceRoot, candidateRelative))) {
            inferred.push(candidateRelative);
            break;
        }
    }
    for (const publicDirName of adapter.publicEntrypointDirectoryNames) {
        const publicDirectoryRelative = `${normalizedSourcePath}/${publicDirName}`;
        if (await pathExists(toAbsolutePath(workspaceRoot, publicDirectoryRelative))) {
            inferred.push(publicDirectoryRelative);
            break;
        }
    }
    return sortUnique(inferred);
}
function buildDependsOnBySource(edges) {
    const bySource = new Map();
    for (const edge of edges) {
        const set = bySource.get(edge.source) ?? new Set();
        set.add(edge.target);
        bySource.set(edge.source, set);
    }
    return new Map([...bySource.entries()]
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([source, targets]) => [
        source,
        [...targets].sort((left, right) => left.localeCompare(right)),
    ]));
}
function resolveContractPath(contract, moduleName) {
    const moduleRegistry = getModuleRegistry(contract);
    const configured = moduleRegistry[moduleName]?.contract;
    if (configured && configured.trim().length > 0) {
        return normalizePath(configured);
    }
    return normalizePath(path.posix.join((0, moduleDiscovery_1.resolveContractsRootFromContract)(contract), `${toSafeContractModuleFileStem(moduleName)}.contract.json`));
}
function createModuleContract(input) {
    return {
        module: input.moduleName,
        dependsOn: sortUnique(input.dependsOn),
        publicEntrypoints: sortUnique(input.publicEntrypoints),
        ownedTables: [],
        exposedApis: [],
    };
}
function normalizeStringArray(value) {
    return Array.isArray(value)
        ? sortUnique(value.filter((entry) => typeof entry === 'string'))
        : [];
}
async function readExistingModuleContractPublicEntrypoints(workspaceRoot, contractPath) {
    try {
        const raw = await node_fs_1.promises.readFile(toAbsolutePath(workspaceRoot, contractPath), {
            encoding: 'utf8',
        });
        const parsed = JSON.parse(raw);
        if (!Array.isArray(parsed.publicEntrypoints)) {
            return undefined;
        }
        if (parsed.publicEntrypoints.some((entry) => typeof entry !== 'string')) {
            return undefined;
        }
        return sortUnique(parsed.publicEntrypoints.map((entry) => normalizePath(entry)));
    }
    catch {
        return undefined;
    }
}
function isModuleContractDifferent(left, right) {
    const fields = [
        'module',
        'dependsOn',
        'publicEntrypoints',
        'ownedTables',
        'exposedApis',
    ];
    return fields.some((field) => {
        const leftValue = left[field];
        const rightValue = right[field];
        if (Array.isArray(leftValue) || Array.isArray(rightValue)) {
            const leftArray = normalizeStringArray(leftValue);
            const rightArray = normalizeStringArray(rightValue);
            return (leftArray.length !== rightArray.length ||
                leftArray.some((entry, index) => entry !== rightArray[index]));
        }
        return leftValue !== rightValue;
    });
}
async function readExistingModuleContract(workspaceRoot, contractPath) {
    try {
        const raw = await node_fs_1.promises.readFile(toAbsolutePath(workspaceRoot, contractPath), {
            encoding: 'utf8',
        });
        const parsed = JSON.parse(raw);
        return parsed && typeof parsed === 'object' ? parsed : undefined;
    }
    catch {
        return undefined;
    }
}
function isRegistryEntryDifferent(input) {
    const moduleRegistry = getModuleRegistry(input.contract);
    const existing = moduleRegistry[input.moduleName] ?? {};
    const existingEntrypoints = sortUnique(existing.publicEntrypoints ?? []);
    const expectedEntrypoints = sortUnique(input.publicEntrypoints);
    return (existing.path !== input.sourcePath ||
        existing.contract !== input.contractPath ||
        existingEntrypoints.length !== expectedEntrypoints.length ||
        existingEntrypoints.some((entry, index) => entry !== expectedEntrypoints[index]));
}
async function generateMissingModuleContracts(workspaceRoot) {
    const contract = await (0, architectureInit_1.loadArchitectureContract)(workspaceRoot);
    if (!contract) {
        throw new Error('ArchPilot is not initialized in this workspace.');
    }
    const adapter = (0, stackAdapters_1.resolvePrimaryAdapterFromContract)(contract);
    const map = await (0, architectureMap_1.generateArchitectureMap)(workspaceRoot);
    const discoveredModules = map.modules
        .map((entry) => entry.moduleName)
        .sort((left, right) => left.localeCompare(right));
    const dependsOnBySource = buildDependsOnBySource(map.edges.map((edge) => ({ source: edge.source, target: edge.target })));
    const createdContracts = [];
    const updatedManagedContracts = [];
    const unchangedManagedContracts = [];
    const skippedExistingContracts = [];
    const updatedRegistryModules = [];
    const generatedMetadata = await (0, managedArtifacts_1.buildManagedMetadata)('module-contracts');
    for (const moduleEntry of [...map.modules].sort((left, right) => left.moduleName.localeCompare(right.moduleName))) {
        const contractPath = resolveContractPath(contract, moduleEntry.moduleName);
        const sourcePath = normalizePath(moduleEntry.sourcePath);
        const absoluteContractPath = toAbsolutePath(workspaceRoot, contractPath);
        const contractExists = await pathExists(absoluteContractPath);
        const existingContract = contractExists
            ? await readExistingModuleContract(workspaceRoot, contractPath)
            : undefined;
        const existingContractPublicEntrypoints = contractExists
            ? await readExistingModuleContractPublicEntrypoints(workspaceRoot, contractPath)
            : undefined;
        const configuredPublicEntrypoints = !contractExists && Array.isArray(getModuleRegistry(contract)[moduleEntry.moduleName]?.publicEntrypoints)
            ? sortUnique(getModuleRegistry(contract)[moduleEntry.moduleName]?.publicEntrypoints?.map((entry) => normalizePath(entry)) ?? [])
            : undefined;
        const publicEntrypoints = existingContractPublicEntrypoints ??
            configuredPublicEntrypoints ??
            (await inferModulePublicEntrypoints(workspaceRoot, sourcePath, adapter));
        const moduleContract = createModuleContract({
            moduleName: moduleEntry.moduleName,
            dependsOn: dependsOnBySource.get(moduleEntry.moduleName) ?? [],
            publicEntrypoints,
        });
        if (contractExists) {
            const plan = await (0, managedArtifacts_1.classifyManagedArtifact)(workspaceRoot, contractPath, 'json', {
                generator: 'module-contracts',
            });
            if (plan.status === 'update-managed') {
                const nextContract = {
                    ...moduleContract,
                    ownedTables: normalizeStringArray(existingContract?.ownedTables),
                    exposedApis: normalizeStringArray(existingContract?.exposedApis),
                };
                const normalizedExistingContract = {
                    module: typeof existingContract?.module === 'string'
                        ? existingContract.module
                        : moduleContract.module,
                    dependsOn: normalizeStringArray(existingContract?.dependsOn),
                    publicEntrypoints: normalizeStringArray(existingContract?.publicEntrypoints),
                    ownedTables: normalizeStringArray(existingContract?.ownedTables),
                    exposedApis: normalizeStringArray(existingContract?.exposedApis),
                };
                if (isModuleContractDifferent(normalizedExistingContract, nextContract)) {
                    await node_fs_1.promises.mkdir(path.dirname(absoluteContractPath), { recursive: true });
                    await node_fs_1.promises.writeFile(absoluteContractPath, `${JSON.stringify((0, managedArtifacts_1.withManagedJsonMetadata)(nextContract, generatedMetadata), null, 2)}\n`, { encoding: 'utf8' });
                    updatedManagedContracts.push(contractPath);
                }
                else {
                    unchangedManagedContracts.push(contractPath);
                }
            }
            else {
                skippedExistingContracts.push(contractPath);
            }
        }
        else {
            await node_fs_1.promises.mkdir(path.dirname(absoluteContractPath), { recursive: true });
            await node_fs_1.promises.writeFile(absoluteContractPath, `${JSON.stringify((0, managedArtifacts_1.withManagedJsonMetadata)({ ...moduleContract }, generatedMetadata), null, 2)}\n`, {
                encoding: 'utf8',
            });
            createdContracts.push(contractPath);
        }
        if (moduleEntry.discoverySource === 'architecture.json' &&
            isRegistryEntryDifferent({
                contract,
                moduleName: moduleEntry.moduleName,
                sourcePath,
                contractPath,
                publicEntrypoints,
            })) {
            const moduleRegistry = getModuleRegistry(contract);
            moduleRegistry[moduleEntry.moduleName] = {
                ...(moduleRegistry[moduleEntry.moduleName] ?? {}),
                path: sourcePath,
                contract: contractPath,
                publicEntrypoints: sortUnique(publicEntrypoints),
            };
            contract.modules = moduleRegistry;
            updatedRegistryModules.push(moduleEntry.moduleName);
        }
    }
    if (updatedRegistryModules.length > 0) {
        await (0, architectureInit_1.saveArchitectureContract)(workspaceRoot, contract);
    }
    return {
        discoveredModules,
        createdContracts: sortUnique(createdContracts),
        updatedManagedContracts: sortUnique(updatedManagedContracts),
        unchangedManagedContracts: sortUnique(unchangedManagedContracts),
        skippedExistingContracts: sortUnique(skippedExistingContracts),
        updatedRegistryModules: sortUnique(updatedRegistryModules),
    };
}
//# sourceMappingURL=moduleContractsGenerator.js.map