"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalysisResultWindowStateRepository = void 0;
exports.createAnalysisResultWindowStateRepository = createAnalysisResultWindowStateRepository;
const DEFAULT_WINDOW_STATE_TTL_MS = 2 * 60 * 1000;
function parseJson(value, fallback) {
    if (!value)
        return fallback;
    try {
        return JSON.parse(value);
    }
    catch {
        return fallback;
    }
}
function optionalText(value) {
    const trimmed = value?.trim();
    return trimmed ? trimmed : null;
}
function boundedLimit(limit) {
    if (limit === undefined)
        return 20;
    if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
        throw new Error('limit must be an integer between 1 and 100');
    }
    return limit;
}
function ttlFromInput(ttlMs) {
    if (ttlMs === undefined)
        return DEFAULT_WINDOW_STATE_TTL_MS;
    if (!Number.isFinite(ttlMs) || ttlMs < 15000 || ttlMs > 10 * 60 * 1000) {
        throw new Error('ttlMs must be between 15000 and 600000');
    }
    return Math.floor(ttlMs);
}
function ensureWindowStateScopeGraph(db, scope, now) {
    db.prepare(`
    INSERT OR IGNORE INTO organizations (id, name, status, plan, created_at, updated_at)
    VALUES (?, ?, 'active', 'enterprise', ?, ?)
  `).run(scope.tenantId, scope.tenantId, now, now);
    db.prepare(`
    INSERT OR IGNORE INTO workspaces (id, tenant_id, name, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?)
  `).run(scope.workspaceId, scope.tenantId, scope.workspaceId, now, now);
    if (!scope.userId)
        return;
    db.prepare(`
    INSERT INTO users (id, tenant_id, email, display_name, idp_subject, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
      email = excluded.email,
      display_name = excluded.display_name,
      updated_at = excluded.updated_at
  `).run(scope.userId, scope.tenantId, `${scope.userId}@window-state.local`, scope.userId, `window-state:${scope.userId}`, now, now);
}
function mapWindowState(row) {
    return {
        tenantId: row.tenant_id,
        workspaceId: row.workspace_id,
        windowId: row.window_id,
        ...(row.user_id ? { userId: row.user_id } : {}),
        ...(row.trace_id ? { traceId: row.trace_id } : {}),
        ...(row.backend_trace_id ? { backendTraceId: row.backend_trace_id } : {}),
        ...(row.active_session_id ? { activeSessionId: row.active_session_id } : {}),
        ...(row.latest_snapshot_id ? { latestSnapshotId: row.latest_snapshot_id } : {}),
        ...(row.trace_title ? { traceTitle: row.trace_title } : {}),
        ...(row.scene_type ? { sceneType: row.scene_type } : {}),
        metadata: parseJson(row.metadata_json, {}),
        updatedAt: row.updated_at,
        expiresAt: row.expires_at,
    };
}
class AnalysisResultWindowStateRepository {
    constructor(db) {
        this.db = db;
    }
    scopeGraphExists(scope) {
        const row = this.db.prepare(`
      SELECT 1
      FROM organizations o
      JOIN workspaces w
        ON w.tenant_id = o.id
       AND w.id = @workspaceId
      WHERE o.id = @tenantId
      LIMIT 1
    `).get({
            tenantId: scope.tenantId,
            workspaceId: scope.workspaceId,
        });
        return Boolean(row);
    }
    upsertWindowState(scope, input, options = {}) {
        const persist = this.db.transaction(() => {
            const now = Date.now();
            if (options.ensureScopeGraph) {
                ensureWindowStateScopeGraph(this.db, scope, now);
            }
            const ttlMs = ttlFromInput(input.ttlMs);
            const params = {
                tenantId: scope.tenantId,
                workspaceId: scope.workspaceId,
                windowId: input.windowId,
                userId: optionalText(input.userId ?? scope.userId),
                traceId: optionalText(input.traceId),
                backendTraceId: optionalText(input.backendTraceId),
                activeSessionId: optionalText(input.activeSessionId),
                latestSnapshotId: optionalText(input.latestSnapshotId),
                traceTitle: optionalText(input.traceTitle),
                sceneType: optionalText(input.sceneType),
                metadataJson: JSON.stringify(input.metadata ?? {}),
                updatedAt: now,
                expiresAt: now + ttlMs,
            };
            this.db.prepare(`
        INSERT INTO analysis_result_window_states
          (tenant_id, workspace_id, window_id, user_id, trace_id, backend_trace_id,
           active_session_id, latest_snapshot_id, trace_title, scene_type,
           metadata_json, updated_at, expires_at)
        VALUES
          (@tenantId, @workspaceId, @windowId, @userId, @traceId, @backendTraceId,
           @activeSessionId, @latestSnapshotId, @traceTitle, @sceneType,
           @metadataJson, @updatedAt, @expiresAt)
        ON CONFLICT(tenant_id, workspace_id, window_id) DO UPDATE SET
          user_id = excluded.user_id,
          trace_id = excluded.trace_id,
          backend_trace_id = excluded.backend_trace_id,
          active_session_id = excluded.active_session_id,
          latest_snapshot_id = excluded.latest_snapshot_id,
          trace_title = excluded.trace_title,
          scene_type = excluded.scene_type,
          metadata_json = excluded.metadata_json,
          updated_at = excluded.updated_at,
          expires_at = excluded.expires_at
      `).run(params);
            const state = this.getWindowState(scope, input.windowId);
            if (!state) {
                throw new Error('Failed to persist analysis result window state');
            }
            return state;
        });
        return persist();
    }
    getWindowState(scope, windowId) {
        const row = this.db.prepare(`
      SELECT *
      FROM analysis_result_window_states
      WHERE tenant_id = @tenantId
        AND workspace_id = @workspaceId
        AND window_id = @windowId
      LIMIT 1
    `).get({
            tenantId: scope.tenantId,
            workspaceId: scope.workspaceId,
            windowId,
        });
        return row ? mapWindowState(row) : null;
    }
    listActiveWindowStates(scope, options = {}) {
        const clauses = [
            'tenant_id = @tenantId',
            'workspace_id = @workspaceId',
            'expires_at > @now',
        ];
        const params = {
            tenantId: scope.tenantId,
            workspaceId: scope.workspaceId,
            now: options.now ?? Date.now(),
            limit: boundedLimit(options.limit),
        };
        if (options.excludeWindowId) {
            clauses.push('window_id != @excludeWindowId');
            params.excludeWindowId = options.excludeWindowId;
        }
        return this.db.prepare(`
      SELECT *
      FROM analysis_result_window_states
      WHERE ${clauses.join(' AND ')}
      ORDER BY updated_at DESC, window_id ASC
      LIMIT @limit
    `).all(params).map(mapWindowState);
    }
}
exports.AnalysisResultWindowStateRepository = AnalysisResultWindowStateRepository;
function createAnalysisResultWindowStateRepository(db) {
    return new AnalysisResultWindowStateRepository(db);
}
//# sourceMappingURL=analysisResultWindowStateStore.js.map