var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var g = generator.apply(thisArg, _arguments || []), i, q = [];
    return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
    function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
    function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
    function fulfill(value) { resume("next", value); }
    function reject(value) { resume("throw", value); }
    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
import { BaseCheckpointSaver, WRITES_IDX_MAP, } from "@langchain/langgraph-checkpoint";
import { Filters } from "adminforth";
const ROOT_CHECKPOINT_NAMESPACE = "__root__";
export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
    constructor(adminforth, pluginOptions) {
        super();
        this.adminforth = adminforth;
        this.pluginOptions = pluginOptions;
    }
    get resourceConfig() {
        const resource = this.pluginOptions.checkpointResource;
        if (!resource) {
            throw new Error("checkpointResource is not configured");
        }
        return resource;
    }
    resource() {
        return this.adminforth.resource(this.resourceConfig.resourceId);
    }
    serialize(value) {
        if (value === undefined || value === null)
            return null;
        return JSON.stringify(value);
    }
    deserialize(value) {
        if (value === undefined || value === null)
            return null;
        if (typeof value === "string") {
            return JSON.parse(value);
        }
        return value;
    }
    now() {
        return new Date().toISOString();
    }
    encodeCheckpointNamespace(checkpointNs) {
        return checkpointNs === "" ? ROOT_CHECKPOINT_NAMESPACE : checkpointNs;
    }
    decodeCheckpointNamespace(checkpointNs) {
        return checkpointNs === ROOT_CHECKPOINT_NAMESPACE ? "" : String(checkpointNs !== null && checkpointNs !== void 0 ? checkpointNs : "");
    }
    getConfigValues(config) {
        var _a, _b, _c;
        const configurable = ((_a = config.configurable) !== null && _a !== void 0 ? _a : {});
        return {
            threadId: String((_b = configurable.thread_id) !== null && _b !== void 0 ? _b : ""),
            checkpointNs: String((_c = configurable.checkpoint_ns) !== null && _c !== void 0 ? _c : ""),
            checkpointId: configurable.checkpoint_id
                ? String(configurable.checkpoint_id)
                : null,
        };
    }
    buildConfig(threadId, checkpointNs, checkpointId) {
        return {
            configurable: {
                thread_id: threadId,
                checkpoint_ns: checkpointNs,
                checkpoint_id: checkpointId,
            },
        };
    }
    buildCheckpointRowId(threadId, checkpointNs, checkpointId) {
        return `cp:${threadId}:${checkpointNs}:${checkpointId}`;
    }
    buildWritesRowId(threadId, checkpointNs, checkpointId, taskId, seq) {
        return `wr:${threadId}:${checkpointNs}:${checkpointId}:${taskId}:${seq}`;
    }
    getWriteIndex(channel, index) {
        var _a;
        return (_a = WRITES_IDX_MAP[channel]) !== null && _a !== void 0 ? _a : index;
    }
    isDuplicateCheckpointWriteError(error) {
        return error instanceof Error && error.message.includes("UNIQUE constraint failed");
    }
    put(config, checkpoint, metadata, _newVersions) {
        return __awaiter(this, void 0, void 0, function* () {
            const r = this.resourceConfig;
            const { threadId, checkpointNs } = this.getConfigValues(config);
            const checkpointId = String(checkpoint.id);
            const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
            const parentCheckpointId = this.getConfigValues(config).checkpointId;
            const createdAt = this.now();
            yield this.resource().create({
                [r.idField]: this.buildCheckpointRowId(threadId, storedCheckpointNs, checkpointId),
                [r.threadIdField]: threadId,
                [r.checkpointNamespaceField]: storedCheckpointNs,
                [r.checkpointIdField]: checkpointId,
                [r.parentCheckpointIdField]: parentCheckpointId,
                [r.rowKindField]: "checkpoint",
                [r.taskIdField]: null,
                [r.sequenceField]: 0,
                [r.createdAtField]: createdAt,
                [r.checkpointPayloadField]: this.serialize(checkpoint),
                [r.metadataPayloadField]: this.serialize(metadata),
                [r.writesPayloadField]: null,
                [r.schemaVersionField]: 1,
            });
            return this.buildConfig(threadId, checkpointNs, checkpointId);
        });
    }
    putWrites(config, writes, taskId) {
        return __awaiter(this, void 0, void 0, function* () {
            const r = this.resourceConfig;
            const { threadId, checkpointNs, checkpointId } = this.getConfigValues(config);
            const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
            if (!checkpointId) {
                throw new Error("putWrites requires checkpoint_id in config");
            }
            const createdAt = this.now();
            yield Promise.all(writes.map((_a, index_1) => __awaiter(this, [_a, index_1], void 0, function* ([channel, value], index) {
                const writeIndex = this.getWriteIndex(channel, index);
                try {
                    yield this.resource().create({
                        [r.idField]: this.buildWritesRowId(threadId, storedCheckpointNs, checkpointId, taskId, writeIndex),
                        [r.threadIdField]: threadId,
                        [r.checkpointNamespaceField]: storedCheckpointNs,
                        [r.checkpointIdField]: checkpointId,
                        [r.parentCheckpointIdField]: null,
                        [r.rowKindField]: "writes",
                        [r.taskIdField]: taskId,
                        [r.sequenceField]: writeIndex,
                        [r.createdAtField]: createdAt,
                        [r.checkpointPayloadField]: null,
                        [r.metadataPayloadField]: null,
                        [r.writesPayloadField]: this.serialize([channel, value]),
                        [r.schemaVersionField]: 1,
                    });
                }
                catch (error) {
                    if (!this.isDuplicateCheckpointWriteError(error)) {
                        throw error;
                    }
                }
            })));
        });
    }
    getTuple(config) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const r = this.resourceConfig;
            const { threadId, checkpointNs, checkpointId } = this.getConfigValues(config);
            const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
            const checkpointRows = yield this.resource().list(checkpointId
                ? Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.checkpointIdField, checkpointId), Filters.EQ(r.rowKindField, "checkpoint"))
                : Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.rowKindField, "checkpoint")), 1, undefined, [{ field: r.checkpointIdField, direction: "desc" }]);
            const checkpointRow = checkpointRows[0];
            if (!checkpointRow) {
                return undefined;
            }
            const resolvedCheckpointId = String(checkpointRow[r.checkpointIdField]);
            const writesRows = yield this.resource().list(Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs), Filters.EQ(r.checkpointIdField, resolvedCheckpointId), Filters.EQ(r.rowKindField, "writes")), undefined, undefined, [{ field: r.sequenceField, direction: "asc" }]);
            const pendingWrites = writesRows.flatMap((row) => {
                var _a;
                const taskId = String((_a = row[r.taskIdField]) !== null && _a !== void 0 ? _a : "");
                const write = this.deserialize(row[r.writesPayloadField]);
                if (!write) {
                    return [];
                }
                const [channel, value] = write;
                return [[taskId, channel, value]];
            });
            const parentCheckpointId = checkpointRow[r.parentCheckpointIdField]
                ? String(checkpointRow[r.parentCheckpointIdField])
                : null;
            const tuple = {
                config: this.buildConfig(threadId, checkpointNs, resolvedCheckpointId),
                checkpoint: this.deserialize(checkpointRow[r.checkpointPayloadField]),
                metadata: ((_a = this.deserialize(checkpointRow[r.metadataPayloadField])) !== null && _a !== void 0 ? _a : {}),
                parentConfig: parentCheckpointId
                    ? this.buildConfig(threadId, checkpointNs, parentCheckpointId)
                    : undefined,
                pendingWrites,
            };
            return tuple;
        });
    }
    list(config, options) {
        return __asyncGenerator(this, arguments, function* list_1() {
            const r = this.resourceConfig;
            const { threadId, checkpointNs } = this.getConfigValues(config);
            const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
            const beforeCheckpointId = (options === null || options === void 0 ? void 0 : options.before)
                ? this.getConfigValues(options.before).checkpointId
                : null;
            const filters = [
                Filters.EQ(r.rowKindField, "checkpoint"),
                Filters.EQ(r.threadIdField, threadId),
                Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs),
            ];
            if (beforeCheckpointId) {
                filters.push(Filters.LT(r.checkpointIdField, beforeCheckpointId));
            }
            const rows = yield __await(this.resource().list(Filters.AND(...filters), options === null || options === void 0 ? void 0 : options.limit, undefined, [{ field: r.checkpointIdField, direction: "desc" }]));
            for (const row of rows) {
                const tuple = yield __await(this.getTuple(this.buildConfig(String(row[r.threadIdField]), this.decodeCheckpointNamespace(row[r.checkpointNamespaceField]), String(row[r.checkpointIdField]))));
                if (tuple) {
                    yield yield __await(tuple);
                }
            }
        });
    }
    deleteThread(threadId_1) {
        return __awaiter(this, arguments, void 0, function* (threadId, checkpointNs = "") {
            const r = this.resourceConfig;
            const storedCheckpointNs = this.encodeCheckpointNamespace(checkpointNs);
            const rows = yield this.resource().list(Filters.AND(Filters.EQ(r.threadIdField, threadId), Filters.EQ(r.checkpointNamespaceField, storedCheckpointNs)), undefined, undefined, [{ field: r.createdAtField, direction: "desc" }]);
            for (const row of rows) {
                yield this.adminforth
                    .resource(this.pluginOptions.checkpointResource.resourceId)
                    .delete(row[r.idField]);
            }
        });
    }
}
