import os from "node:os";
import path from "node:path";
export class CheatFilter {
    cloneDir;
    allowedPaths;
    violations = [];
    uninspectable = [];
    observedEvidence = new Set();
    constructor(input) {
        this.cloneDir = path.resolve(input.cloneDir);
        this.allowedPaths = defaultAllowedPaths()
            .concat(input.allowedPaths ?? [])
            .map((allowedPath) => path.resolve(allowedPath));
    }
    onEvent(event) {
        if (!isObservedOperation(event.operation) || !canContainNewEvidence(event)) {
            return;
        }
        if (event.paths.length > 0) {
            this.clearResolvedMissingPath(event);
        }
        if (event.inspection?.status === "uninspectable" &&
            isUninspectableOperation(event.operation) &&
            this.recordEvidence(event, `inspection:${event.inspection.reason}`)) {
            this.uninspectable.push({
                toolCall: event.name,
                operation: event.operation,
                reason: event.inspection.reason,
                evidenceKey: this.evidenceKey(event, `inspection:${event.inspection.reason}`)
            });
        }
        for (const eventPath of event.paths) {
            const resolvedPath = resolveAgainstClone(this.cloneDir, eventPath);
            if (isUnderAny(resolvedPath, [this.cloneDir, ...this.allowedPaths])) {
                continue;
            }
            if (!this.recordEvidence(event, `path:${resolvedPath}`)) {
                continue;
            }
            this.violations.push({
                path: resolvedPath,
                toolCall: event.name,
                reason: "outside-clone"
            });
        }
    }
    recordEvidence(event, evidence) {
        const evidenceKey = this.evidenceKey(event, evidence);
        if (this.observedEvidence.has(evidenceKey)) {
            return false;
        }
        this.observedEvidence.add(evidenceKey);
        return true;
    }
    clearResolvedMissingPath(event) {
        const missingPathKey = this.evidenceKey(event, "inspection:missing-path");
        if (!this.observedEvidence.delete(missingPathKey)) {
            return;
        }
        const index = this.uninspectable.findIndex((action) => action.evidenceKey === missingPathKey);
        if (index !== -1) {
            this.uninspectable.splice(index, 1);
        }
    }
    evidenceKey(event, evidence) {
        return `${event.id ?? `sequence:${event.sequence}`}:${evidence}`;
    }
    report() {
        return {
            cheated: this.violations.length > 0,
            violations: this.violations.slice(),
            ...(this.uninspectable.length === 0
                ? {}
                : {
                    uninspectable: this.uninspectable.map(({ evidenceKey: _evidenceKey, ...action }) => action)
                })
        };
    }
}
function canContainNewEvidence(event) {
    return (event.id !== undefined ||
        event.phase === "start" ||
        event.paths.length > 0 ||
        event.inspection !== undefined);
}
function defaultAllowedPaths() {
    const allowedPaths = [
        os.tmpdir(),
        path.join(os.homedir(), ".cache"),
        "/usr/bin",
        "/usr/local/bin",
        "/bin"
    ];
    if (process.platform === "darwin") {
        allowedPaths.push("/opt/homebrew/bin");
    }
    return allowedPaths;
}
function isObservedOperation(operation) {
    return (operation === "read" ||
        operation === "search" ||
        operation === "exec" ||
        operation === "edit" ||
        operation === "write" ||
        operation === "mcp");
}
function isUninspectableOperation(operation) {
    return isObservedOperation(operation);
}
function resolveAgainstClone(cloneDir, targetPath) {
    if (path.isAbsolute(targetPath)) {
        return path.resolve(targetPath);
    }
    return path.resolve(cloneDir, targetPath);
}
function isUnderAny(targetPath, roots) {
    return roots.some((root) => isUnderPath(targetPath, root));
}
function isUnderPath(targetPath, rootPath) {
    const relative = path.relative(rootPath, targetPath);
    return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
