"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.boardsService = exports.BoardsService = exports.BOARD_STATUS = void 0;
const drizzle_orm_1 = require("drizzle-orm");
const database_1 = require("../../../database");
const tables_1 = require("../../../database/tables");
const logger_1 = __importDefault(require("../../../logger"));
const uuid_1 = require("uuid");
const callControlPlaneApi_1 = require("../../../util/callContolPlane/callControlPlaneApi");
const constants_1 = require("../../../constants");
exports.BOARD_STATUS = {
    DRAFT: 0,
    ACTIVE: 1,
    ARCHIVED: 2,
};
class BoardsService {
    constructor() {
        this.TAG_MAP = {
            'violin': 'Violin Chart',
            'treemap': 'Treemap Chart',
            'stacked': 'Stacked Bar Chart',
            'table': 'Table',
            'donut': 'Donut Chart',
            'histogram': 'Histogram',
            'stat': 'Stat Card',
            'stat-card': 'Stat Card',
            'bar': 'Bar Chart',
            'multi-line': 'Multi-line Chart',
            'radar': 'Radar Chart',
            'line': 'Line Chart',
        };
    }
    async create(input) {
        const db = (0, database_1.getDb)();
        const id = (0, uuid_1.v4)();
        const [board] = await db
            .insert(tables_1.boardsTable)
            .values({
            id,
            name: input.name,
            description: input.description ?? null,
            status: exports.BOARD_STATUS.ACTIVE,
            applicationId: input.applicationId ?? null,
            projectId: input.projectId,
            workspaceId: input.workspaceId,
            createdBy: input.createdBy,
        })
            .returning();
        await db.insert(tables_1.boardUserMappingTable).values({
            boardId: id,
            userId: input.createdBy,
            role: 'creator',
        });
        logger_1.default.info(`Created board ${id} for project ${input.projectId}`);
        return board;
    }
    async isUserMemberOfBoard(boardId, userId) {
        const db = (0, database_1.getDb)();
        const [row] = await db
            .select({ boardId: tables_1.boardUserMappingTable.boardId })
            .from(tables_1.boardUserMappingTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.boardId, boardId), (0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.userId, userId)))
            .limit(1);
        return !!row;
    }
    async getBoardMembers(boardId, projectId, workspaceId, requestedByUserId) {
        const db = (0, database_1.getDb)();
        const [requestingUserRow] = await db
            .select({ role: tables_1.boardUserMappingTable.role })
            .from(tables_1.boardUserMappingTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.boardId, boardId), (0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.userId, requestedByUserId)))
            .limit(1);
        const isMember = !!requestingUserRow;
        if (!isMember) {
            return [];
        }
        const requestingUserRole = requestingUserRow?.role ?? null;
        const canRemove = requestingUserRole === 'creator';
        const [board] = await db
            .select({ id: tables_1.boardsTable.id, workspaceId: tables_1.boardsTable.workspaceId, projectId: tables_1.boardsTable.projectId })
            .from(tables_1.boardsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)))
            .limit(1);
        if (!board) {
            return [];
        }
        const rows = await db
            .select({
            userId: tables_1.boardUserMappingTable.userId,
            role: tables_1.boardUserMappingTable.role,
        })
            .from(tables_1.boardUserMappingTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.boardId, boardId), (0, drizzle_orm_1.ne)(tables_1.boardUserMappingTable.userId, requestedByUserId)));
        const members = rows.map((r) => ({ userId: r.userId, role: r.role }));
        const memberIds = members.map((m) => m.userId);
        const membersList = await (0, callControlPlaneApi_1.callControlPlane)(`/organizations/${board?.workspaceId}/board/memberships`, {
            method: 'GET',
            params: {
                userIds: memberIds,
                orgId: board?.workspaceId,
                projectId: board?.projectId,
            },
        }, '');
        if (membersList?.response?.members
            && Array.isArray(membersList.response.members)
            && membersList.response.members.length > 0) {
            return membersList.response.members.map((m) => ({
                id: m.user_id,
                name: m.name,
                email: m.email,
                userId: m.user_id,
                role: m.role,
                canRemove,
            }));
        }
        return [];
    }
    async getList(query) {
        const db = (0, database_1.getDb)();
        const { projectId, workspaceId, userId, includeArchived = false, page = 1, limit = 50, } = query;
        const offset = (page - 1) * limit;
        const conditions = [
            (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId),
            (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId),
            (0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.userId, userId),
        ];
        if (!includeArchived) {
            conditions.push((0, drizzle_orm_1.eq)(tables_1.boardsTable.status, exports.BOARD_STATUS.ACTIVE));
        }
        const boardsRows = await db
            .select({
            id: tables_1.boardsTable.id,
            name: tables_1.boardsTable.name,
            description: tables_1.boardsTable.description,
            status: tables_1.boardsTable.status,
            applicationId: tables_1.boardsTable.applicationId,
            projectId: tables_1.boardsTable.projectId,
            workspaceId: tables_1.boardsTable.workspaceId,
            createdBy: tables_1.boardsTable.createdBy,
            createdAt: tables_1.boardsTable.createdAt,
            updatedAt: tables_1.boardsTable.updatedAt,
            applicationName: tables_1.applicationsTable.name,
            othersCount: (0, drizzle_orm_1.sql) `(
          SELECT count(*)::int
          FROM board_user_mapping m
          WHERE m.board_id = ${tables_1.boardsTable.id}
          AND m.role != 'creator'
        )`,
        })
            .from(tables_1.boardsTable)
            .innerJoin(tables_1.boardUserMappingTable, (0, drizzle_orm_1.eq)(tables_1.boardsTable.id, tables_1.boardUserMappingTable.boardId))
            .leftJoin(tables_1.applicationsTable, (0, drizzle_orm_1.eq)(tables_1.boardsTable.applicationId, tables_1.applicationsTable.id))
            .where((0, drizzle_orm_1.and)(...conditions))
            .orderBy((0, drizzle_orm_1.desc)(tables_1.boardsTable.updatedAt))
            .limit(limit)
            .offset(offset);
        const boards = boardsRows;
        const countResult = await db
            .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
            .from(tables_1.boardsTable)
            .innerJoin(tables_1.boardUserMappingTable, (0, drizzle_orm_1.eq)(tables_1.boardsTable.id, tables_1.boardUserMappingTable.boardId))
            .where((0, drizzle_orm_1.and)(...conditions));
        const total = Number(countResult[0]?.count ?? 0);
        return {
            boards,
            total,
            page,
            limit,
        };
    }
    async getById(boardId, projectId, workspaceId) {
        const db = (0, database_1.getDb)();
        const [board] = await db
            .select({
            id: tables_1.boardsTable.id,
            name: tables_1.boardsTable.name,
            description: tables_1.boardsTable.description,
            status: tables_1.boardsTable.status,
            applicationId: tables_1.boardsTable.applicationId,
            projectId: tables_1.boardsTable.projectId,
            workspaceId: tables_1.boardsTable.workspaceId,
            createdBy: tables_1.boardsTable.createdBy,
            createdAt: tables_1.boardsTable.createdAt,
            updatedAt: tables_1.boardsTable.updatedAt,
            applicationName: tables_1.applicationsTable.name,
        })
            .from(tables_1.boardsTable)
            .leftJoin(tables_1.applicationsTable, (0, drizzle_orm_1.eq)(tables_1.boardsTable.applicationId, tables_1.applicationsTable.id))
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)));
        if (!board)
            return null;
        // Fetch board widget instances from mapping, joined with catalog widget for base fields
        const mappingRows = await db
            .select({
            mappingId: tables_1.boardWidgetsMappingTable.id,
            boardId: tables_1.boardWidgetsMappingTable.boardId,
            workspaceId: tables_1.boardWidgetsMappingTable.workspaceId,
            projectId: tables_1.boardWidgetsMappingTable.projectId,
            applicationId: tables_1.boardWidgetsMappingTable.applicationId,
            widgetId: tables_1.boardWidgetsMappingTable.widgetId,
            position: tables_1.boardWidgetsMappingTable.position,
            mappingName: tables_1.boardWidgetsMappingTable.name,
            mappingDescription: tables_1.boardWidgetsMappingTable.description,
            mappingParameters: tables_1.boardWidgetsMappingTable.parameters,
            mappingType: tables_1.boardWidgetsMappingTable.type,
            mappingChartType: tables_1.boardWidgetsMappingTable.chartType,
            mappingTitle: tables_1.boardWidgetsMappingTable.title,
            mappingItems: tables_1.boardWidgetsMappingTable.items,
            mappingMetadata: tables_1.boardWidgetsMappingTable.metadata,
            mappingCreatedAt: tables_1.boardWidgetsMappingTable.createdAt,
            mappingUpdatedAt: tables_1.boardWidgetsMappingTable.updatedAt,
            widgetName: tables_1.boardWidgetsTable.name,
            widgetDescription: tables_1.boardWidgetsTable.description,
            widgetParameters: tables_1.boardWidgetsTable.parameters,
            widgetType: tables_1.boardWidgetsTable.type,
            widgetChartType: tables_1.boardWidgetsTable.chartType,
            widgetTitle: tables_1.boardWidgetsTable.title,
            widgetItems: tables_1.boardWidgetsTable.items,
            widgetMetadata: tables_1.boardWidgetsTable.metadata,
        })
            .from(tables_1.boardWidgetsMappingTable)
            .innerJoin(tables_1.boardWidgetsTable, (0, drizzle_orm_1.eq)(tables_1.boardWidgetsMappingTable.widgetId, tables_1.boardWidgetsTable.id))
            .where((0, drizzle_orm_1.eq)(tables_1.boardWidgetsMappingTable.boardId, boardId));
        const widgets = mappingRows.map((row) => {
            const pos = row.position ?? { x: 0, y: 0, w: 1, h: 1 };
            const baseMeta = row.widgetMetadata ?? {};
            const position = {
                x: pos.x ?? 0,
                y: pos.y ?? 0,
                w: pos.w ?? 1,
                h: pos.h ?? 1,
                minW: baseMeta.minW != null ? Number(baseMeta.minW) : pos.minW,
                maxW: baseMeta.maxW != null ? Number(baseMeta.maxW) : pos.maxW,
                minH: baseMeta.minH != null ? Number(baseMeta.minH) : pos.minH,
                maxH: baseMeta.maxH != null ? Number(baseMeta.maxH) : pos.maxH,
            };
            const useMapping = (mappingVal, widgetVal) => {
                if (mappingVal == null)
                    return widgetVal;
                if (typeof mappingVal === 'string')
                    return (mappingVal !== '' ? mappingVal : widgetVal);
                if (Array.isArray(mappingVal))
                    return (mappingVal.length > 0 ? mappingVal : widgetVal);
                if (typeof mappingVal === 'object' && mappingVal !== null)
                    return (Object.keys(mappingVal).length > 0 ? mappingVal : widgetVal);
                return mappingVal;
            };
            return {
                id: row.mappingId,
                boardId: row.boardId,
                workspaceId: row.workspaceId,
                projectId: row.projectId,
                applicationId: row.applicationId ?? null,
                widgetId: row.widgetId,
                position,
                name: useMapping(row.mappingName, row.widgetName),
                description: useMapping(row.mappingDescription, row.widgetDescription) ?? '',
                parameters: useMapping(row.mappingParameters, row.widgetParameters) ?? {},
                type: useMapping(row.mappingType, row.widgetType),
                chartType: useMapping(row.mappingChartType, row.widgetChartType),
                title: useMapping(row.mappingTitle, row.widgetTitle),
                items: useMapping(row.mappingItems, row.widgetItems) ?? [],
                metadata: useMapping(row.mappingMetadata, row.widgetMetadata) ?? {},
                createdAt: row.mappingCreatedAt,
                updatedAt: row.mappingUpdatedAt,
            };
        });
        const positions = mappingRows.map((row) => {
            const widget = widgets.find((w) => w.id === row.mappingId);
            const pos = widget?.position ?? row.position ?? { x: 0, y: 0, w: 1, h: 1 };
            return {
                id: row.mappingId,
                x: pos.x ?? 0,
                y: pos.y ?? 0,
                w: pos.w ?? 1,
                h: pos.h ?? 1,
                minW: pos.minW,
                maxW: pos.maxW,
                minH: pos.minH,
                maxH: pos.maxH,
            };
        });
        return {
            ...board,
            widgets,
            positions,
        };
    }
    /**
     * Save board widget layout using reconciliation: fetch existing mappings, then compute
     * toModify / toInsert / toDelete from incoming widget list and apply only the changes.
     */
    async saveBoardWidgets(boardId, projectId, workspaceId, userId, widgets) {
        const isMember = await this.isUserMemberOfBoard(boardId, userId);
        if (!isMember) {
            return false;
        }
        const db = (0, database_1.getDb)();
        const [board] = await db
            .select({
            id: tables_1.boardsTable.id,
            applicationId: tables_1.boardsTable.applicationId,
        })
            .from(tables_1.boardsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)))
            .limit(1);
        if (!board) {
            return false;
        }
        const existingRows = await db
            .select()
            .from(tables_1.boardWidgetsMappingTable)
            .where((0, drizzle_orm_1.eq)(tables_1.boardWidgetsMappingTable.boardId, boardId));
        const existingById = new Map(existingRows.map((r) => [r.id, r]));
        const incomingIds = new Set(widgets.map((w) => w.id).filter(Boolean));
        const toModify = [];
        const toInsert = [];
        for (const w of widgets) {
            if (w.id && existingById.has(w.id)) {
                toModify.push(w);
            }
            else {
                toInsert.push(w);
            }
        }
        const toDelete = existingRows.filter((r) => !incomingIds.has(r.id)).map((r) => r.id);
        if (toDelete.length > 0) {
            await db
                .delete(tables_1.boardWidgetsMappingTable)
                .where((0, drizzle_orm_1.inArray)(tables_1.boardWidgetsMappingTable.id, toDelete));
        }
        if (toModify.length > 0) {
            const widgetIds = [...new Set(toModify.map((w) => w.widgetId))];
            const catalogRows = await db
                .select({
                id: tables_1.boardWidgetsTable.id,
                metadata: tables_1.boardWidgetsTable.metadata,
            })
                .from(tables_1.boardWidgetsTable)
                .where((0, drizzle_orm_1.inArray)(tables_1.boardWidgetsTable.id, widgetIds));
            const catalogByWidgetId = Object.fromEntries(catalogRows.map((r) => [r.id, r.metadata ?? {}]));
            const now = new Date();
            for (const w of toModify) {
                const position = w.position ?? { x: 0, y: 0, w: 1, h: 1 };
                const baseMeta = catalogByWidgetId[w.widgetId] ?? {};
                const savedPosition = {
                    x: position.x ?? 0,
                    y: position.y ?? 0,
                    w: position.w ?? 1,
                    h: position.h ?? 1,
                    minW: baseMeta.minW != null ? Number(baseMeta.minW) : position.minW,
                    maxW: baseMeta.maxW != null ? Number(baseMeta.maxW) : position.maxW,
                    minH: baseMeta.minH != null ? Number(baseMeta.minH) : position.minH,
                    maxH: baseMeta.maxH != null ? Number(baseMeta.maxH) : position.maxH,
                };
                await db
                    .update(tables_1.boardWidgetsMappingTable)
                    .set({
                    widgetId: w.widgetId,
                    position: savedPosition,
                    name: w.name ?? '',
                    description: w.description ?? '',
                    parameters: w.parameters ?? {},
                    type: w.type ?? 'monitoring',
                    chartType: w.chartType ?? 'line',
                    title: w.title ?? '',
                    items: w.items ?? [],
                    metadata: w.metadata ?? {},
                    updatedAt: now,
                })
                    .where((0, drizzle_orm_1.eq)(tables_1.boardWidgetsMappingTable.id, w.id));
            }
        }
        if (toInsert.length > 0) {
            const widgetIds = [...new Set(toInsert.map((w) => w.widgetId))];
            const catalogRows = await db
                .select({
                id: tables_1.boardWidgetsTable.id,
                metadata: tables_1.boardWidgetsTable.metadata,
            })
                .from(tables_1.boardWidgetsTable)
                .where((0, drizzle_orm_1.inArray)(tables_1.boardWidgetsTable.id, widgetIds));
            const catalogByWidgetId = Object.fromEntries(catalogRows.map((r) => [r.id, r.metadata ?? {}]));
            const now = new Date();
            const rows = toInsert.map((w) => {
                const position = w.position ?? { x: 0, y: 0, w: 1, h: 1 };
                const baseMeta = catalogByWidgetId[w.widgetId] ?? {};
                const savedPosition = {
                    x: position.x ?? 0,
                    y: position.y ?? 0,
                    w: position.w ?? 1,
                    h: position.h ?? 1,
                    minW: baseMeta.minW != null ? Number(baseMeta.minW) : position.minW,
                    maxW: baseMeta.maxW != null ? Number(baseMeta.maxW) : position.maxW,
                    minH: baseMeta.minH != null ? Number(baseMeta.minH) : position.minH,
                    maxH: baseMeta.maxH != null ? Number(baseMeta.maxH) : position.maxH,
                };
                return {
                    id: (0, uuid_1.v4)(),
                    boardId,
                    workspaceId,
                    projectId,
                    applicationId: board.applicationId ?? null,
                    widgetId: w.widgetId,
                    position: savedPosition,
                    name: w.name ?? '',
                    description: w.description ?? '',
                    parameters: w.parameters ?? {},
                    type: w.type ?? 'monitoring',
                    chartType: w.chartType ?? 'line',
                    title: w.title ?? '',
                    items: w.items ?? [],
                    metadata: w.metadata ?? {},
                    createdAt: now,
                    updatedAt: now,
                };
            });
            await db.insert(tables_1.boardWidgetsMappingTable).values(rows);
        }
        await db
            .update(tables_1.boardsTable)
            .set({ updatedAt: new Date() })
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)));
        logger_1.default.info(`Saved board ${boardId}: ${toModify.length} updated, ${toInsert.length} inserted, ${toDelete.length} deleted`);
        return true;
    }
    /**
     * "Delete" = set status to ARCHIVED (soft delete).
     * Returns null if user is not a member (unauthorized) or board not found.
     */
    async archive(boardId, projectId, workspaceId, userId) {
        const isMember = await this.isUserMemberOfBoard(boardId, userId);
        if (!isMember) {
            return null;
        }
        const db = (0, database_1.getDb)();
        const [updated] = await db
            .update(tables_1.boardsTable)
            .set({
            status: exports.BOARD_STATUS.ARCHIVED,
            updatedAt: new Date(),
        })
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)))
            .returning({ id: tables_1.boardsTable.id });
        if (updated) {
            logger_1.default.info(`Archived board ${boardId}`);
        }
        return updated ?? null;
    }
    async addMembership(boardId, projectId, workspaceId, requestedByUserId, members) {
        const isMember = await this.isUserMemberOfBoard(boardId, requestedByUserId);
        if (!isMember) {
            return { added: 0, boardName: null };
        }
        const db = (0, database_1.getDb)();
        const [board] = await db
            .select({ id: tables_1.boardsTable.id, name: tables_1.boardsTable.name })
            .from(tables_1.boardsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)))
            .limit(1);
        if (!board) {
            return { added: 0, boardName: null };
        }
        let added = 0;
        for (const m of members) {
            if (!m.userId?.trim())
                continue;
            try {
                await db
                    .insert(tables_1.boardUserMappingTable)
                    .values({
                    boardId,
                    userId: m.userId.trim(),
                    role: 'viewer',
                })
                    .onConflictDoNothing({
                    target: [tables_1.boardUserMappingTable.boardId, tables_1.boardUserMappingTable.userId],
                });
                added += 1;
            }
            catch {
                // skip duplicate or invalid
            }
        }
        logger_1.default.info(`Added ${added} member(s) to board ${boardId}`);
        return { added, boardName: board.name };
    }
    /**
     * Remove a user from a board. Only the board creator may remove members.
     */
    async removeMembership(boardId, projectId, workspaceId, requestedByUserId, removedUserId) {
        const db = (0, database_1.getDb)();
        const [requestingUserRow] = await db
            .select({ role: tables_1.boardUserMappingTable.role })
            .from(tables_1.boardUserMappingTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.boardId, boardId), (0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.userId, requestedByUserId)))
            .limit(1);
        const isMember = !!requestingUserRow;
        if (!isMember || requestingUserRow?.role !== 'creator') {
            return false;
        }
        const [board] = await db
            .select({ id: tables_1.boardsTable.id })
            .from(tables_1.boardsTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardsTable.id, boardId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.projectId, projectId), (0, drizzle_orm_1.eq)(tables_1.boardsTable.workspaceId, workspaceId)))
            .limit(1);
        if (!board) {
            return false;
        }
        await db
            .delete(tables_1.boardUserMappingTable)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.boardId, boardId), (0, drizzle_orm_1.eq)(tables_1.boardUserMappingTable.userId, removedUserId)));
        logger_1.default.info(`Removed user ${removedUserId} from board ${boardId}`);
        return true;
    }
    /**
     * Get unique widget groups from board_widgets table; each group is used as the category (label).
     */
    async getWidgetCategories() {
        const db = (0, database_1.getDb)();
        const rows = await db
            .select({
            category: tables_1.boardWidgetsTable.category,
            count: (0, drizzle_orm_1.sql) `count(*)::int`,
        })
            .from(tables_1.boardWidgetsTable)
            .groupBy(tables_1.boardWidgetsTable.category)
            .orderBy(tables_1.boardWidgetsTable.category);
        const categoryToIconKey = {
            monitoring: 'lightbulb',
            observability: 'clock',
            security: 'shield',
            functional: 'document',
            guardrails: 'calendar',
        };
        const categoryOrder = [
            'monitoring',
            'observability',
            'security',
            'functional',
            'guardrails',
            'rag',
        ];
        const normalizeGroupKey = (value) => value
            .toLowerCase()
            .replace(/\s+/g, '')
            .replace(/widgets$/i, '')
            .replace(/evaluation$/i, '')
            .replace(/evaluations$/i, '');
        return rows
            .filter((row) => row.category != null && String(row.category).trim() !== '')
            .sort((a, b) => categoryOrder.indexOf(normalizeGroupKey(String(a.category))) - categoryOrder.indexOf(normalizeGroupKey(String(b.category))))
            .map((row) => {
            const category = String(row.category);
            const count = row.count ?? 0;
            const normalizedKey = normalizeGroupKey(category);
            return {
                id: category,
                label: category,
                status: count > 0 ? `${count} widget${count !== 1 ? 's' : ''}` : 'Enabled',
                iconKey: categoryToIconKey[normalizedKey] ?? 'chart',
            };
        });
    }
    /**
     * Get available widgets for a widget group from board_widgets table (group = category label).
     *
     * Widget metadata may include:
     * - isAgentic: boolean — for observability category, widget is enabled only when application isAgentic is true.
     * - metricRequired: string — for monitoring category, widget is enabled only when pulse monitoring has this metric.
     * - isRag: boolean — reserved for future use (RAG/VectorDB widget gating).
     */
    async getWidgetsByCategory(groupId, applicationId, projectId, workspaceId) {
        const categoryOrder = [
            'monitoring',
            'observability',
            'security',
            'functional',
            'guardrails',
            'rag',
        ];
        const normalizeGroupKey = (value) => value
            .toLowerCase()
            .replace(/\s+/g, '')
            .replace(/widgets$/i, '')
            .replace(/evaluation$/i, '')
            .replace(/evaluations$/i, '');
        const db = (0, database_1.getDb)();
        const normalizedAppId = typeof applicationId === 'string' ? applicationId.trim() : '';
        const hasApplication = normalizedAppId.length > 0 &&
            normalizedAppId !== 'null' &&
            normalizedAppId !== 'undefined';
        let hasRunFunctionalEvaluation = [];
        let hasRunSecurityEvaluation = [];
        let enabledMonitoring = [];
        let hasEnabledGuardrails = [];
        let application = null;
        let monitoringMetrics = [];
        if (hasApplication) {
            const [functionalResult, securityResult, monitoringCountResult, guardrailsResult, appRows, monitoringRows,] = await Promise.all([
                db.select({ count: (0, drizzle_orm_1.sql) `count(*)::int` })
                    .from(tables_1.testRunsTable)
                    .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.testRunsTable.status, constants_1.TEST_RUN_STATUS.COMPLETED), (0, drizzle_orm_1.eq)(tables_1.testRunsTable.type, constants_1.TEST_RUN_TYPE.EVAL), (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.applicationIds} @> ${JSON.stringify([normalizedAppId])}::jsonb`)),
                db.select({ count: (0, drizzle_orm_1.sql) `count(*)::int` })
                    .from(tables_1.testRunsTable)
                    .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.testRunsTable.status, constants_1.TEST_RUN_STATUS.COMPLETED), (0, drizzle_orm_1.eq)(tables_1.testRunsTable.type, constants_1.TEST_RUN_TYPE.SECURITY), (0, drizzle_orm_1.sql) `${tables_1.testRunsTable.applicationIds} @> ${JSON.stringify([normalizedAppId])}::jsonb`)),
                db.select({ count: (0, drizzle_orm_1.sql) `count(*)::int` })
                    .from(tables_1.pulseMonitoringTable)
                    .where((0, drizzle_orm_1.eq)(tables_1.pulseMonitoringTable.applicationId, normalizedAppId)),
                db.select({ count: (0, drizzle_orm_1.sql) `count(*)::int` })
                    .from(tables_1.applicationGuardrailsTable)
                    .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(tables_1.applicationGuardrailsTable.applicationId, normalizedAppId), (0, drizzle_orm_1.eq)(tables_1.applicationGuardrailsTable.enable_guard_rails, true))),
                db.select({
                    id: tables_1.applicationsTable.id,
                    isAgentic: tables_1.applicationsTable.isAgentic,
                    hasVectorDb: tables_1.applicationsTable.hasVectorDb,
                })
                    .from(tables_1.applicationsTable)
                    .where((0, drizzle_orm_1.eq)(tables_1.applicationsTable.id, normalizedAppId))
                    .limit(1),
                db.select({ metrics: tables_1.pulseMonitoringTable.metrics })
                    .from(tables_1.pulseMonitoringTable)
                    .where((0, drizzle_orm_1.eq)(tables_1.pulseMonitoringTable.applicationId, normalizedAppId))
                    .limit(1),
            ]);
            hasRunFunctionalEvaluation = functionalResult;
            hasRunSecurityEvaluation = securityResult;
            enabledMonitoring = monitoringCountResult;
            hasEnabledGuardrails = guardrailsResult;
            const appRow = appRows?.[0];
            if (appRow) {
                application = {
                    id: appRow.id,
                    isAgentic: Boolean(appRow.isAgentic),
                    hasVectorDb: Boolean(appRow.hasVectorDb),
                };
            }
            const monitoringRow = monitoringRows?.[0];
            if (monitoringRow?.metrics && Array.isArray(monitoringRow.metrics)) {
                monitoringMetrics = monitoringRow.metrics.map((m) => (m?.type ?? m?.metric ?? '').trim()).filter(Boolean);
            }
        }
        const widgets = await db
            .select({
            id: tables_1.boardWidgetsTable.id,
            name: tables_1.boardWidgetsTable.name,
            category: tables_1.boardWidgetsTable.category,
            title: tables_1.boardWidgetsTable.title,
            items: tables_1.boardWidgetsTable.items,
            metadata: tables_1.boardWidgetsTable.metadata,
            parameters: tables_1.boardWidgetsTable.parameters,
            type: tables_1.boardWidgetsTable.type,
            chartType: tables_1.boardWidgetsTable.chartType,
            description: tables_1.boardWidgetsTable.description,
            group: tables_1.boardWidgetsTable.group,
        })
            .from(tables_1.boardWidgetsTable)
            .where((0, drizzle_orm_1.eq)(tables_1.boardWidgetsTable.category, groupId))
            .orderBy(tables_1.boardWidgetsTable.name);
        const enrichedWidgets = widgets.map((widget) => {
            const categoryKey = normalizeGroupKey(widget.category ?? '');
            const metadata = widget.metadata && typeof widget.metadata === 'object' ? widget.metadata : {};
            const metricRequired = typeof metadata.metricRequired === 'string' ? metadata.metricRequired.trim() : '';
            const { isAgentic, metricRequired: metricRequiredFromMetadata, isRag: isRagFromMetadata, ...rest } = metadata;
            let enabled = false;
            if (hasApplication) {
                if (categoryKey === 'observability' && (enabledMonitoring?.[0]?.count ?? 0) > 0) {
                    enabled = isAgentic === true && application?.isAgentic !== true ? false : true;
                }
                else if (categoryKey === 'rag') {
                    enabled = application?.hasVectorDb ? true : false;
                }
                else if (categoryKey === 'monitoring' && (enabledMonitoring?.[0]?.count ?? 0) > 0) {
                    enabled = typeof metricRequiredFromMetadata !== 'undefined' && metricRequiredFromMetadata !== null && metricRequired !== '' && !monitoringMetrics.includes(metricRequired) ? false : true;
                }
                else if (categoryKey === 'functional') {
                    enabled = (hasRunFunctionalEvaluation?.[0]?.count ?? 0) > 0;
                }
                else if (categoryKey === 'security') {
                    enabled = (hasRunSecurityEvaluation?.[0]?.count ?? 0) > 0;
                }
                else if (categoryKey === 'guardrails') {
                    enabled = (hasEnabledGuardrails?.[0]?.count ?? 0) > 0;
                }
            }
            return {
                ...widget,
                enabled,
                tag: this.TAG_MAP[widget.chartType] ?? undefined,
                metadata: rest,
            };
        });
        return enrichedWidgets;
    }
}
exports.BoardsService = BoardsService;
exports.boardsService = new BoardsService();
//# sourceMappingURL=boardsService.js.map