import { createHash } from "node:crypto";
export function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function cleanList(values) {
    return [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
}
export function byteLength(value) {
    return Buffer.byteLength(value, "utf8");
}
export function sha256Text(value) {
    return createHash("sha256").update(value, "utf8").digest("hex");
}
export function normalizeRelativeTargetPath(targetPath, toolId) {
    if (typeof targetPath !== "string" || targetPath.trim().length === 0) {
        return { ok: false, code: "MISSING_TARGET_PATH", message: `${toolId} requires a targetPath` };
    }
    const normalized = targetPath.trim().replaceAll("\\", "/").replace(/\/+/g, "/");
    const parts = normalized.split("/");
    if (normalized.length === 0 ||
        normalized.includes("\0") ||
        normalized.startsWith("/") ||
        parts.some((part) => part === "..")) {
        return { ok: false, code: "TARGET_OUT_OF_SCOPE", message: `${toolId} targetPath must stay inside the workspace scope` };
    }
    const path = parts.filter((part) => part !== "." && part.length > 0).join("/");
    if (path.length === 0) {
        return { ok: false, code: "TARGET_OUT_OF_SCOPE", message: `${toolId} targetPath must stay inside the workspace scope` };
    }
    return { ok: true, path };
}
export function resolveScopes(requestedScopes, allowedScopes, toolId) {
    const requested = cleanList(requestedScopes);
    const allowed = cleanList(allowedScopes);
    if (requested.length === 0) {
        return { ok: true, acceptedScopes: [] };
    }
    const denied = requested.filter((scope) => !allowed.includes(scope));
    if (denied.length > 0) {
        return { ok: false, code: "SCOPE_DENIED", message: `${toolId} scope ${denied[0]} is outside runtime governance` };
    }
    return { ok: true, acceptedScopes: requested };
}
export function shouldDryRun(requestDryRun, contextDryRun) {
    return requestDryRun !== false && contextDryRun !== false;
}
export function gateRejected(...gates) {
    return gates.find((gate) => gate?.accepted === false || gate?.allowed === false);
}
export function hasExecutionApproval(...gates) {
    return gates.some((gate) => gate?.accepted === true || gate?.allowed === true);
}
export function providerFailureMessage(toolId) {
    return `${toolId} provider failed while applying the runtime-supported operation`;
}
export function providerUnavailableMessage(toolId, capability) {
    return `${toolId} requires runtime ${capability} support for non-dry-run execution`;
}
export function replaceOccurrences(content, searchText, replacementText, occurrence, maxReplacements) {
    if (searchText.length === 0) {
        return { content, replacements: 0 };
    }
    let replacements = 0;
    let offset = 0;
    let nextContent = "";
    const limit = occurrence === "first" ? 1 : maxReplacements;
    while (replacements < limit) {
        const index = content.indexOf(searchText, offset);
        if (index < 0) {
            break;
        }
        nextContent += content.slice(offset, index) + replacementText;
        offset = index + searchText.length;
        replacements += 1;
    }
    if (replacements === 0) {
        return { content, replacements };
    }
    return { content: nextContent + content.slice(offset), replacements };
}
export function deleteLineRange(content, range) {
    const lines = content.split(/\r?\n/);
    const startIndex = Math.max(0, range.startLine - 1);
    const endExclusive = Math.min(lines.length, range.endLine);
    const deletedLines = Math.max(0, endExclusive - startIndex);
    const nextLines = [...lines.slice(0, startIndex), ...lines.slice(endExclusive)];
    return { content: nextLines.join("\n"), deletedLines };
}
export function isValidPositiveRange(range) {
    return (range !== undefined &&
        Number.isInteger(range.startLine) &&
        Number.isInteger(range.endLine) &&
        range.startLine > 0 &&
        range.endLine >= range.startLine);
}
function offsetForPosition(content, position) {
    const lines = content.split(/\n/);
    let offset = 0;
    for (let line = 0; line < Math.min(position.line, lines.length); line += 1) {
        offset += lines[line].length + 1;
    }
    return Math.min(content.length, offset + Math.max(0, position.character));
}
export function applyLspTextEdits(content, edits) {
    const sorted = [...edits].sort((left, right) => {
        if (left.range.start.line !== right.range.start.line) {
            return right.range.start.line - left.range.start.line;
        }
        return right.range.start.character - left.range.start.character;
    });
    let nextContent = content;
    for (const edit of sorted) {
        const start = offsetForPosition(nextContent, edit.range.start);
        const end = offsetForPosition(nextContent, edit.range.end);
        nextContent = `${nextContent.slice(0, start)}${edit.newText}${nextContent.slice(end)}`;
    }
    return nextContent;
}
