"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CanonicalWriteTargetError = exports.CANONICAL_WRITE_TARGET_ERROR_CODES = void 0;
exports.resolveCanonicalWriteTargetSync = resolveCanonicalWriteTargetSync;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const path_contract_1 = require("./path-contract");
exports.CANONICAL_WRITE_TARGET_ERROR_CODES = [
    "invalid-input",
    "invalid-repository-root",
    "repository-escape",
    "symlink-escape",
    "nested-owner",
    "case-collision",
    "non-directory-ancestor"
];
class CanonicalWriteTargetError extends Error {
    code;
    inputPath;
    constructor(code, inputPath, message) {
        super(message);
        this.name = "CanonicalWriteTargetError";
        this.code = code;
        this.inputPath = inputPath;
    }
}
exports.CanonicalWriteTargetError = CanonicalWriteTargetError;
/**
 * Resolves a proposed canonical write destination against one selected repository root.
 * Stored identity is repository-relative POSIX; runtime identity is fully resolved on disk.
 */
function resolveCanonicalWriteTargetSync(repositoryRoot, inputPath, options = {}) {
    if (typeof repositoryRoot !== "string" || repositoryRoot.trim() === "") {
        throw new CanonicalWriteTargetError("invalid-repository-root", inputPath, "A non-empty repository root is required.");
    }
    if (typeof inputPath !== "string" || inputPath.trim() === "") {
        throw new CanonicalWriteTargetError("invalid-input", typeof inputPath === "string" ? inputPath : "", "A non-empty canonical write target is required.");
    }
    const repositoryRootAbsolute = node_path_1.default.resolve(repositoryRoot);
    const rootStats = node_fs_1.default.statSync(repositoryRootAbsolute, { throwIfNoEntry: false });
    if (rootStats === undefined || !rootStats.isDirectory()) {
        throw new CanonicalWriteTargetError("invalid-repository-root", inputPath, `Repository root '${repositoryRoot}' must resolve to an existing directory.`);
    }
    const resolvedRepositoryRoot = node_fs_1.default.realpathSync(repositoryRootAbsolute);
    if (options.pathPolicy !== undefined
        && options.caseSensitive !== undefined
        && options.pathPolicy.caseSensitive !== options.caseSensitive) {
        throw new CanonicalWriteTargetError("invalid-input", inputPath, "caseSensitive must agree with the supplied target-platform path policy.");
    }
    const pathPolicy = options.pathPolicy ?? (0, path_contract_1.createCanonicalPathPolicy)({
        caseSensitive: options.caseSensitive
    });
    const caseSensitive = pathPolicy.caseSensitive;
    let parsedPath;
    try {
        parsedPath = (0, path_contract_1.parseRepositoryRelativePath)(inputPath, pathPolicy);
    }
    catch (error) {
        throw new CanonicalWriteTargetError("invalid-input", inputPath, error instanceof Error ? error.message : `Invalid canonical write target '${inputPath}'.`);
    }
    let currentPath = resolvedRepositoryRoot;
    let exists = true;
    for (let index = 0; index < parsedPath.segments.length; index += 1) {
        assertNoNestedOwner(resolvedRepositoryRoot, currentPath, inputPath);
        const currentStats = node_fs_1.default.statSync(currentPath, { throwIfNoEntry: false });
        if (currentStats === undefined || !currentStats.isDirectory()) {
            throw new CanonicalWriteTargetError("non-directory-ancestor", inputPath, `Canonical write target '${inputPath}' traverses a non-directory ancestor.`);
        }
        const requestedSegment = parsedPath.segments[index];
        const observedSegment = findObservedSegment(currentPath, requestedSegment, caseSensitive, inputPath);
        if (observedSegment === null) {
            currentPath = node_path_1.default.join(currentPath, ...parsedPath.segments.slice(index));
            exists = false;
            break;
        }
        const unresolvedEntryPath = node_path_1.default.join(currentPath, observedSegment);
        const entryStats = node_fs_1.default.lstatSync(unresolvedEntryPath);
        if (entryStats.isSymbolicLink()) {
            const resolvedEntryPath = node_fs_1.default.realpathSync(unresolvedEntryPath);
            if (!isWithinRoot(resolvedRepositoryRoot, resolvedEntryPath)) {
                throw new CanonicalWriteTargetError("symlink-escape", inputPath, `Canonical write target '${inputPath}' resolves through a symlink outside the repository root.`);
            }
            currentPath = resolvedEntryPath;
        }
        else {
            currentPath = unresolvedEntryPath;
        }
        if (!isWithinRoot(resolvedRepositoryRoot, currentPath)) {
            throw new CanonicalWriteTargetError("repository-escape", inputPath, `Canonical write target '${inputPath}' resolves outside the repository root.`);
        }
    }
    if (!isWithinRoot(resolvedRepositoryRoot, currentPath)) {
        throw new CanonicalWriteTargetError("repository-escape", inputPath, `Canonical write target '${inputPath}' resolves outside the repository root.`);
    }
    const ownershipProbe = nearestExistingDirectory(currentPath);
    assertNoNestedOwner(resolvedRepositoryRoot, ownershipProbe, inputPath);
    const repositoryRelativePath = toPosix(node_path_1.default.relative(resolvedRepositoryRoot, currentPath));
    if (repositoryRelativePath === "" || repositoryRelativePath === ".") {
        throw new CanonicalWriteTargetError("invalid-input", inputPath, "Canonical write targets must resolve to a concrete path below the repository root.");
    }
    return {
        inputPath,
        repositoryRelativePath,
        runtimePath: currentPath,
        collisionKey: caseSensitive ? repositoryRelativePath : repositoryRelativePath.toLowerCase(),
        caseSensitive,
        pathPolicy,
        exists
    };
}
function findObservedSegment(parentPath, requestedSegment, caseSensitive, inputPath) {
    const entries = node_fs_1.default.readdirSync(parentPath);
    if (caseSensitive) {
        return entries.includes(requestedSegment) ? requestedSegment : null;
    }
    const foldedSegment = requestedSegment.toLowerCase();
    const matches = entries.filter((entry) => entry.toLowerCase() === foldedSegment);
    if (matches.length > 1) {
        throw new CanonicalWriteTargetError("case-collision", inputPath, `Canonical write target '${inputPath}' is ambiguous under case-insensitive path behavior.`);
    }
    return matches[0] ?? null;
}
function nearestExistingDirectory(targetPath) {
    let candidate = targetPath;
    for (;;) {
        const stats = node_fs_1.default.statSync(candidate, { throwIfNoEntry: false });
        if (stats !== undefined) {
            return stats.isDirectory() ? candidate : node_path_1.default.dirname(candidate);
        }
        const parent = node_path_1.default.dirname(candidate);
        if (parent === candidate) {
            return candidate;
        }
        candidate = parent;
    }
}
function assertNoNestedOwner(repositoryRoot, targetDirectory, inputPath) {
    let candidate = targetDirectory;
    while (candidate !== repositoryRoot) {
        if (!isWithinRoot(repositoryRoot, candidate)) {
            throw new CanonicalWriteTargetError("repository-escape", inputPath, `Canonical write target '${inputPath}' resolves outside the repository root.`);
        }
        if (hasOwnershipMarker(candidate)) {
            const nestedRoot = toPosix(node_path_1.default.relative(repositoryRoot, candidate));
            throw new CanonicalWriteTargetError("nested-owner", inputPath, `Canonical write target '${inputPath}' is owned by nested repository root '${nestedRoot}'.`);
        }
        const parent = node_path_1.default.dirname(candidate);
        if (parent === candidate) {
            break;
        }
        candidate = parent;
    }
}
function hasOwnershipMarker(directoryPath) {
    return node_fs_1.default.existsSync(node_path_1.default.join(directoryPath, ".git")) ||
        node_fs_1.default.existsSync(node_path_1.default.join(directoryPath, ".day-shift", "config.toml"));
}
function isWithinRoot(repositoryRoot, candidatePath) {
    const relativePath = node_path_1.default.relative(repositoryRoot, candidatePath);
    return relativePath === "" ||
        (!relativePath.startsWith(`..${node_path_1.default.sep}`) && relativePath !== ".." && !node_path_1.default.isAbsolute(relativePath));
}
function toPosix(value) {
    return value.replace(/\\/g, "/");
}
