var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { ChangeDetectorRef, Component, inject } from '@angular/core';
import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
import { BaseResourceComponent } from '@memberjunction/ng-shared';
import { RunView } from '@memberjunction/core';
import { validateStringParam } from '../shared/agent-tool-validation';
import { buildRealtimeRecordingsAgentContext, buildSessionNotFoundError, isValidSessionSortDirection, isValidSessionSortField, resolveSessionByIdOrName, sessionMatchesQuery, } from './realtime-recordings-agent-context';
import * as i0 from "@angular/core";
import * as i1 from "@memberjunction/ng-ui-components";
import * as i2 from "@memberjunction/ng-shared-generic";
import * as i3 from "angular-split";
import * as i4 from "@memberjunction/ng-media-player";
function RealtimeRecordingsDashboardComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 1);
    i0.ɵɵelement(1, "mj-stat-badge", 8);
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const ctx_r0 = i0.ɵɵnextContext();
    i0.ɵɵadvance();
    i0.ɵɵproperty("Count", ctx_r0.VisibleSessions.length)("Total", ctx_r0.Sessions.length);
} }
function RealtimeRecordingsDashboardComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 4);
    i0.ɵɵelement(1, "mj-loading", 9);
    i0.ɵɵelementEnd();
} }
function RealtimeRecordingsDashboardComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
    const _r2 = i0.ɵɵgetCurrentView();
    i0.ɵɵelementStart(0, "div", 5);
    i0.ɵɵelement(1, "i", 10);
    i0.ɵɵelementStart(2, "p");
    i0.ɵɵtext(3);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(4, "button", 11);
    i0.ɵɵlistener("click", function RealtimeRecordingsDashboardComponent_Conditional_7_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.LoadData()); });
    i0.ɵɵtext(5, "Try again");
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const ctx_r0 = i0.ɵɵnextContext();
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(ctx_r0.ErrorMessage);
} }
function RealtimeRecordingsDashboardComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 6);
    i0.ɵɵelement(1, "i", 12);
    i0.ɵɵelementStart(2, "p", 13);
    i0.ɵɵtext(3, "No recorded sessions yet.");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(4, "p", 14);
    i0.ɵɵtext(5, " When a realtime session is recorded, it will appear here for review and replay. ");
    i0.ɵɵelementEnd()();
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Conditional_10_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "span", 30);
    i0.ɵɵelement(1, "i", 32);
    i0.ɵɵtext(2);
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const session_r4 = i0.ɵɵnextContext().$implicit;
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1(" ", session_r4.DurationLabel, " ");
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Conditional_11_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 31);
    i0.ɵɵelement(1, "i", 33);
    i0.ɵɵtext(2);
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const session_r4 = i0.ɵɵnextContext().$implicit;
    i0.ɵɵproperty("title", session_r4.ConversationName);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1(" ", session_r4.ConversationName, " ");
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Template(rf, ctx) { if (rf & 1) {
    const _r3 = i0.ɵɵgetCurrentView();
    i0.ɵɵelementStart(0, "li", 23);
    i0.ɵɵlistener("click", function RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Template_li_click_0_listener() { const session_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.SelectSession(session_r4)); })("keydown", function RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Template_li_keydown_0_listener($event) { const session_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnRowKeydown($event, session_r4)); });
    i0.ɵɵelementStart(1, "div", 24);
    i0.ɵɵelement(2, "i");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(3, "div", 25)(4, "div", 26);
    i0.ɵɵtext(5);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(6, "div", 27)(7, "span", 28);
    i0.ɵɵelement(8, "i", 29);
    i0.ɵɵtext(9);
    i0.ɵɵelementEnd();
    i0.ɵɵconditionalCreate(10, RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Conditional_10_Template, 3, 1, "span", 30);
    i0.ɵɵelementEnd();
    i0.ɵɵconditionalCreate(11, RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Conditional_11_Template, 3, 2, "div", 31);
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const session_r4 = ctx.$implicit;
    const ctx_r0 = i0.ɵɵnextContext(2);
    i0.ɵɵclassProp("rr-list-item--active", (ctx_r0.SelectedSession == null ? null : ctx_r0.SelectedSession.ID) === session_r4.ID);
    i0.ɵɵattribute("aria-selected", (ctx_r0.SelectedSession == null ? null : ctx_r0.SelectedSession.ID) === session_r4.ID);
    i0.ɵɵadvance(2);
    i0.ɵɵclassMap(session_r4.RecordingMedia === "AudioVideo" ? "fa-solid fa-video" : "fa-solid fa-waveform-lines");
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(session_r4.AgentName);
    i0.ɵɵadvance(4);
    i0.ɵɵtextInterpolate1(" ", ctx_r0.SessionDateLabel(session_r4), " ");
    i0.ɵɵadvance();
    i0.ɵɵconditional(session_r4.DurationLabel ? 10 : -1);
    i0.ɵɵadvance();
    i0.ɵɵconditional(session_r4.ConversationName ? 11 : -1);
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_7_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 20);
    i0.ɵɵelement(1, "i", 34);
    i0.ɵɵelementStart(2, "p", 13);
    i0.ɵɵtext(3, "Select a session to replay");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(4, "p", 14);
    i0.ɵɵtext(5, " Pick a recording from the list to hear its audio alongside the synchronized transcript. ");
    i0.ɵɵelementEnd()();
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_8_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 21);
    i0.ɵɵelement(1, "mj-loading", 35);
    i0.ɵɵelementEnd();
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_9_Template(rf, ctx) { if (rf & 1) {
    const _r5 = i0.ɵɵgetCurrentView();
    i0.ɵɵelementStart(0, "div", 5);
    i0.ɵɵelement(1, "i", 10);
    i0.ɵɵelementStart(2, "p");
    i0.ɵɵtext(3);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(4, "button", 11);
    i0.ɵɵlistener("click", function RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_9_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r5); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.SelectSession(ctx_r0.SelectedSession)); });
    i0.ɵɵtext(5, "Try again");
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const ctx_r0 = i0.ɵɵnextContext(2);
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(ctx_r0.DetailErrorMessage);
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_10_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelement(0, "mj-storage-media-player", 22);
} if (rf & 2) {
    const ctx_r0 = i0.ɵɵnextContext(2);
    i0.ɵɵproperty("FileID", ctx_r0.SelectedSession.RecordingFileID)("Transcript", ctx_r0.SelectedCues)("Provider", ctx_r0.Provider);
} }
function RealtimeRecordingsDashboardComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "as-split", 7)(1, "as-split-area", 15)(2, "ul", 16);
    i0.ɵɵrepeaterCreate(3, RealtimeRecordingsDashboardComponent_Conditional_9_For_4_Template, 12, 9, "li", 17, i0.ɵɵcomponentInstance().TrackBySession, true);
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(5, "as-split-area", 18)(6, "div", 19);
    i0.ɵɵconditionalCreate(7, RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_7_Template, 6, 0, "div", 20)(8, RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_8_Template, 2, 0, "div", 21)(9, RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_9_Template, 6, 1, "div", 5)(10, RealtimeRecordingsDashboardComponent_Conditional_9_Conditional_10_Template, 1, 3, "mj-storage-media-player", 22);
    i0.ɵɵelementEnd()()();
} if (rf & 2) {
    const ctx_r0 = i0.ɵɵnextContext();
    i0.ɵɵadvance();
    i0.ɵɵproperty("size", 360)("minSize", 260)("maxSize", 560);
    i0.ɵɵadvance(2);
    i0.ɵɵrepeater(ctx_r0.VisibleSessions);
    i0.ɵɵadvance(2);
    i0.ɵɵproperty("size", 640)("minSize", 360);
    i0.ɵɵadvance(2);
    i0.ɵɵconditional(!ctx_r0.SelectedSession ? 7 : ctx_r0.IsDetailLoading ? 8 : ctx_r0.DetailErrorMessage ? 9 : 10);
} }
/**
 * REALTIME RECORDINGS — an Explorer resource dashboard for reviewing & replaying historical realtime
 * sessions that were recorded. The left pane lists every recorded session (agent · date · duration ·
 * conversation); selecting one loads its time-aligned transcript turns and resolves the recording's
 * builds transcript cues, then hands the recording's `RecordingFileID` + cues to
 * {@link MJStorageMediaPlayerComponent} in the right pane — the player resolves the authenticated
 * audio itself for synchronized audio + transcript playback.
 *
 * Data sources:
 *  - List: `RunView('MJ: AI Agent Sessions', RecordingFileID IS NOT NULL)` — `simple` rows.
 *  - Turns: `RunView('MJ: Conversation Details', AgentSessionID = <id>)` — `entity_object` rows.
 *  - Audio: resolved by the storage media player from `RecordingFileID` (authenticated bytes → blob).
 */
