"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sha256Hex = sha256Hex;
exports.createStatePatchRunId = createStatePatchRunId;
exports.resolveStatePatchBackupFilePath = resolveStatePatchBackupFilePath;
exports.resolveStateRestoreBackupFilePath = resolveStateRestoreBackupFilePath;
exports.appendStateBackupRecord = appendStateBackupRecord;
exports.readStateRestorableBackupRecords = readStateRestorableBackupRecords;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const global_settings_1 = require("../core/global-settings");
const provider_resolution_1 = require("../platform/provider-resolution");
function sha256Hex(text) {
    return (0, node_crypto_1.createHash)("sha256").update(text, "utf8").digest("hex");
}
function createStatePatchRunId(now) {
    return now.toISOString().replaceAll(/[:.]/gu, "-");
}
function resolveStatePatchBackupFilePath(input) {
    return node_path_1.default.join((0, global_settings_1.getControlPlaneEvidenceRootPath)(input.homeDirectory), "backups", "workflow-state-patches", ...(0, provider_resolution_1.getTrackedProjectStorageSegments)(input.config), `${input.runId}.jsonl`);
}
function resolveStateRestoreBackupFilePath(backupFilePath) {
    return backupFilePath.endsWith(".jsonl")
        ? `${backupFilePath.slice(0, -".jsonl".length)}.restore.jsonl`
        : `${backupFilePath}.restore.jsonl`;
}
async function appendStateBackupRecord(filePath, record) {
    await (0, promises_1.mkdir)(node_path_1.default.dirname(filePath), { recursive: true });
    const file = await (0, promises_1.open)(filePath, "a", 0o600);
    try {
        await file.writeFile(`${JSON.stringify(record)}\n`, "utf8");
    }
    finally {
        await file.close();
    }
}
async function readStateRestorableBackupRecords(filePath) {
    const lines = (await (0, promises_1.readFile)(filePath, "utf8"))
        .split(/\r?\n/u)
        .filter((line) => line.trim().length > 0);
    return lines.flatMap((line, index) => {
        const parsed = JSON.parse(line);
        const record = asBackupRecordCandidate(parsed);
        if (record === null || typeof record.type !== "string") {
            throw new Error(`Invalid backup record at line ${index + 1}.`);
        }
        if (record.type === "comment_backup") {
            return [coerceStatePatchCommentBackupRecord(record, index + 1)];
        }
        if (record.type === "restore_comment_backup") {
            return [coerceStateRestoreCommentBackupRecord(record, index + 1)];
        }
        throw new Error(`Invalid backup record at line ${index + 1}.`);
    });
}
function coerceStatePatchCommentBackupRecord(value, lineNumber) {
    const issue = asBackupIssueCandidate(value.issue);
    const comment = asBackupCommentCandidate(value.comment);
    const state = asBackupStateCandidate(value.state);
    const hashes = asBackupHashesCandidate(value.hashes);
    if (value.schemaVersion !== 1 ||
        typeof value.runId !== "string" ||
        typeof value.sequence !== "number" ||
        typeof value.createdAt !== "string" ||
        issue === null ||
        comment === null ||
        state === null ||
        hashes === null ||
        typeof value.originalBody !== "string" ||
        typeof value.patchedBody !== "string") {
        throw new Error(`Invalid backup record at line ${lineNumber}.`);
    }
    if (typeof issue.id !== "string" ||
        typeof issue.displayId !== "string" ||
        typeof issue.url !== "string" ||
        typeof comment.id !== "string" ||
        typeof comment.url !== "string" ||
        !(typeof comment.updatedAt === "string" || comment.updatedAt === null) ||
        typeof state.fromVersion !== "number" ||
        typeof state.toVersion !== "number" ||
        !Array.isArray(state.appliedPatchVersions) ||
        !state.appliedPatchVersions.every((version) => typeof version === "number") ||
        typeof hashes.originalBodySha256 !== "string" ||
        typeof hashes.patchedBodySha256 !== "string") {
        throw new Error(`Invalid backup record at line ${lineNumber}.`);
    }
    assertBackupBodyHashes({
        originalBody: value.originalBody,
        patchedBody: value.patchedBody,
        originalBodySha256: hashes.originalBodySha256,
        patchedBodySha256: hashes.patchedBodySha256,
    }, lineNumber);
    return {
        type: "comment_backup",
        schemaVersion: 1,
        runId: value.runId,
        sequence: value.sequence,
        createdAt: value.createdAt,
        issue: {
            id: issue.id,
            displayId: issue.displayId,
            url: issue.url,
        },
        comment: {
            id: comment.id,
            url: comment.url,
            updatedAt: comment.updatedAt,
        },
        state: {
            fromVersion: state.fromVersion,
            toVersion: state.toVersion,
            appliedPatchVersions: [...state.appliedPatchVersions],
        },
        hashes: {
            originalBodySha256: hashes.originalBodySha256,
            patchedBodySha256: hashes.patchedBodySha256,
        },
        originalBody: value.originalBody,
        patchedBody: value.patchedBody,
    };
}
function coerceStateRestoreCommentBackupRecord(value, lineNumber) {
    const issue = asBackupIssueCandidate(value.issue);
    const comment = asBackupCommentCandidate(value.comment);
    const hashes = asBackupHashesCandidate(value.hashes);
    if (value.schemaVersion !== 1 ||
        typeof value.runId !== "string" ||
        typeof value.sequence !== "number" ||
        typeof value.createdAt !== "string" ||
        issue === null ||
        comment === null ||
        hashes === null ||
        typeof value.currentBody !== "string" ||
        typeof value.restoredBody !== "string") {
        throw new Error(`Invalid backup record at line ${lineNumber}.`);
    }
    if (typeof issue.id !== "string" ||
        typeof issue.displayId !== "string" ||
        typeof issue.url !== "string" ||
        typeof comment.id !== "string" ||
        typeof comment.url !== "string" ||
        !(typeof comment.updatedAt === "string" || comment.updatedAt === null) ||
        typeof hashes.currentBodySha256 !== "string" ||
        typeof hashes.restoredBodySha256 !== "string") {
        throw new Error(`Invalid backup record at line ${lineNumber}.`);
    }
    assertBackupBodyHashes({
        originalBody: value.currentBody,
        patchedBody: value.restoredBody,
        originalBodySha256: hashes.currentBodySha256,
        patchedBodySha256: hashes.restoredBodySha256,
    }, lineNumber);
    return {
        issue: {
            id: issue.id,
            displayId: issue.displayId,
            url: issue.url,
        },
        comment: {
            id: comment.id,
            url: comment.url,
            updatedAt: comment.updatedAt,
        },
        hashes: {
            originalBodySha256: hashes.currentBodySha256,
            patchedBodySha256: hashes.restoredBodySha256,
        },
        originalBody: value.currentBody,
        patchedBody: value.restoredBody,
    };
}
function assertBackupBodyHashes(input, lineNumber) {
    if (sha256Hex(input.originalBody) !== input.originalBodySha256 ||
        sha256Hex(input.patchedBody) !== input.patchedBodySha256) {
        throw new Error(`Invalid backup record body hash at line ${lineNumber}.`);
    }
}
function asBackupRecordCandidate(value) {
    return isObject(value) ? value : null;
}
function asBackupIssueCandidate(value) {
    return isObject(value) ? value : null;
}
function asBackupCommentCandidate(value) {
    return isObject(value) ? value : null;
}
function asBackupStateCandidate(value) {
    return isObject(value) ? value : null;
}
function asBackupHashesCandidate(value) {
    return isObject(value) ? value : null;
}
function isObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
