import { readFile, stat } from "node:fs/promises";
import path from "node:path";
import { diagnosticFromLifecycleError, RuntimeLifecycleError, } from "../runtimeLifecycleDiagnostics.js";
import { statusFromVerifiedTarget, } from "./OpenCodeBinaryManager.js";
import { PlatformTargetResolver } from "./PlatformTargetResolver.js";
import { TufVerificationError } from "./tufTypes.js";
export class FileSystemOpenCodeBinaryManager {
    #metadataStore;
    #verifier;
    #artifactRoot;
    #targetResolver;
    constructor(options) {
        this.#metadataStore = options.metadataStore;
        this.#verifier = options.verifier;
        this.#artifactRoot = options.artifactRoot;
        this.#targetResolver =
            options.targetResolver ?? new PlatformTargetResolver();
    }
    async status() {
        try {
            const binary = await this.resolveVerifiedBinary();
            return statusFromVerifiedTarget(binary);
        }
        catch (error) {
            const diagnostic = diagnosticFromLifecycleError(error, {
                reasonCode: "verification_rejected",
            });
            return {
                state: stateFromDiagnostic(diagnostic),
                reason: diagnostic.reason,
                reasonCode: diagnostic.reasonCode,
                phase: diagnostic.phase,
                timestamp: diagnostic.timestamp,
            };
        }
    }
    async resolveVerifiedBinary() {
        const metadata = await loadMetadataSafely(this.#metadataStore);
        const targetPath = resolveTargetPathSafely(this.#targetResolver);
        const verifiedTarget = verifyTargetSafely(this.#verifier, metadata, targetPath);
        const executablePath = resolveArtifactPath(this.#artifactRoot, verifiedTarget);
        const bytes = await readArtifactSafely(executablePath);
        this.#verifier.verifyArtifactBytes(verifiedTarget, bytes);
        await assertExecutableUsable(executablePath);
        return {
            ...verifiedTarget,
            executablePath,
        };
    }
}
async function loadMetadataSafely(metadataStore) {
    try {
        return await metadataStore.load();
    }
    catch (error) {
        if (error instanceof SyntaxError) {
            throw new RuntimeLifecycleError("metadata_malformed", "metadata", {
                cause: error,
            });
        }
        if (isNodeError(error)) {
            throw new RuntimeLifecycleError("metadata_unavailable", "metadata", {
                cause: error,
            });
        }
        throw new RuntimeLifecycleError("metadata_unavailable", "metadata", {
            cause: error,
        });
    }
}
function resolveTargetPathSafely(targetResolver) {
    try {
        return targetResolver.targetPath();
    }
    catch (error) {
        throw new RuntimeLifecycleError("unsupported_platform", "target_resolution", { cause: error });
    }
}
function verifyTargetSafely(verifier, metadata, targetPath) {
    try {
        return verifier.verifyTarget(metadata, targetPath);
    }
    catch (error) {
        if (error instanceof TufVerificationError ||
            error instanceof RuntimeLifecycleError) {
            throw error;
        }
        if (error instanceof TypeError) {
            throw new RuntimeLifecycleError("metadata_malformed", "metadata", {
                cause: error,
            });
        }
        throw new RuntimeLifecycleError("verification_rejected", "metadata", {
            cause: error,
        });
    }
}
function resolveArtifactPath(artifactRoot, artifact) {
    if (!artifact.bundledPath) {
        throw new TufVerificationError("unknown_target", "Verified OpenCode target does not include a bundledPath; remote download is not implemented in MVP.");
    }
    const resolved = path.isAbsolute(artifact.bundledPath)
        ? artifact.bundledPath
        : path.resolve(artifactRoot, artifact.bundledPath);
    if (!isPathInsideRoot(resolved, artifactRoot)) {
        throw new RuntimeLifecycleError("artifact_path_invalid", "artifact");
    }
    return resolved;
}
async function readArtifactSafely(executablePath) {
    try {
        return await readFile(executablePath);
    }
    catch (error) {
        throw lifecycleErrorForArtifactFs(error);
    }
}
async function assertExecutableUsable(executablePath) {
    let stats;
    try {
        stats = await stat(executablePath);
    }
    catch (error) {
        throw lifecycleErrorForArtifactFs(error);
    }
    if (!stats.isFile()) {
        throw new RuntimeLifecycleError("artifact_not_file", "artifact");
    }
    if (process.platform !== "win32" && (stats.mode & 0o111) === 0) {
        throw new RuntimeLifecycleError("artifact_not_executable", "artifact");
    }
}
function lifecycleErrorForArtifactFs(error) {
    return new RuntimeLifecycleError(reasonCodeForArtifactFs(error), "artifact", {
        cause: error,
    });
}
function reasonCodeForArtifactFs(error) {
    if (!isNodeError(error))
        return "filesystem_error";
    if (error.code === "ENOENT" || error.code === "ENOTDIR") {
        return "artifact_missing";
    }
    if (error.code === "EACCES" || error.code === "EPERM") {
        return "artifact_unreadable";
    }
    return "filesystem_error";
}
function stateFromDiagnostic(diagnostic) {
    if (diagnostic.reasonCode === "metadata_unavailable") {
        return "metadata_unavailable";
    }
    return "rejected";
}
function isPathInsideRoot(candidate, root) {
    const relative = path.relative(path.resolve(root), candidate);
    return (relative === "" ||
        (!relative.startsWith("..") && !path.isAbsolute(relative)));
}
function isNodeError(error) {
    return error instanceof Error && "code" in error;
}