let RealtimeRecordingsDashboardComponent = class RealtimeRecordingsDashboardComponent extends BaseResourceComponent {
    /** All recorded sessions (master list), newest first. */
    Sessions = [];
    /** Active client-side text filter applied to the list (empty when none). */
    SearchQuery = '';
    /** Active client-side sort field for the list. */
    SortField = 'date';
    /** Active client-side sort direction for the list. */
    SortDirection = 'desc';
    /** The session currently selected for playback, or null. */
    SelectedSession = null;
    /** Transcript turns for the selected session (used to build the cues). */
    SelectedTurns = [];
    /** Transcript cues for the selected session, built from its turns (passed to the media player). */
    SelectedCues = [];
    /** True while the master list is loading. */
    IsLoading = true;
    /** True while a selected session's turns are loading. */
    IsDetailLoading = false;
    /** Master-list load error, shown in the body when set. */
    ErrorMessage = null;
    /** Detail-pane (turns) error, shown in the right pane when set. */
    DetailErrorMessage = null;
    cdr = inject(ChangeDetectorRef);
    async GetResourceDisplayName(_data) {
        return 'Realtime Recordings';
    }
    async GetResourceIconClass(_data) {
        return 'fa-solid fa-circle-play';
    }
    async ngOnInit() {
        super.ngOnInit();
        await this.LoadData();
        this.NotifyLoadComplete();
    }
    /**
     * After the view initializes, publish the initial agent context and register the read-only
     * playback-navigation tools the AI agent can invoke against this surface. The ongoing context
     * re-emit happens after every meaningful state change (load, select, deselect).
     */
    ngAfterViewInit() {
        this.publishAgentContext();
        this.registerAgentClientTools();
    }
    ngOnDestroy() {
        super.ngOnDestroy();
    }
    /** Loads the master list of recorded sessions. */
    async LoadData() {
        this.IsLoading = true;
        this.ErrorMessage = null;
        this.cdr.detectChanges();
        try {
            const rv = RunView.FromMetadataProvider(this.ProviderToUse);
            const result = await rv.RunView({
                EntityName: 'MJ: AI Agent Sessions',
                ExtraFilter: 'RecordingFileID IS NOT NULL',
                OrderBy: '__mj_CreatedAt DESC',
                ResultType: 'simple'
            });
            if (!result.Success) {
                this.ErrorMessage = result.ErrorMessage || 'Failed to load recorded sessions.';
                this.Sessions = [];
                return;
            }
            this.Sessions = (result.Results ?? []).map(row => this.toRecordedSession(row));
        }
        catch (err) {
            this.ErrorMessage = err instanceof Error ? err.message : String(err);
            this.Sessions = [];
        }
        finally {
            this.IsLoading = false;
            this.publishAgentContext();
            this.cdr.detectChanges();
        }
    }
    /** Selects a session and loads its transcript into the playback pane (the player resolves audio). */
    async SelectSession(session) {
        if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {
            return;
        }
        this.SelectedSession = session;
        this.SelectedTurns = [];
        this.SelectedCues = [];
        this.DetailErrorMessage = null;
        this.IsDetailLoading = true;
        this.publishAgentContext();
        this.cdr.detectChanges();
        try {
            const turns = await this.loadTurns(session.ID);
            // Guard against a newer selection having superseded this load.
            if (!UUIDsEqual(this.SelectedSession?.ID, session.ID)) {
                return;
            }
            this.SelectedTurns = turns;
            this.SelectedCues = this.buildCues(turns, session);
            this.maybeBackfillDuration(session, turns);
        }
        catch (err) {
            if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {
                this.DetailErrorMessage = err instanceof Error ? err.message : String(err);
            }
        }
        finally {
            if (UUIDsEqual(this.SelectedSession?.ID, session.ID)) {
                this.IsDetailLoading = false;
            }
            this.publishAgentContext();
            this.cdr.detectChanges();
        }
    }
    /** Keyboard activation for a list row (Enter / Space select it). */
    OnRowKeydown(event, session) {
        if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault();
            void this.SelectSession(session);
        }
    }
    /** Stable `@for` track for the session list. */
    TrackBySession(_index, session) {
        return session.ID;
    }
    /**
     * The recordings currently visible — the master list narrowed by the active client-side
     * text filter and ordered by the active sort field + direction. Pure derivation from
     * `Sessions` / `SearchQuery` / `SortField` / `SortDirection`; drives both the list UI and
     * the agent context, so the agent's "what's selectable" view matches the screen exactly.
     */
    get VisibleSessions() {
        const filtered = this.Sessions.filter(s => sessionMatchesQuery(s, this.SearchQuery));
        const dir = this.SortDirection === 'asc' ? 1 : -1;
        return filtered.sort((a, b) => this.compareSessions(a, b, this.SortField) * dir);
    }
    /** Comparator for two recordings on the given sort field (ascending; caller applies direction). */
    compareSessions(a, b, field) {
        switch (field) {
            case 'agent':
                return a.AgentName.localeCompare(b.AgentName);
            case 'duration':
                return this.durationSortKey(a) - this.durationSortKey(b);
            case 'date':
            default:
                return (a.CreatedAt?.getTime() ?? 0) - (b.CreatedAt?.getTime() ?? 0);
        }
    }
    /** A stable numeric sort key for a recording's duration (createdAt epoch as a coarse proxy when absent). */
    durationSortKey(session) {
        if (!session.DurationLabel) {
            return 0;
        }
        // DurationLabel is `m:ss` or `h:mm:ss` — parse to total seconds for a stable ordering.
        const parts = session.DurationLabel.split(':').map(p => Number(p));
        if (parts.some(n => !Number.isFinite(n))) {
            return 0;
        }
        return parts.reduce((acc, n) => acc * 60 + n, 0);
    }
    /** A short, localized date/time label for a session row. */
    SessionDateLabel(session) {
        if (!session.CreatedAt) {
            return '—';
        }
        return session.CreatedAt.toLocaleString(undefined, {
            year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'
        });
    }
    /** Clears the current playback selection, returning the surface to the empty playback state. */
    DeselectSession() {
        if (this.SelectedSession === null) {
            return;
        }
        this.SelectedSession = null;
        this.SelectedTurns = [];
        this.SelectedCues = [];
        this.DetailErrorMessage = null;
        this.IsDetailLoading = false;
        this.publishAgentContext();
        this.cdr.detectChanges();
    }
    // ========================================
    // AI AGENT CONTEXT & CLIENT TOOLS
    //
    // 🚨 SAFETY BOUNDARY: This is a read-only session-playback surface. Only playback-navigation
    // tools are exposed to the agent (select / play / deselect a recording, refresh the list, narrow
    // the list by a client-side text filter, re-order the list). NO tool mutates, deletes, or exports
    // a recording, a session, or its transcript — there is no such tool, and none may ever be added.
    // ========================================
    /**
     * Publish the current Realtime Recordings state to the AI agent via NavigationService.
     * Reports the recordings-list size (total + filtered), the active search/sort state, the open
     * selection (id / agent / conversation / media / duration), the loaded turn count, the detail
     * loading flag, and a bounded structured summary of the visible recordings. The shaping lives in
     * the pure {@link buildRealtimeRecordingsAgentContext} helper so it stays unit-testable. Called
     * on init and after every meaningful state change (load, select, deselect, search, sort).
     */
    publishAgentContext() {
        const selected = this.SelectedSession;
        const context = buildRealtimeRecordingsAgentContext({
            SessionCount: this.Sessions.length,
            SelectedSession: selected
                ? {
                    ID: selected.ID,
                    AgentName: selected.AgentName,
                    ConversationName: selected.ConversationName,
                    RecordingMedia: selected.RecordingMedia,
                    DurationLabel: selected.DurationLabel,
                }
                : null,
            TurnCount: this.SelectedTurns.length,
            IsDetailLoading: this.IsDetailLoading,
            SearchQuery: this.SearchQuery,
            VisibleSessions: this.VisibleSessions.map(s => ({
                ID: s.ID,
                AgentName: s.AgentName,
                ConversationName: s.ConversationName,
                RecordingMedia: s.RecordingMedia,
                DurationLabel: s.DurationLabel,
            })),
            SortField: this.SortField,
            SortDirection: this.SortDirection,
        });
        this.navigationService.SetAgentContext(this, context);
    }
    /**
     * Register the read-only playback-navigation tools the AI agent can invoke against this surface.
     * Each handler delegates to the same component method a user interaction would call, and returns
     * `{ Success: true, Data? }` on success or `{ Success: false, ErrorMessage }` on failure.
     * Handlers are tolerant — they never throw.
     *
     * Tools (read-only — see the SAFETY BOUNDARY comment above):
     * - SelectSession / PlayRecording: select a recording by id OR name and load/play its transcript.
     * - DeselectSession: clear the current playback selection.
     * - RefreshSessionList: reload the master list of recorded sessions.
     * - SearchSessions: narrow the visible list by a client-side text filter (updates the list).
     * - SortSessions: re-order the visible list by date / agent / duration, asc or desc.
     */
    registerAgentClientTools() {
        this.navigationService.SetAgentClientTools(this, [
            {
                Name: 'SelectSession',
                Description: 'Select a recorded realtime session by its session ID OR name (the agent name, conversation name, or "Agent — Conversation" label) and load its transcript for playback.',
                ParameterSchema: { type: 'object', properties: { session: { type: 'string', description: 'Session ID or name.' } }, required: ['session'] },
                Handler: async (params) => this.toolSelectSession(params),
            },
            {
                Name: 'PlayRecording',
                Description: 'Open and play a recording by its session ID OR name — selects it and mounts the synchronized audio + transcript player (alias of SelectSession).',
                ParameterSchema: { type: 'object', properties: { session: { type: 'string', description: 'Session ID or name.' } }, required: ['session'] },
                Handler: async (params) => this.toolSelectSession(params),
            },
            {
                Name: 'DeselectSession',
                Description: 'Clear the currently selected recording, returning playback to the empty state.',
                ParameterSchema: { type: 'object', properties: {} },
                Handler: async () => {
                    this.DeselectSession();
                    return { Success: true };
                },
            },
            {
                Name: 'RefreshSessionList',
                Description: 'Reload the master list of recorded realtime sessions.',
                ParameterSchema: { type: 'object', properties: {} },
                Handler: async () => {
                    await this.LoadData();
                    return { Success: true, Data: { SessionCount: this.Sessions.length } };
                },
            },
            {
                Name: 'SearchSessions',
                Description: 'Narrow the visible recordings by a free-text query, matched against the agent name, conversation name, and captured-media type. Updates the list shown to the user (read-only; does not change the selection). Pass an empty string to clear the filter.',
                ParameterSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
                Handler: async (params) => this.toolSearchSessions(params),
            },
            {
                Name: 'SortSessions',
                Description: 'Re-order the visible recordings by "date", "agent", or "duration", in "asc" or "desc" direction. Read-only display ordering.',
                ParameterSchema: {
                    type: 'object',
                    properties: {
                        field: { type: 'string', enum: ['date', 'agent', 'duration'] },
                        direction: { type: 'string', enum: ['asc', 'desc'] },
                    },
                    required: ['field'],
                },
                Handler: async (params) => this.toolSortSessions(params),
            },
        ]);
    }
    /**
     * Resolve a session reference (id OR name) to a loaded recording and select it for playback.
     * Tolerant: returns a structured failure (with a sample of available recordings) on a miss.
     */
    async toolSelectSession(params) {
        const validation = validateStringParam(params['session'], 'session');
        if (!validation.ok) {
            return validation.result;
        }
        const ref = validation.value.trim();
        if (ref.length === 0) {
            return { Success: false, ErrorMessage: 'A session ID or name is required.' };
        }
        const session = resolveSessionByIdOrName(ref, this.Sessions);
        if (!session) {
            return { Success: false, ErrorMessage: buildSessionNotFoundError(ref, this.Sessions) };
        }
        await this.SelectSession(session);
        return { Success: true, Data: { SelectedSessionId: session.ID, AgentName: session.AgentName } };
    }
    /** Apply a client-side text filter to the visible list (updates the UI + agent context). */
    toolSearchSessions(params) {
        const validation = validateStringParam(params['query'], 'query');
        if (!validation.ok) {
            return validation.result;
        }
        this.SearchQuery = validation.value;
        this.publishAgentContext();
        this.cdr.detectChanges();
        const matches = this.VisibleSessions;
        return {
            Success: true,
            Data: {
                MatchCount: matches.length,
                Matches: matches.map(s => ({
                    ID: s.ID,
                    AgentName: s.AgentName,
                    ConversationName: s.ConversationName,
                    RecordingMedia: s.RecordingMedia,
                    DurationLabel: s.DurationLabel,
                })),
            },
        };
    }
    /** Re-order the visible list by date / agent / duration, asc or desc. Read-only display ordering. */
    toolSortSessions(params) {
        const rawField = params['field'];
        if (!isValidSessionSortField(rawField)) {
            return { Success: false, ErrorMessage: 'field must be one of: date, agent, duration.' };
        }
        const rawDirection = params['direction'];
        // direction is optional — default to the sensible per-field default (date: desc, else asc).
        let direction;
        if (rawDirection === undefined || rawDirection === null) {
            direction = rawField === 'date' ? 'desc' : 'asc';
        }
        else if (isValidSessionSortDirection(rawDirection)) {
            direction = rawDirection;
        }
        else {
            return { Success: false, ErrorMessage: 'direction must be one of: asc, desc.' };
        }
        this.SortField = rawField;
        this.SortDirection = direction;
        this.publishAgentContext();
        this.cdr.detectChanges();
        return { Success: true, Data: { SortField: this.SortField, SortDirection: this.SortDirection } };
    }
    // ----- helpers ----------------------------------------------------------------------------
    /** Loads the transcript turns for a session as full entity objects (for the playback component). */
    async loadTurns(sessionId) {
        const rv = RunView.FromMetadataProvider(this.ProviderToUse);
        const result = await rv.RunView({
            EntityName: 'MJ: Conversation Details',
            ExtraFilter: `AgentSessionID='${sessionId}'`,
            OrderBy: '__mj_CreatedAt ASC',
            ResultType: 'entity_object'
        });
        if (!result.Success) {
            throw new Error(result.ErrorMessage || 'Failed to load transcript turns.');
        }
        return result.Results ?? [];
    }
    /**
     * Builds time-aligned {@link MediaTranscriptCue}s from the loaded turns, for the media player's
     * transcript panel. Skips turns with no text; derives each cue's start from `UtteranceStartMs`,
     * falling back to (turn-created − recording-start) ms, else 0. Cues sort by start ascending.
     */
    buildCues(turns, session) {
        const startedAt = session.RecordingStartedAt;
        const cues = [];
        turns.forEach((turn, index) => {
            const text = turn.Message?.trim() ?? '';
            if (text.length === 0) {
                return;
            }
            cues.push({
                Id: turn.ID || `turn-${index}`,
                StartMs: this.cueStartMs(turn, startedAt),
                EndMs: turn.UtteranceEndMs ?? undefined,
                SpeakerLabel: turn.Role === 'User' ? 'You' : session.AgentName,
                Text: text
            });
        });
        cues.sort((a, b) => a.StartMs - b.StartMs);
        return cues;
    }
    /** A turn's media-relative start (ms): precise offset when present, else derived from t0, else 0. */
    cueStartMs(turn, startedAt) {
        if (turn.UtteranceStartMs != null) {
            return turn.UtteranceStartMs;
        }
        if (startedAt && turn.__mj_CreatedAt) {
            return Math.max(0, turn.__mj_CreatedAt.getTime() - startedAt.getTime());
        }
        return 0;
    }
    /** Projects a raw `simple` view row into the list model, deriving display fields. */
    toRecordedSession(row) {
        const startedAt = this.toDate(row.RecordingStartedAt);
        const createdAt = this.toDate(row.__mj_CreatedAt);
        return {
            ID: row.ID,
            AgentName: row.Agent || 'Realtime Agent',
            ConversationName: row.Conversation ?? null,
            RecordingFileID: row.RecordingFileID,
            RecordingMedia: row.RecordingMedia ?? null,
            RecordingStartedAt: startedAt,
            CreatedAt: createdAt,
            DurationLabel: null
        };
    }
    /**
     * Once turns are loaded, derive a duration (t0 → last turn's end/start) and stamp it on the row
     * so the list shows it. Best-effort: skipped when we lack a recording start time or any turns.
     */
    maybeBackfillDuration(session, turns) {
        if (session.DurationLabel || !session.RecordingStartedAt || turns.length === 0) {
            return;
        }
        const endMs = this.lastTurnOffsetMs(turns, session.RecordingStartedAt);
        if (endMs != null && endMs > 0) {
            session.DurationLabel = this.formatDuration(endMs);
        }
    }
    /** The end offset (ms) of the last turn relative to the recording start, or null. */
    lastTurnOffsetMs(turns, startedAt) {
        const last = turns[turns.length - 1];
        if (last.UtteranceEndMs != null) {
            return last.UtteranceEndMs;
        }
        if (last.UtteranceStartMs != null) {
            return last.UtteranceStartMs;
        }
        if (last.__mj_CreatedAt) {
            return last.__mj_CreatedAt.getTime() - startedAt.getTime();
        }
        return null;
    }
    /** Formats a ms span as `m:ss` (or `h:mm:ss` past an hour). */
    formatDuration(ms) {
        const totalSeconds = Math.max(0, Math.floor(ms / 1000));
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = totalSeconds % 60;
        if (hours > 0) {
            return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
        }
        return `${minutes}:${seconds.toString().padStart(2, '0')}`;
    }
    /** Coerces a `simple` row's date-ish value (string | Date | null) to a Date or null. */
    toDate(value) {
        if (!value) {
            return null;
        }
        const date = value instanceof Date ? value : new Date(value);
        return Number.isNaN(date.getTime()) ? null : date;
    }
    static ɵfac = /*@__PURE__*/ (() => { let ɵRealtimeRecordingsDashboardComponent_BaseFactory; return function RealtimeRecordingsDashboardComponent_Factory(__ngFactoryType__) { return (ɵRealtimeRecordingsDashboardComponent_BaseFactory || (ɵRealtimeRecordingsDashboardComponent_BaseFactory = i0.ɵɵgetInheritedFactory(RealtimeRecordingsDashboardComponent)))(__ngFactoryType__ || RealtimeRecordingsDashboardComponent); }; })();
    static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeRecordingsDashboardComponent, selectors: [["mj-realtime-recordings-dashboard"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 10, vars: 3, consts: [["Title", "Realtime Recordings", "Icon", "fa-solid fa-circle-play", "Subtitle", "Review and replay recorded realtime sessions \u2014 time-aligned audio + transcript."], ["meta", ""], ["actions", ""], [3, "Clicked", "Loading"], [1, "rr-state"], ["role", "alert", 1, "rr-state", "rr-state--error"], [1, "rr-state", "rr-state--empty"], ["direction", "horizontal", "unit", "pixel", 1, "rr-split"], ["Label", "recorded sessions", 3, "Count", "Total"], ["text", "Loading recorded sessions..."], ["aria-hidden", "true", 1, "fa-solid", "fa-triangle-exclamation"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-microphone-slash"], [1, "rr-state-title"], [1, "rr-state-hint"], [3, "size", "minSize", "maxSize"], ["role", "listbox", "aria-label", "Recorded sessions", 1, "rr-list"], ["role", "option", "tabindex", "0", 1, "rr-list-item", 3, "rr-list-item--active"], [3, "size", "minSize"], [1, "rr-detail"], [1, "rr-state", "rr-state--empty", "rr-detail-empty"], [1, "rr-state", "rr-detail-loading"], [3, "FileID", "Transcript", "Provider"], ["role", "option", "tabindex", "0", 1, "rr-list-item", 3, "click", "keydown"], ["aria-hidden", "true", 1, "rr-list-item-icon"], [1, "rr-list-item-body"], [1, "rr-list-item-title"], [1, "rr-list-item-meta"], [1, "rr-list-item-date"], ["aria-hidden", "true", 1, "fa-regular", "fa-clock"], [1, "rr-list-item-duration"], [1, "rr-list-item-conversation", 3, "title"], ["aria-hidden", "true", 1, "fa-solid", "fa-stopwatch"], ["aria-hidden", "true", 1, "fa-regular", "fa-comments"], ["aria-hidden", "true", 1, "fa-solid", "fa-hand-pointer"], ["text", "Loading recording..."]], template: function RealtimeRecordingsDashboardComponent_Template(rf, ctx) { if (rf & 1) {
            i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 0);
            i0.ɵɵconditionalCreate(2, RealtimeRecordingsDashboardComponent_Conditional_2_Template, 2, 2, "div", 1);
            i0.ɵɵelementStart(3, "div", 2)(4, "mj-refresh-button", 3);
            i0.ɵɵlistener("Clicked", function RealtimeRecordingsDashboardComponent_Template_mj_refresh_button_Clicked_4_listener() { return ctx.LoadData(); });
            i0.ɵɵelementEnd()()();
            i0.ɵɵelementStart(5, "mj-page-body");
            i0.ɵɵconditionalCreate(6, RealtimeRecordingsDashboardComponent_Conditional_6_Template, 2, 0, "div", 4)(7, RealtimeRecordingsDashboardComponent_Conditional_7_Template, 6, 1, "div", 5)(8, RealtimeRecordingsDashboardComponent_Conditional_8_Template, 6, 0, "div", 6)(9, RealtimeRecordingsDashboardComponent_Conditional_9_Template, 11, 6, "as-split", 7);
            i0.ɵɵelementEnd()();
        } if (rf & 2) {
            i0.ɵɵadvance(2);
            i0.ɵɵconditional(!ctx.IsLoading && ctx.Sessions.length > 0 ? 2 : -1);
            i0.ɵɵadvance(2);
            i0.ɵɵproperty("Loading", ctx.IsLoading);
            i0.ɵɵadvance(2);
            i0.ɵɵconditional(ctx.IsLoading ? 6 : ctx.ErrorMessage ? 7 : ctx.Sessions.length === 0 ? 8 : 9);
        } }, dependencies: [i1.MJButtonDirective, i1.MJPageHeaderComponent, i1.MJPageLayoutComponent, i1.MJPageBodyComponent, i1.MJStatBadgeComponent, i1.MJRefreshButtonComponent, i2.LoadingComponent, i3.SplitComponent, i3.SplitAreaComponent, i4.MJStorageMediaPlayerComponent], styles: ["\n\n\n.rr-split[_ngcontent-%COMP%] {\n  height: 100%;\n  width: 100%;\n}\n\n\n\n\n.rr-list[_ngcontent-%COMP%] {\n  list-style: none;\n  margin: 0;\n  padding: 0.5rem;\n  height: 100%;\n  overflow-y: auto;\n  background: var(--mj-bg-surface);\n  border-right: 1px solid var(--mj-border-subtle);\n}\n\n.rr-list-item[_ngcontent-%COMP%] {\n  display: flex;\n  gap: 0.75rem;\n  align-items: flex-start;\n  padding: 0.75rem;\n  border-radius: var(--mj-radius-md, 8px);\n  cursor: pointer;\n  border: 1px solid transparent;\n  transition: background 0.12s ease, border-color 0.12s ease;\n}\n\n.rr-list-item[_ngcontent-%COMP%]    + .rr-list-item[_ngcontent-%COMP%] {\n  margin-top: 0.25rem;\n}\n\n.rr-list-item[_ngcontent-%COMP%]:hover {\n  background: var(--mj-bg-surface-hover);\n}\n\n.rr-list-item[_ngcontent-%COMP%]:focus-visible {\n  outline: none;\n  border-color: var(--mj-border-focus);\n  box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n\n.rr-list-item--active[_ngcontent-%COMP%] {\n  background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n  border-color: color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.rr-list-item-icon[_ngcontent-%COMP%] {\n  flex: 0 0 auto;\n  width: 2rem;\n  height: 2rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: var(--mj-radius-md, 8px);\n  background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n  color: var(--mj-brand-primary);\n  font-size: 0.85rem;\n}\n\n.rr-list-item-body[_ngcontent-%COMP%] {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.rr-list-item-title[_ngcontent-%COMP%] {\n  font-weight: 600;\n  color: var(--mj-text-primary);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.rr-list-item-meta[_ngcontent-%COMP%] {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n  margin-top: 0.2rem;\n  font-size: 0.78rem;\n  color: var(--mj-text-secondary);\n}\n\n.rr-list-item-meta[_ngcontent-%COMP%]   i[_ngcontent-%COMP%], \n.rr-list-item-conversation[_ngcontent-%COMP%]   i[_ngcontent-%COMP%] {\n  margin-right: 0.25rem;\n  opacity: 0.85;\n}\n\n.rr-list-item-conversation[_ngcontent-%COMP%] {\n  margin-top: 0.3rem;\n  font-size: 0.78rem;\n  color: var(--mj-text-muted);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n\n\n\n.rr-detail[_ngcontent-%COMP%] {\n  height: 100%;\n  overflow: auto;\n  background: var(--mj-bg-page);\n  padding: 1rem;\n}\n\n.rr-detail[_ngcontent-%COMP%]   mj-realtime-evidence-playback[_ngcontent-%COMP%] {\n  display: block;\n  height: 100%;\n}\n\n\n\n\n.rr-state[_ngcontent-%COMP%] {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 0.6rem;\n  text-align: center;\n  padding: 2.5rem 1.5rem;\n  min-height: 240px;\n  color: var(--mj-text-secondary);\n}\n\n.rr-state[_ngcontent-%COMP%]    > i[_ngcontent-%COMP%] {\n  font-size: 2rem;\n  color: var(--mj-text-muted);\n}\n\n.rr-state-title[_ngcontent-%COMP%] {\n  font-weight: 600;\n  font-size: 1rem;\n  color: var(--mj-text-primary);\n  margin: 0;\n}\n\n.rr-state-hint[_ngcontent-%COMP%] {\n  max-width: 380px;\n  font-size: 0.85rem;\n  color: var(--mj-text-muted);\n  margin: 0;\n}\n\n.rr-state--error[_ngcontent-%COMP%] {\n  color: var(--mj-status-error-text);\n}\n\n.rr-state--error[_ngcontent-%COMP%]    > i[_ngcontent-%COMP%] {\n  color: var(--mj-status-error);\n}\n\n.rr-detail-empty[_ngcontent-%COMP%], \n.rr-detail-loading[_ngcontent-%COMP%] {\n  height: 100%;\n}\n\n\n\n\n\n\n@media (max-width: 720px) {\n  .rr-list[_ngcontent-%COMP%] {\n    padding: 0.25rem;\n  }\n\n  .rr-detail[_ngcontent-%COMP%] {\n    padding: 0.5rem;\n  }\n}"] });
};
RealtimeRecordingsDashboardComponent = __decorate([
    RegisterClass(BaseResourceComponent, 'RealtimeRecordingsDashboard')
], RealtimeRecordingsDashboardComponent);
export { RealtimeRecordingsDashboardComponent };
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeRecordingsDashboardComponent, [{
        type: Component,
        args: [{ standalone: false, selector: 'mj-realtime-recordings-dashboard', template: "<mj-page-layout>\n  <mj-page-header\n    Title=\"Realtime Recordings\"\n    Icon=\"fa-solid fa-circle-play\"\n    Subtitle=\"Review and replay recorded realtime sessions \u2014 time-aligned audio + transcript.\">\n    @if (!IsLoading && Sessions.length > 0) {\n      <div meta>\n        <mj-stat-badge [Count]=\"VisibleSessions.length\" [Total]=\"Sessions.length\" Label=\"recorded sessions\" />\n      </div>\n    }\n    <div actions>\n      <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"LoadData()\" />\n    </div>\n  </mj-page-header>\n\n  <mj-page-body>\n    @if (IsLoading) {\n      <div class=\"rr-state\">\n        <mj-loading text=\"Loading recorded sessions...\"></mj-loading>\n      </div>\n    } @else if (ErrorMessage) {\n      <div class=\"rr-state rr-state--error\" role=\"alert\">\n        <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n        <p>{{ ErrorMessage }}</p>\n        <button mjButton variant=\"primary\" size=\"sm\" (click)=\"LoadData()\">Try again</button>\n      </div>\n    } @else if (Sessions.length === 0) {\n      <div class=\"rr-state rr-state--empty\">\n        <i class=\"fa-solid fa-microphone-slash\" aria-hidden=\"true\"></i>\n        <p class=\"rr-state-title\">No recorded sessions yet.</p>\n        <p class=\"rr-state-hint\">\n          When a realtime session is recorded, it will appear here for review and replay.\n        </p>\n      </div>\n    } @else {\n      <as-split direction=\"horizontal\" unit=\"pixel\" class=\"rr-split\">\n        <as-split-area [size]=\"360\" [minSize]=\"260\" [maxSize]=\"560\">\n          <ul class=\"rr-list\" role=\"listbox\" aria-label=\"Recorded sessions\">\n            @for (session of VisibleSessions; track TrackBySession($index, session)) {\n              <li\n                class=\"rr-list-item\"\n                role=\"option\"\n                tabindex=\"0\"\n                [class.rr-list-item--active]=\"SelectedSession?.ID === session.ID\"\n                [attr.aria-selected]=\"SelectedSession?.ID === session.ID\"\n                (click)=\"SelectSession(session)\"\n                (keydown)=\"OnRowKeydown($event, session)\">\n                <div class=\"rr-list-item-icon\" aria-hidden=\"true\">\n                  <i [class]=\"session.RecordingMedia === 'AudioVideo' ? 'fa-solid fa-video' : 'fa-solid fa-waveform-lines'\"></i>\n                </div>\n                <div class=\"rr-list-item-body\">\n                  <div class=\"rr-list-item-title\">{{ session.AgentName }}</div>\n                  <div class=\"rr-list-item-meta\">\n                    <span class=\"rr-list-item-date\">\n                      <i class=\"fa-regular fa-clock\" aria-hidden=\"true\"></i>\n                      {{ SessionDateLabel(session) }}\n                    </span>\n                    @if (session.DurationLabel) {\n                      <span class=\"rr-list-item-duration\">\n                        <i class=\"fa-solid fa-stopwatch\" aria-hidden=\"true\"></i>\n                        {{ session.DurationLabel }}\n                      </span>\n                    }\n                  </div>\n                  @if (session.ConversationName) {\n                    <div class=\"rr-list-item-conversation\" [title]=\"session.ConversationName\">\n                      <i class=\"fa-regular fa-comments\" aria-hidden=\"true\"></i>\n                      {{ session.ConversationName }}\n                    </div>\n                  }\n                </div>\n              </li>\n            }\n          </ul>\n        </as-split-area>\n\n        <as-split-area [size]=\"640\" [minSize]=\"360\">\n          <div class=\"rr-detail\">\n            @if (!SelectedSession) {\n              <div class=\"rr-state rr-state--empty rr-detail-empty\">\n                <i class=\"fa-solid fa-hand-pointer\" aria-hidden=\"true\"></i>\n                <p class=\"rr-state-title\">Select a session to replay</p>\n                <p class=\"rr-state-hint\">\n                  Pick a recording from the list to hear its audio alongside the synchronized transcript.\n                </p>\n              </div>\n            } @else if (IsDetailLoading) {\n              <div class=\"rr-state rr-detail-loading\">\n                <mj-loading text=\"Loading recording...\"></mj-loading>\n              </div>\n            } @else if (DetailErrorMessage) {\n              <div class=\"rr-state rr-state--error\" role=\"alert\">\n                <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>\n                <p>{{ DetailErrorMessage }}</p>\n                <button mjButton variant=\"primary\" size=\"sm\" (click)=\"SelectSession(SelectedSession!)\">Try again</button>\n              </div>\n            } @else {\n              <mj-storage-media-player\n                [FileID]=\"SelectedSession.RecordingFileID\"\n                [Transcript]=\"SelectedCues\"\n                [Provider]=\"Provider\">\n              </mj-storage-media-player>\n            }\n          </div>\n        </as-split-area>\n      </as-split>\n    }\n  </mj-page-body>\n</mj-page-layout>\n", styles: ["/* ===== Realtime Recordings dashboard ===== */\n\n.rr-split {\n  height: 100%;\n  width: 100%;\n}\n\n/* ----- master list pane ----- */\n\n.rr-list {\n  list-style: none;\n  margin: 0;\n  padding: 0.5rem;\n  height: 100%;\n  overflow-y: auto;\n  background: var(--mj-bg-surface);\n  border-right: 1px solid var(--mj-border-subtle);\n}\n\n.rr-list-item {\n  display: flex;\n  gap: 0.75rem;\n  align-items: flex-start;\n  padding: 0.75rem;\n  border-radius: var(--mj-radius-md, 8px);\n  cursor: pointer;\n  border: 1px solid transparent;\n  transition: background 0.12s ease, border-color 0.12s ease;\n}\n\n.rr-list-item + .rr-list-item {\n  margin-top: 0.25rem;\n}\n\n.rr-list-item:hover {\n  background: var(--mj-bg-surface-hover);\n}\n\n.rr-list-item:focus-visible {\n  outline: none;\n  border-color: var(--mj-border-focus);\n  box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n\n.rr-list-item--active {\n  background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n  border-color: color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.rr-list-item-icon {\n  flex: 0 0 auto;\n  width: 2rem;\n  height: 2rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: var(--mj-radius-md, 8px);\n  background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n  color: var(--mj-brand-primary);\n  font-size: 0.85rem;\n}\n\n.rr-list-item-body {\n  flex: 1 1 auto;\n  min-width: 0;\n}\n\n.rr-list-item-title {\n  font-weight: 600;\n  color: var(--mj-text-primary);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.rr-list-item-meta {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n  margin-top: 0.2rem;\n  font-size: 0.78rem;\n  color: var(--mj-text-secondary);\n}\n\n.rr-list-item-meta i,\n.rr-list-item-conversation i {\n  margin-right: 0.25rem;\n  opacity: 0.85;\n}\n\n.rr-list-item-conversation {\n  margin-top: 0.3rem;\n  font-size: 0.78rem;\n  color: var(--mj-text-muted);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n/* ----- detail (playback) pane ----- */\n\n.rr-detail {\n  height: 100%;\n  overflow: auto;\n  background: var(--mj-bg-page);\n  padding: 1rem;\n}\n\n.rr-detail mj-realtime-evidence-playback {\n  display: block;\n  height: 100%;\n}\n\n/* ----- state blocks (loading / empty / error) ----- */\n\n.rr-state {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  gap: 0.6rem;\n  text-align: center;\n  padding: 2.5rem 1.5rem;\n  min-height: 240px;\n  color: var(--mj-text-secondary);\n}\n\n.rr-state > i {\n  font-size: 2rem;\n  color: var(--mj-text-muted);\n}\n\n.rr-state-title {\n  font-weight: 600;\n  font-size: 1rem;\n  color: var(--mj-text-primary);\n  margin: 0;\n}\n\n.rr-state-hint {\n  max-width: 380px;\n  font-size: 0.85rem;\n  color: var(--mj-text-muted);\n  margin: 0;\n}\n\n.rr-state--error {\n  color: var(--mj-status-error-text);\n}\n\n.rr-state--error > i {\n  color: var(--mj-status-error);\n}\n\n.rr-detail-empty,\n.rr-detail-loading {\n  height: 100%;\n}\n\n/* ----- responsive: tighten paddings on narrow viewports -----\n   (angular-split owns pane layout/direction via its [direction] input, so we\n   only adjust inner spacing here rather than overriding its flex-direction) */\n\n@media (max-width: 720px) {\n  .rr-list {\n    padding: 0.25rem;\n  }\n\n  .rr-detail {\n    padding: 0.5rem;\n  }\n}\n"] }]
    }], null, null); })();
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeRecordingsDashboardComponent, { className: "RealtimeRecordingsDashboardComponent", filePath: "src/RealtimeRecordings/realtime-recordings-dashboard.component.ts", lineNumber: 63 }); })();
/**
 * Tree-shaking guard — referenced from the feature module's public-api so the bundler can't drop the
 * `@RegisterClass`-decorated component (which is only ever instantiated dynamically via the class factory).
 */
export function LoadRealtimeRecordingsDashboard() {
    // intentionally empty
}
//# sourceMappingURL=realtime-recordings-dashboard.component.js.map