"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateMonorepoProjectsSync = validateMonorepoProjectsSync;
exports.resolveMonorepoProjectContextSync = resolveMonorepoProjectContextSync;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../errors");
const paths_1 = require("../paths");
const PROJECT_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
const WORKFLOW_LEVELS = new Set(["basic", "structured", "governed"]);
function validateMonorepoProjectsSync(options) {
    const configPath = options.configPath ?? ".day-shift/config.toml";
    if (!Array.isArray(options.projects)) {
        return {
            projects: [],
            findings: [finding("INVALID_MONOREPO_PROJECTS", configPath, "Config key 'monorepo_projects' must be an array of project tables.")]
        };
    }
    if (options.projects.length === 0) {
        return { projects: [], findings: [] };
    }
    const pathPolicy = options.pathPolicy ?? (0, paths_1.createCanonicalPathPolicy)({
        caseSensitive: options.caseSensitive
    });
    const caseSensitive = pathPolicy.caseSensitive;
    const findings = [];
    const projects = [];
    const repositoryRoot = node_fs_1.default.realpathSync(node_path_1.default.resolve(options.repositoryRoot));
    const ids = new Map();
    const roots = new Map();
    for (const [index, rawProject] of options.projects.entries()) {
        const fieldPath = `monorepo_projects[${index}]`;
        if (!isPlainObject(rawProject)) {
            findings.push(finding("INVALID_MONOREPO_PROJECTS", configPath, `${fieldPath} must be a table with id, root, and optional default_workflow_level fields.`));
            continue;
        }
        const unknownFields = Object.keys(rawProject)
            .filter((key) => ![
            "id",
            "root",
            "default_workflow_level",
            "contract_revision_impact_budget"
        ].includes(key))
            .sort();
        if (unknownFields.length > 0) {
            findings.push(finding("INVALID_MONOREPO_PROJECTS", configPath, `${fieldPath} contains unsupported field(s): ${unknownFields.join(", ")}.`));
            continue;
        }
        const id = typeof rawProject.id === "string" ? rawProject.id.trim() : "";
        const rootInput = typeof rawProject.root === "string" ? rawProject.root.trim() : "";
        const defaultWorkflowLevel = rawProject.default_workflow_level;
        const impactBudget = parseProjectContractRevisionImpactBudget({
            value: rawProject.contract_revision_impact_budget,
            fieldPath,
            configPath
        });
        if (impactBudget.finding !== null) {
            findings.push(impactBudget.finding);
            continue;
        }
        if (!PROJECT_ID_PATTERN.test(id)) {
            findings.push(finding("INVALID_PROJECT_ID", configPath, `${fieldPath}.id must be a lower-case machine-safe project id.`));
            continue;
        }
        const foldedId = id.toLowerCase();
        if (ids.has(foldedId)) {
            findings.push(finding("DUPLICATE_PROJECT_ID", configPath, `Project id '${id}' collides with configured project '${ids.get(foldedId)}'.`));
            continue;
        }
        ids.set(foldedId, id);
        if (defaultWorkflowLevel !== undefined
            && (typeof defaultWorkflowLevel !== "string"
                || !WORKFLOW_LEVELS.has(defaultWorkflowLevel))) {
            findings.push(finding("INVALID_PROJECT_WORKFLOW_LEVEL", configPath, `${fieldPath}.default_workflow_level must be basic, structured, or governed.`));
            continue;
        }
        let normalizedRoot;
        try {
            normalizedRoot = (0, paths_1.parseRepositoryRelativePath)(rootInput, pathPolicy).normalizedPath;
        }
        catch (error) {
            findings.push(finding("INVALID_PROJECT_ROOT", configPath, error instanceof Error ? error.message : `${fieldPath}.root is invalid.`));
            continue;
        }
        const absoluteRoot = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, normalizedRoot);
        const pathFinding = validateProjectRoot({
            repositoryRoot,
            absoluteRoot,
            normalizedRoot,
            configPath
        });
        if (pathFinding !== null) {
            findings.push(pathFinding);
            continue;
        }
        const project = {
            id,
            root: normalizedRoot,
            defaultWorkflowLevel: defaultWorkflowLevel ?? null,
            contractRevisionImpactBudget: impactBudget.value
        };
        const collisionKey = caseSensitive ? normalizedRoot : normalizedRoot.toLowerCase();
        const collidingRoot = roots.get(collisionKey);
        if (collidingRoot !== undefined) {
            findings.push(finding("PROJECT_ROOT_CASE_COLLISION", configPath, `Project root '${normalizedRoot}' collides with '${collidingRoot.root}' under the target filesystem case policy.`));
            continue;
        }
        roots.set(collisionKey, project);
        projects.push(project);
    }
    const orderedProjects = projects.sort((left, right) => left.id.localeCompare(right.id));
    for (let leftIndex = 0; leftIndex < orderedProjects.length; leftIndex += 1) {
        for (let rightIndex = leftIndex + 1; rightIndex < orderedProjects.length; rightIndex += 1) {
            const left = orderedProjects[leftIndex];
            const right = orderedProjects[rightIndex];
            if (projectRootsOverlap(left.root, right.root, caseSensitive)) {
                findings.push(finding("OVERLAPPING_PROJECT_ROOT", configPath, `Project roots '${left.root}' and '${right.root}' overlap; project ownership must be unambiguous.`));
            }
        }
    }
    return {
        projects: orderedProjects,
        findings: findings.sort(compareFindings)
    };
}
function parseProjectContractRevisionImpactBudget(options) {
    if (options.value === undefined)
        return { value: null, finding: null };
    if (!isPlainObject(options.value)) {
        return {
            value: null,
            finding: finding("INVALID_PROJECT_CONTRACT_REVISION_IMPACT_BUDGET", options.configPath, `${options.fieldPath}.contract_revision_impact_budget must be a table with non-negative integer thresholds.`)
        };
    }
    const unknown = Object.keys(options.value)
        .filter((key) => !["max_invalidated_entries", "max_evidence_bytes"].includes(key))
        .sort();
    const values = [
        options.value.max_invalidated_entries,
        options.value.max_evidence_bytes
    ];
    if (unknown.length > 0 || values.some((value) => value !== undefined && (!Number.isSafeInteger(value) || Number(value) < 0))) {
        return {
            value: null,
            finding: finding("INVALID_PROJECT_CONTRACT_REVISION_IMPACT_BUDGET", options.configPath, unknown.length > 0
                ? `${options.fieldPath}.contract_revision_impact_budget contains unsupported field(s): ${unknown.join(", ")}.`
                : `${options.fieldPath}.contract_revision_impact_budget thresholds must be non-negative safe integers.`)
        };
    }
    return {
        value: {
            ...(options.value.max_invalidated_entries === undefined
                ? {}
                : { max_invalidated_entries: Number(options.value.max_invalidated_entries) }),
            ...(options.value.max_evidence_bytes === undefined
                ? {}
                : { max_evidence_bytes: Number(options.value.max_evidence_bytes) })
        },
        finding: null
    };
}
function resolveMonorepoProjectContextSync(options) {
    const validation = validateMonorepoProjectsSync(options);
    if (validation.findings.length > 0) {
        throw (0, errors_1.createInvalidConfigError)("Invalid monorepo project configuration.", validation.findings.map((entry) => entry.message), [...validation.findings]);
    }
    const repositoryDefaultWorkflowLevel = options.repositoryDefaultWorkflowLevel ?? "structured";
    const explicitProjectId = options.explicitProjectId?.trim();
    let selected = null;
    let source = "repository";
    if (explicitProjectId !== undefined && explicitProjectId !== "") {
        selected = validation.projects.find((project) => project.id === explicitProjectId) ?? null;
        if (selected === null) {
            const projectFinding = finding("UNKNOWN_PROJECT", options.configPath ?? ".day-shift/config.toml", `Project '${explicitProjectId}' is not declared in monorepo_projects.`);
            throw (0, errors_1.createInvalidConfigError)("Unknown monorepo project selection.", [projectFinding.message], [projectFinding]);
        }
        source = "explicit";
    }
    else {
        const currentDirectory = resolveCurrentDirectory(options.repositoryRoot, options.currentDirectory ?? process.cwd());
        const matches = validation.projects.filter((project) => {
            const absoluteProjectRoot = (0, paths_1.resolveRepositoryRelativePath)(options.repositoryRoot, project.root);
            return isWithinOrEqual(currentDirectory, absoluteProjectRoot);
        });
        if (matches.length > 1) {
            const contextFinding = finding("AMBIGUOUS_PROJECT_CONTEXT", options.configPath ?? ".day-shift/config.toml", `Current-directory evidence matches multiple projects: ${matches.map((project) => project.id).sort().join(", ")}.`);
            throw (0, errors_1.createInvalidConfigError)("Ambiguous monorepo project context.", [contextFinding.message], [contextFinding]);
        }
        selected = matches[0] ?? null;
        if (selected !== null)
            source = "current-directory";
    }
    return {
        protocolVersion: 1,
        readOnly: true,
        context: selected === null ? "repository" : "project",
        projectId: selected?.id ?? null,
        source,
        effectiveWorkflowLevel: selected?.defaultWorkflowLevel ?? repositoryDefaultWorkflowLevel,
        workflowLevelSource: selected?.defaultWorkflowLevel === null || selected === null
            ? "repository"
            : "project",
        projects: validation.projects,
        findings: [],
        recoveryGuidance: selected === null
            ? ["Use --project <id> to request one configured project view explicitly."]
            : []
    };
}
function validateProjectRoot(options) {
    let current = options.repositoryRoot;
    for (const segment of options.normalizedRoot.split("/")) {
        current = node_path_1.default.join(current, segment);
        const stats = node_fs_1.default.lstatSync(current, { throwIfNoEntry: false });
        if (stats === undefined) {
            return finding("MISSING_PROJECT_ROOT", options.configPath, `Project root '${options.normalizedRoot}' does not exist.`);
        }
        if (stats.isSymbolicLink()) {
            return finding("PROJECT_ROOT_SYMLINK", options.configPath, `Project root '${options.normalizedRoot}' traverses a symbolic link.`);
        }
    }
    const stats = node_fs_1.default.statSync(options.absoluteRoot, { throwIfNoEntry: false });
    if (stats === undefined || !stats.isDirectory()) {
        return finding("MISSING_PROJECT_ROOT", options.configPath, `Project root '${options.normalizedRoot}' must resolve to an existing directory.`);
    }
    const resolved = node_fs_1.default.realpathSync(options.absoluteRoot);
    if (!isWithinOrEqual(resolved, options.repositoryRoot)) {
        return finding("INVALID_PROJECT_ROOT", options.configPath, `Project root '${options.normalizedRoot}' resolves outside the repository root.`);
    }
    if (node_fs_1.default.existsSync(node_path_1.default.join(resolved, ".git"))
        || node_fs_1.default.existsSync(node_path_1.default.join(resolved, ".day-shift", "config.toml"))) {
        return finding("NESTED_PROJECT_OWNER", options.configPath, `Project root '${options.normalizedRoot}' contains a nested repository owner marker.`);
    }
    return null;
}
function resolveCurrentDirectory(repositoryRoot, currentDirectory) {
    const absolute = node_path_1.default.resolve(currentDirectory);
    const stats = node_fs_1.default.statSync(absolute, { throwIfNoEntry: false });
    return stats === undefined ? absolute : node_fs_1.default.realpathSync(absolute);
}
function projectRootsOverlap(left, right, caseSensitive) {
    const normalize = (value) => caseSensitive ? value : value.toLowerCase();
    const leftValue = normalize(left);
    const rightValue = normalize(right);
    return leftValue === rightValue
        || leftValue.startsWith(`${rightValue}/`)
        || rightValue.startsWith(`${leftValue}/`);
}
function isWithinOrEqual(candidate, parent) {
    const relative = node_path_1.default.relative(parent, candidate);
    return relative === ""
        || (!relative.startsWith(`..${node_path_1.default.sep}`) && relative !== ".." && !node_path_1.default.isAbsolute(relative));
}
function finding(code, configPath, message) {
    return { code, severity: "error", path: configPath, message };
}
function compareFindings(left, right) {
    return left.code.localeCompare(right.code) || left.message.localeCompare(right.message);
}
function isPlainObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
