import { generateServerId, insertMessage, getMessagesAfterCursor, getMessagesBeforeCursor, getLatestServerId, getLastSyncSeq, setLastSyncSeq, getSessionMessageCount, listSessionsByDevice, listAllSessions, findServerIdByRunAndSender, } from "./database.js";
function buildUserMessageJson(params) {
    const ts = params.timestampMs ?? Date.now();
    return {
        id: params.localId ?? "",
        content: params.text,
        sender: { user: { name: "You" } },
        timestamp: new Date(ts).toISOString(),
        seq: params.seq ?? 0,
        status: "sent",
        isFromCurrentUser: true,
        toolCalls: null,
        attachment: null,
        extraAttachments: params.attachments.length > 0 ? params.attachments.map((url) => ({
            id: "",
            filename: "",
            url,
            isImage: false,
            localData: null,
        })) : null,
        thoughtEvents: null,
        ttsAttachment: null,
        commandTerminals: null,
        isInternalTrace: false,
        modelName: null,
        totalTokens: null,
        contextWindowMax: null,
        contextTokensUsed: null,
        runId: params.runId,
    };
}
/** Store a user message and return its serverId. */
export function storeUserMessage(params) {
    const serverId = generateServerId();
    const msg = buildUserMessageJson(params);
    msg.serverId = serverId;
    // App-side syncStatus is not stored server-side; the server is always "synced"
    insertMessage({
        serverId,
        sessionKey: params.sessionKey,
        deviceId: params.deviceId,
        localId: params.localId ?? null,
        senderType: "user",
        senderName: "You",
        content: params.text,
        timestampMs: params.timestampMs ?? Date.now(),
        seq: params.seq ?? 0,
        status: "sent",
        isFromCurrentUser: true,
        messageJson: JSON.stringify(msg),
        runId: params.runId,
    });
    return serverId;
}
/** Store an assistant message assembled by the accumulator. */
export function storeAssistantMessage(params) {
    const serverId = generateServerId();
    const msg = { ...params.message };
    msg.serverId = serverId;
    msg.isFromCurrentUser = false;
    const content = typeof msg.content === "string" ? msg.content : "";
    const sender = msg.sender;
    const senderName = sender?.assistant?.name ?? "Claude";
    const ts = typeof msg.timestamp === "string" ? Date.parse(msg.timestamp) : Date.now();
    const seq = typeof msg.seq === "number" ? msg.seq : 0;
    insertMessage({
        serverId,
        sessionKey: params.sessionKey,
        deviceId: params.deviceId,
        localId: null,
        senderType: "assistant",
        senderName,
        content,
        timestampMs: Number.isFinite(ts) ? ts : Date.now(),
        seq,
        status: "sent",
        isFromCurrentUser: false,
        messageJson: JSON.stringify(msg),
        runId: params.runId,
    });
    return serverId;
}
/** Paginated history query. Returns messages BEFORE the given cursor (for loading older messages). */
export function getMessagesBefore(sessionKey, beforeServerId, limit) {
    const effectiveLimit = Math.min(Math.max(limit || 25, 1), 50);
    const before = beforeServerId ?? "";
    const jsons = getMessagesBeforeCursor(sessionKey, before, effectiveLimit + 1);
    const hasMore = jsons.length > effectiveLimit;
    const slice = jsons.slice(0, effectiveLimit);
    return {
        messages: slice.map((j) => JSON.parse(j)),
        hasMore,
    };
}
/** Get latest serverId for a session (used as next cursor). */
export function getLatestServerIdForSession(sessionKey) {
    return getLatestServerId(sessionKey);
}
/** Process a sync request from a device. Idempotent by (deviceId, syncSeq). */
export function processSync(params) {
    const { sessionKey, deviceId, syncSeq, lastServerId, messages } = params;
    const did = deviceId.trim().toUpperCase();
    const idMappings = {};
    // Idempotency check
    const lastSeq = getLastSyncSeq(did);
    if (syncSeq <= lastSeq) {
        // Duplicate sync request — still return remote messages
        const after = lastServerId ?? "";
        const jsons = getMessagesAfterCursor(sessionKey, after, 50);
        const remoteMessages = jsons.map((j) => JSON.parse(j));
        return {
            idMappings: {},
            remoteMessages,
            latestServerId: getLatestServerId(sessionKey),
            hasMore: remoteMessages.length >= 50,
            syncSeq,
        };
    }
    // Store pushed messages (app's offline messages)
    for (const msg of messages) {
        const localId = typeof msg.id === "string" ? msg.id : "";
        const fromCurrentUser = msg.isFromCurrentUser === true;
        const msgRunId = typeof msg.runId === "string" ? msg.runId : null;
        // Dedup: if user message with same run_id already exists (stored by POST /messages handler),
        // reuse its serverId instead of creating a duplicate.
        let serverId;
        let skipInsert = false;
        if (msgRunId && fromCurrentUser) {
            const existing = findServerIdByRunAndSender(msgRunId, "user");
            if (existing) {
                serverId = existing;
                skipInsert = true;
            }
            else {
                serverId = generateServerId();
            }
        }
        else {
            serverId = generateServerId();
        }
        idMappings[localId] = serverId;
        if (!skipInsert) {
            const content = typeof msg.content === "string" ? msg.content : "";
            const senderObj = msg.sender;
            const senderName = fromCurrentUser
                ? senderObj?.user?.name ?? "You"
                : senderObj?.assistant?.name ?? "Claude";
            const ts = typeof msg.timestamp === "string"
                ? Date.parse(msg.timestamp)
                : typeof msg.timestamp === "number"
                    ? msg.timestamp
                    : Date.now();
            const seq = typeof msg.seq === "number" ? msg.seq : 0;
            const status = typeof msg.status === "string" ? msg.status : "sent";
            const enriched = { ...msg };
            enriched.serverId = serverId;
            insertMessage({
                serverId,
                sessionKey,
                deviceId: did,
                localId: localId || null,
                senderType: fromCurrentUser ? "user" : "assistant",
                senderName,
                content,
                timestampMs: Number.isFinite(ts) ? ts : Date.now(),
                seq,
                status,
                isFromCurrentUser: fromCurrentUser,
                messageJson: JSON.stringify(enriched),
                runId: msgRunId,
            });
        }
    }
    // Update sync state
    setLastSyncSeq(did, syncSeq);
    // Pull remote messages
    const after = lastServerId ?? "";
    const jsons = getMessagesAfterCursor(sessionKey, after, 50);
    const remoteMessages = jsons.map((j) => JSON.parse(j));
    return {
        idMappings,
        remoteMessages,
        latestServerId: getLatestServerId(sessionKey),
        hasMore: remoteMessages.length >= 50,
        syncSeq,
    };
}
/** Bulk import for first-time migration. Rejects if session already has data. */
export function importMessages(params) {
    const { sessionKey, deviceId, messages } = params;
    if (sessionExistsData(sessionKey)) {
        throw new ConflictError("Session already has data — use POST /sync instead");
    }
    const idMappings = {};
    let imported = 0;
    for (const msg of messages) {
        const localId = typeof msg.id === "string" ? msg.id : "";
        const serverId = generateServerId();
        idMappings[localId] = serverId;
        const content = typeof msg.content === "string" ? msg.content : "";
        const senderObj = msg.sender;
        const isFromCurrentUser = msg.isFromCurrentUser === true;
        const senderName = isFromCurrentUser
            ? senderObj?.user?.name ?? "You"
            : senderObj?.assistant?.name ?? "Claude";
        const ts = typeof msg.timestamp === "string"
            ? Date.parse(msg.timestamp)
            : typeof msg.timestamp === "number"
                ? msg.timestamp
                : Date.now();
        const seq = typeof msg.seq === "number" ? msg.seq : 0;
        const status = typeof msg.status === "string" ? msg.status : "sent";
        const enriched = { ...msg };
        enriched.serverId = serverId;
        insertMessage({
            serverId,
            sessionKey,
            deviceId: deviceId.trim().toUpperCase(),
            localId: localId || null,
            senderType: isFromCurrentUser ? "user" : "assistant",
            senderName,
            content,
            timestampMs: Number.isFinite(ts) ? ts : Date.now(),
            seq,
            status,
            isFromCurrentUser,
            messageJson: JSON.stringify(enriched),
            runId: typeof msg.runId === "string" ? msg.runId : null,
        });
        imported += 1;
    }
    return { idMappings, imported };
}
/** Check if session has any messages in the DB (used by import guard). */
function sessionExistsData(sessionKey) {
    return getSessionMessageCount(sessionKey) > 0;
}
export function listSessions(deviceId) {
    const rows = listSessionsByDevice(deviceId);
    return rows.map(toSessionItem);
}
export function listAllSessionsList() {
    const rows = listAllSessions();
    return rows.map(toSessionItem);
}
function toSessionItem(r) {
    return {
        sessionKey: r.session_key,
        deviceId: r.device_id,
        title: r.title,
        createdAt: r.created_at,
        lastActiveAt: r.last_active_at,
        messageCount: r.message_count,
        latestServerId: r.latest_msg_id,
    };
}
export class ConflictError extends Error {
    constructor(message) {
        super(message);
        this.name = "ConflictError";
    }
}
