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());
    });
};
import { Filters, Sorts } from "adminforth";
import { randomUUID } from "crypto";
export const AGENT_SYSTEM_TURN_PROMPT = "__adminforth_system_message__";
export const STEER_PROMPT_MARKER = "__adminforth_steer__:";
export class AgentSessionStore {
    constructor(getAdminforth, options) {
        this.getAdminforth = getAdminforth;
        this.options = options;
    }
    createNewTurn(sessionId, prompt, response) {
        return __awaiter(this, void 0, void 0, function* () {
            const turnId = randomUUID();
            const turnRecord = {
                [this.options.turnResource.idField]: turnId,
                [this.options.turnResource.sessionIdField]: sessionId,
                [this.options.turnResource.promptField]: prompt,
                [this.options.turnResource.responseField]: response !== null && response !== void 0 ? response : "not_finished",
            };
            const newTurn = yield this.getAdminforth().resource(this.options.turnResource.resourceId).create(turnRecord);
            return newTurn.createdRecord[this.options.turnResource.idField];
        });
    }
    createSystemTurn(sessionId, systemMessage) {
        return __awaiter(this, void 0, void 0, function* () {
            const turnId = randomUUID();
            const turnRecord = {
                [this.options.turnResource.idField]: turnId,
                [this.options.turnResource.sessionIdField]: sessionId,
                [this.options.turnResource.promptField]: AGENT_SYSTEM_TURN_PROMPT,
                [this.options.turnResource.responseField]: systemMessage,
            };
            const newTurn = yield this.getAdminforth().resource(this.options.turnResource.resourceId).create(turnRecord);
            return newTurn.createdRecord[this.options.turnResource.idField];
        });
    }
    /**
     * Append a mid-turn steer to the current (running) turn's prompt instead of creating a
     * new record. Targets the latest non-system turn so a steer attaches to the real
     * conversation turn, never an audio/system note.
     */
    appendSteerToCurrentTurn(sessionId, steerText) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
            const turn = turns.find((candidate) => candidate[this.options.turnResource.promptField] !== AGENT_SYSTEM_TURN_PROMPT);
            if (!turn) {
                return;
            }
            const currentPrompt = (_a = turn[this.options.turnResource.promptField]) !== null && _a !== void 0 ? _a : "";
            yield this.getAdminforth().resource(this.options.turnResource.resourceId).update(turn[this.options.turnResource.idField], {
                [this.options.turnResource.promptField]: `${currentPrompt}${STEER_PROMPT_MARKER}${steerText}`,
            });
        });
    }
    getSessionTurns(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField), Sorts.ASC(this.options.turnResource.idField)]);
            return turns.map(turn => ({
                id: turn[this.options.turnResource.idField],
                prompt: turn[this.options.turnResource.promptField],
                response: turn[this.options.turnResource.responseField],
            }));
        });
    }
    /**
     * Ordered non-system (real conversation) turns with their stored checkpoint id.
     * Used by message editing to locate the target turn and its previous turn's
     * fork point. Stable order (`createdAt ASC, id ASC`) so positions never drift.
     */
    getAgentTurns(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const checkpointIdField = this.options.turnResource.checkpointIdField;
            const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField), Sorts.ASC(this.options.turnResource.idField)]);
            return turns
                .filter((turn) => turn[this.options.turnResource.promptField] !== AGENT_SYSTEM_TURN_PROMPT)
                .map((turn) => ({
                id: turn[this.options.turnResource.idField],
                prompt: turn[this.options.turnResource.promptField],
                response: turn[this.options.turnResource.responseField],
                checkpointId: checkpointIdField
                    ? (turn[checkpointIdField] ? String(turn[checkpointIdField]) : null)
                    : null,
            }));
        });
    }
    /**
     * Apply a message edit and branch: replace the edited turn's prompt and truncate
     * everything after it. Deletes later turns FIRST, then updates the edited turn, so
     * an interruption can only leave a consistent "truncated, edit-not-yet-applied"
     * state (recoverable by retry) — never "edited but stale later turns remain".
     * (AdminForth's resource API has no transactions, hence the deliberate ordering.)
     */
    editTurnAndTruncateAfter(input) {
        return __awaiter(this, void 0, void 0, function* () {
            const r = this.options.turnResource;
            const resource = this.getAdminforth().resource(r.resourceId);
            const turns = yield resource.list([Filters.EQ(r.sessionIdField, input.sessionId)], undefined, undefined, [Sorts.ASC(r.createdAtField), Sorts.ASC(r.idField)]);
            const targetIndex = turns.findIndex((turn) => turn[r.idField] === input.turnId);
            if (targetIndex === -1) {
                throw new Error(`Turn "${input.turnId}" not found in session "${input.sessionId}".`);
            }
            const laterTurns = turns.slice(targetIndex + 1);
            for (const turn of laterTurns) {
                yield resource.delete(turn[r.idField]);
            }
            const updates = {
                [r.promptField]: input.newPrompt,
                [r.responseField]: "not_finished",
            };
            if (r.checkpointIdField) {
                updates[r.checkpointIdField] = null;
            }
            yield resource.update(input.turnId, updates);
        });
    }
    getPreviousUserMessages(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], 2, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
            return turns
                .reverse()
                .filter((turn) => turn[this.options.turnResource.promptField] !== AGENT_SYSTEM_TURN_PROMPT)
                .map((turn) => ({
                text: turn[this.options.turnResource.promptField],
            }));
        });
    }
    getLatestTurn(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], 1, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
            return turns[0];
        });
    }
    getChatSurfaceSessionId(incoming) {
        return `${incoming.surface}:${incoming.externalConversationId}`;
    }
    getOrCreateChatSurfaceSession(incoming, adminUser) {
        return __awaiter(this, void 0, void 0, function* () {
            const sessionId = this.getChatSurfaceSessionId(incoming);
            const sessionResource = this.getAdminforth().resource(this.options.sessionResource.resourceId);
            const session = yield sessionResource.get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
            if (session) {
                return sessionId;
            }
            yield sessionResource.create({
                [this.options.sessionResource.idField]: sessionId,
                [this.options.sessionResource.titleField]: incoming.prompt.slice(0, 40) || "New Session",
                [this.options.sessionResource.askerIdField]: adminUser.pk,
            });
            return sessionId;
        });
    }
    touchSession(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.getAdminforth().resource(this.options.sessionResource.resourceId).update(sessionId, {
                [this.options.sessionResource.createdAtField]: new Date().toISOString(),
            });
        });
    }
    saveTurnResponse(input) {
        return __awaiter(this, void 0, void 0, function* () {
            const turnUpdates = {
                [this.options.turnResource.responseField]: input.responseText,
            };
            if (this.options.turnResource.debugField) {
                turnUpdates[this.options.turnResource.debugField] = input.debugHistory;
            }
            if (this.options.turnResource.checkpointIdField && input.checkpointId !== undefined) {
                turnUpdates[this.options.turnResource.checkpointIdField] = input.checkpointId;
            }
            yield this.getAdminforth()
                .resource(this.options.turnResource.resourceId)
                .update(input.turnId, turnUpdates);
        });
    }
    getResumeState(sessionId) {
        return __awaiter(this, void 0, void 0, function* () {
            const latestTurn = yield this.getLatestTurn(sessionId);
            if (!latestTurn) {
                throw new Error(`No agent turn found for session "${sessionId}".`);
            }
            const response = latestTurn[this.options.turnResource.responseField];
            return {
                turnId: latestTurn[this.options.turnResource.idField],
                initialResponse: response === "not_finished" ? "" : String(response),
            };
        });
    }
}
