"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.EnterpriseWorkspaceRepository = exports.ENTERPRISE_WORKSPACE_SCOPED_TABLES = void 0;
exports.repositoryScopeFromRequestContext = repositoryScopeFromRequestContext;
exports.buildWorkspaceScopedWhere = buildWorkspaceScopedWhere;
exports.createEnterpriseWorkspaceRepository = createEnterpriseWorkspaceRepository;
exports.ENTERPRISE_WORKSPACE_SCOPED_TABLES = [
    'trace_assets',
    'trace_processor_leases',
    'analysis_sessions',
    'analysis_runs',
    'conversation_turns',
    'agent_events',
    'analysis_result_snapshots',
    'runtime_snapshots',
    'report_artifacts',
    'memory_entries',
    'skill_registry_entries',
];
const IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
const SCOPE_COLUMNS = new Set(['tenant_id', 'workspace_id']);
const IMMUTABLE_UPDATE_COLUMNS = new Set(['id', 'tenant_id', 'workspace_id']);
const IMMUTABLE_WRITE_COLUMNS = new Set(['id', 'tenant_id', 'workspace_id']);
function assertNonEmptyId(value, name) {
    if (!value.trim()) {
        throw new Error(`${name} is required`);
    }
}
function assertIdentifier(value, label) {
    if (!IDENTIFIER_RE.test(value)) {
        throw new Error(`Invalid ${label}: ${value}`);
    }
}
function assertWorkspaceScopedTable(table) {
    if (!exports.ENTERPRISE_WORKSPACE_SCOPED_TABLES.includes(table)) {
        throw new Error(`Table is not workspace-scoped: ${table}`);
    }
}
function repositoryScopeFromRequestContext(context) {
    return {
        tenantId: context.tenantId,
        workspaceId: context.workspaceId,
        userId: context.userId,
    };
}
function buildWorkspaceScopedWhere(scope, criteria = {}) {
    assertNonEmptyId(scope.tenantId, 'tenantId');
    assertNonEmptyId(scope.workspaceId, 'workspaceId');
    const clauses = [
        'tenant_id = @scopeTenantId',
        'workspace_id = @scopeWorkspaceId',
    ];
    const params = {
        scopeTenantId: scope.tenantId,
        scopeWorkspaceId: scope.workspaceId,
    };
    for (const [column, value] of Object.entries(criteria)) {
        if (value === undefined)
            continue;
        assertIdentifier(column, 'criteria column');
        if (SCOPE_COLUMNS.has(column)) {
            throw new Error(`${column} must come from EnterpriseRepositoryScope`);
        }
        const paramName = `criteria_${column}`;
        clauses.push(`${column} = @${paramName}`);
        params[paramName] = value;
    }
    return {
        sql: clauses.join(' AND '),
        params,
    };
}
function buildOrderClause(options) {
    if (!options.orderBy)
        return '';
    assertIdentifier(options.orderBy, 'orderBy column');
    const direction = options.direction ?? 'ASC';
    if (direction !== 'ASC' && direction !== 'DESC') {
        throw new Error(`Invalid order direction: ${direction}`);
    }
    return ` ORDER BY ${options.orderBy} ${direction}`;
}
function buildLimitClause(options) {
    if (options.limit === undefined)
        return '';
    if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1000) {
        throw new Error('limit must be an integer between 1 and 1000');
    }
    return ` LIMIT ${options.limit}`;
}
function normalizeUpdateValues(values) {
    const assignments = [];
    const params = {};
    for (const [column, value] of Object.entries(values)) {
        if (value === undefined)
            continue;
        assertIdentifier(column, 'update column');
        if (IMMUTABLE_UPDATE_COLUMNS.has(column)) {
            throw new Error(`${column} cannot be updated through a scoped repository`);
        }
        const paramName = `update_${column}`;
        assignments.push(`${column} = @${paramName}`);
        params[paramName] = value;
    }
    if (assignments.length === 0) {
        throw new Error('At least one update value is required');
    }
    return { assignments, params };
}
function normalizeWriteValues(values) {
    const columns = [];
    const placeholders = [];
    const updateAssignments = [];
    const params = {};
    for (const [column, value] of Object.entries(values)) {
        if (value === undefined)
            continue;
        assertIdentifier(column, 'write column');
        if (IMMUTABLE_WRITE_COLUMNS.has(column)) {
            throw new Error(`${column} cannot be written through a scoped repository`);
        }
        const paramName = `write_${column}`;
        columns.push(column);
        placeholders.push(`@${paramName}`);
        updateAssignments.push(`${column} = excluded.${column}`);
        params[paramName] = value;
    }
    if (columns.length === 0) {
        throw new Error('At least one write value is required');
    }
    return { columns, placeholders, updateAssignments, params };
}
class EnterpriseWorkspaceRepository {
    constructor(db, table) {
        this.db = db;
        this.table = table;
        assertWorkspaceScopedTable(table);
    }
    getById(scope, id) {
        assertNonEmptyId(id, 'id');
        const where = buildWorkspaceScopedWhere(scope, { id });
        return this.db.prepare(`
      SELECT * FROM ${this.table}
      WHERE ${where.sql}
      LIMIT 1
    `).get(where.params) ?? null;
    }
    list(scope, criteria = {}, options = {}) {
        const where = buildWorkspaceScopedWhere(scope, criteria);
        return this.db.prepare(`
      SELECT * FROM ${this.table}
      WHERE ${where.sql}${buildOrderClause(options)}${buildLimitClause(options)}
    `).all(where.params);
    }
    updateById(scope, id, values) {
        assertNonEmptyId(id, 'id');
        const where = buildWorkspaceScopedWhere(scope, { id });
        const update = normalizeUpdateValues(values);
        const result = this.db.prepare(`
      UPDATE ${this.table}
      SET ${update.assignments.join(', ')}
      WHERE ${where.sql}
    `).run({
            ...where.params,
            ...update.params,
        });
        return result.changes;
    }
    upsertById(scope, id, values) {
        assertNonEmptyId(id, 'id');
        assertNonEmptyId(scope.tenantId, 'tenantId');
        assertNonEmptyId(scope.workspaceId, 'workspaceId');
        const write = normalizeWriteValues(values);
        const result = this.db.prepare(`
      INSERT INTO ${this.table}
        (id, tenant_id, workspace_id, ${write.columns.join(', ')})
      VALUES
        (@insertId, @scopeTenantId, @scopeWorkspaceId, ${write.placeholders.join(', ')})
      ON CONFLICT(id) DO UPDATE SET
        ${write.updateAssignments.join(', ')}
      WHERE tenant_id = @scopeTenantId
        AND workspace_id = @scopeWorkspaceId
    `).run({
            insertId: id,
            scopeTenantId: scope.tenantId,
            scopeWorkspaceId: scope.workspaceId,
            ...write.params,
        });
        return result.changes;
    }
    deleteById(scope, id) {
        assertNonEmptyId(id, 'id');
        const where = buildWorkspaceScopedWhere(scope, { id });
        const result = this.db.prepare(`
      DELETE FROM ${this.table}
      WHERE ${where.sql}
    `).run(where.params);
        return result.changes;
    }
}
exports.EnterpriseWorkspaceRepository = EnterpriseWorkspaceRepository;
function createEnterpriseWorkspaceRepository(db, table) {
    return new EnterpriseWorkspaceRepository(db, table);
}
//# sourceMappingURL=enterpriseRepository.js.map