import { Injectable } from '@angular/core';
import { BehaviorSubject, from, combineLatest } from 'rxjs';
import { switchMap, shareReplay, tap } from 'rxjs/operators';
import { RunView, Metadata } from '@memberjunction/core';
import * as i0 from "@angular/core";
export class AIInstrumentationService {
    _dateRange$ = new BehaviorSubject({
        start: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
        end: new Date()
    });
    _refreshTrigger$ = new BehaviorSubject(0);
    _isLoading$ = new BehaviorSubject(false);
    metadata = new Metadata();
    // Expose loading state as observable
    isLoading$ = this._isLoading$.asObservable();
    constructor() { }
    // Main data streams - trigger on refresh or date range change
    kpis$ = combineLatest([this._refreshTrigger$, this._dateRange$]).pipe(tap(() => this._isLoading$.next(true)), switchMap(() => from(this.loadKPIs())), tap(() => this.checkLoadingComplete()), shareReplay(1));
    trends$ = combineLatest([this._refreshTrigger$, this._dateRange$]).pipe(tap(() => this._isLoading$.next(true)), switchMap(() => from(this.loadTrends())), tap(() => this.checkLoadingComplete()), shareReplay(1));
    liveExecutions$ = combineLatest([this._refreshTrigger$, this._dateRange$]).pipe(tap(() => this._isLoading$.next(true)), switchMap(() => from(this.loadLiveExecutions())), tap(() => this.checkLoadingComplete()), shareReplay(1));
    chartData$ = combineLatest([this._refreshTrigger$, this._dateRange$]).pipe(tap(() => this._isLoading$.next(true)), switchMap(() => from(this.loadChartData())), tap(() => this.checkLoadingComplete()), shareReplay(1));
    loadingCount = 0;
    checkLoadingComplete() {
        // Simple mechanism to track when all streams have completed
        setTimeout(() => {
            this._isLoading$.next(false);
        }, 100);
    }
    setDateRange(start, end) {
        this._dateRange$.next({ start, end });
        // No need to manually refresh - streams now react to date range changes
    }
    refresh() {
        this._refreshTrigger$.next(this._refreshTrigger$.value + 1);
    }
    async loadKPIs() {
        const { start, end } = this._dateRange$.value;
        const rv = new RunView();
        // Use RunViews to batch the queries
        const [promptResults, agentResults] = await rv.RunViews([
            {
                EntityName: 'MJ: AI Prompt Runs',
                ExtraFilter: `RunAt >= '${start.toISOString()}' AND RunAt <= '${end.toISOString()}'`
            },
            {
                EntityName: 'MJ: AI Agent Runs',
                ExtraFilter: `StartedAt >= '${start.toISOString()}' AND StartedAt <= '${end.toISOString()}'`
            }
        ]);
        const promptRuns = promptResults.Results;
        const agentRuns = agentResults.Results;
        // Calculate KPIs
        const totalExecutions = promptRuns.length + agentRuns.length;
        const activeExecutions = this.countActiveExecutions(promptRuns, agentRuns);
        const totalCost = this.sumCosts(promptRuns, agentRuns);
        const totalTokens = this.sumTokens(promptRuns, agentRuns);
        const avgExecutionTime = this.calculateAverageExecutionTime(promptRuns, agentRuns);
        const successRate = this.calculateSuccessRate(promptRuns, agentRuns);
        const errorRate = 1 - successRate;
        const costPerToken = totalTokens > 0 ? totalCost / totalTokens : 0;
        const dailyCostBurn = this.calculateDailyCostBurn(promptRuns, agentRuns);
        const topModel = await this.getTopModel(promptRuns);
        const topAgent = await this.getTopAgent(agentRuns);
        return {
            totalExecutions,
            activeExecutions,
            totalCost,
            costCurrency: 'USD',
            avgExecutionTime,
            successRate,
            totalTokens,
            costPerToken,
            topModel,
            topAgent,
            errorRate,
            dailyCostBurn
        };
    }
    async loadTrends() {
        const { start, end } = this._dateRange$.value;
        const hourlyBuckets = this.createHourlyBuckets(start, end);
        // Calculate bucket size based on the number of buckets
        const duration = end.getTime() - start.getTime();
        const hours = duration / (1000 * 60 * 60);
        let bucketSizeMs;
        if (hours <= 24) {
            bucketSizeMs = 60 * 60 * 1000; // 1 hour
        }
        else if (hours <= 24 * 7) {
            bucketSizeMs = 4 * 60 * 60 * 1000; // 4 hours
        }
        else {
            bucketSizeMs = 24 * 60 * 60 * 1000; // 24 hours
        }
        // Load all data in a single query instead of per-bucket
        const rv = new RunView();
        const [promptResults, agentResults] = await rv.RunViews([
            {
                EntityName: 'MJ: AI Prompt Runs',
                ExtraFilter: `RunAt >= '${start.toISOString()}' AND RunAt <= '${end.toISOString()}'`
            },
            {
                EntityName: 'MJ: AI Agent Runs',
                ExtraFilter: `StartedAt >= '${start.toISOString()}' AND StartedAt <= '${end.toISOString()}'`
            }
        ]);
        const allPromptRuns = promptResults.Results;
        const allAgentRuns = agentResults.Results;
        // Now aggregate data into buckets on the client side
        const trends = [];
        for (const bucket of hourlyBuckets) {
            const bucketStart = bucket;
            const bucketEnd = new Date(bucket.getTime() + bucketSizeMs);
            // Filter runs for this bucket
            const promptRuns = allPromptRuns.filter(r => {
                const runAt = new Date(r.RunAt);
                return runAt >= bucketStart && runAt < bucketEnd;
            });
            const agentRuns = allAgentRuns.filter(r => {
                const startedAt = new Date(r.StartedAt);
                return startedAt >= bucketStart && startedAt < bucketEnd;
            });
            trends.push({
                timestamp: bucket,
                executions: promptRuns.length + agentRuns.length,
                cost: this.sumCosts(promptRuns, agentRuns),
                tokens: this.sumTokens(promptRuns, agentRuns),
                avgTime: this.calculateAverageExecutionTime(promptRuns, agentRuns),
                errors: this.countErrors(promptRuns, agentRuns)
            });
        }
        return trends;
    }
    async loadLiveExecutions() {
        const now = new Date();
        const recentTime = new Date(now.getTime() - 5 * 60 * 1000);
        const rv = new RunView();
        const [promptResults, agentResults] = await rv.RunViews([
            {
                EntityName: 'MJ: AI Prompt Runs',
                ExtraFilter: `RunAt >= '${recentTime.toISOString()}'`,
                OrderBy: 'RunAt DESC'
            },
            {
                EntityName: 'MJ: AI Agent Runs',
                ExtraFilter: `StartedAt >= '${recentTime.toISOString()}'`,
                OrderBy: 'StartedAt DESC'
            }
        ]);
        const promptRuns = promptResults.Results;
        const agentRuns = agentResults.Results;
        const liveExecutions = [];
        // Process prompt runs - they already have the prompt name in the Prompt field
        for (const run of promptRuns) {
            const isRunning = !run.CompletedAt && run.Success !== false;
            const duration = run.CompletedAt ?
                new Date(run.CompletedAt).getTime() - new Date(run.RunAt).getTime() :
                now.getTime() - new Date(run.RunAt).getTime();
            liveExecutions.push({
                id: run.ID,
                type: 'prompt',
                name: run.Prompt || 'Unnamed Prompt', // Use the Prompt field directly
                status: isRunning ? 'running' : (run.Success ? 'completed' : 'failed'),
                startTime: new Date(run.RunAt),
                duration: duration,
                cost: run.Cost || 0,
                tokens: run.TokensUsed || 0,
                progress: isRunning ? Math.min(90, (duration / 30000) * 100) : 100
            });
        }
        // Process agent runs - they already have the agent name in the Agent field
        for (const run of agentRuns) {
            const isRunning = run.Status === 'Running';
            const duration = run.CompletedAt ?
                new Date(run.CompletedAt).getTime() - new Date(run.StartedAt).getTime() :
                now.getTime() - new Date(run.StartedAt).getTime();
            liveExecutions.push({
                id: run.ID,
                type: 'agent',
                name: run.Agent || 'Unnamed Agent', // Use the Agent field directly
                status: run.Status.toLowerCase(),
                startTime: new Date(run.StartedAt),
                duration: duration,
                cost: run.TotalCost || 0,
                tokens: run.TotalTokensUsed || 0,
                progress: isRunning ? Math.min(90, (duration / 60000) * 100) : 100
            });
        }
        return liveExecutions.sort((a, b) => b.startTime.getTime() - a.startTime.getTime());
    }
    async loadChartData() {
        const { start, end } = this._dateRange$.value;
        const dateFilter = `RunAt >= '${start.toISOString()}' AND RunAt <= '${end.toISOString()}'`;
        const promptRv = new RunView();
        const promptResults = await promptRv.RunView({
            EntityName: 'MJ: AI Prompt Runs',
            ExtraFilter: dateFilter
        });
        const promptRuns = promptResults.Results;
        const executionTrends = await this.loadTrends();
        const costByModel = await this.analyzeCostByModel(promptRuns);
        const performanceMatrix = await this.analyzePerformanceMatrix(promptRuns);
        const tokenEfficiency = await this.analyzeTokenEfficiency(promptRuns);
        return {
            executionTrends,
            costByModel,
            performanceMatrix,
            tokenEfficiency
        };
    }
    // Helper methods
    countActiveExecutions(promptRuns, agentRuns) {
        const activePrompts = promptRuns.filter(r => !r.CompletedAt && r.Success !== false).length;
        const activeAgents = agentRuns.filter(r => r.Status === 'Running').length;
        return activePrompts + activeAgents;
    }
    sumCosts(promptRuns, agentRuns) {
        const promptCost = promptRuns.reduce((sum, r) => sum + (r.Cost || 0), 0);
        const agentCost = agentRuns.reduce((sum, r) => sum + (r.TotalCost || 0), 0);
        return promptCost + agentCost;
    }
    sumTokens(promptRuns, agentRuns) {
        const promptTokens = promptRuns.reduce((sum, r) => sum + (r.TokensUsed || 0), 0);
        const agentTokens = agentRuns.reduce((sum, r) => sum + (r.TotalTokensUsed || 0), 0);
        return promptTokens + agentTokens;
    }
    calculateAverageExecutionTime(promptRuns, agentRuns) {
        const promptTimes = promptRuns
            .filter(r => r.ExecutionTimeMS)
            .map(r => r.ExecutionTimeMS);
        const agentTimes = agentRuns
            .filter(r => r.StartedAt && r.CompletedAt)
            .map(r => new Date(r.CompletedAt).getTime() - new Date(r.StartedAt).getTime());
        const allTimes = [...promptTimes, ...agentTimes];
        return allTimes.length > 0 ? allTimes.reduce((sum, time) => sum + time, 0) / allTimes.length : 0;
    }
    calculateSuccessRate(promptRuns, agentRuns) {
        const totalExecutions = promptRuns.length + agentRuns.length;
        if (totalExecutions === 0)
            return 1;
        const successfulPrompts = promptRuns.filter(r => r.Success).length;
        const successfulAgents = agentRuns.filter(r => r.Success).length;
        return (successfulPrompts + successfulAgents) / totalExecutions;
    }
    countErrors(promptRuns, agentRuns) {
        const promptErrors = promptRuns.filter(r => !r.Success).length;
        const agentErrors = agentRuns.filter(r => !r.Success).length;
        return promptErrors + agentErrors;
    }
    calculateDailyCostBurn(promptRuns, agentRuns) {
        const now = new Date();
        const dayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        const todayPrompts = promptRuns.filter(r => new Date(r.RunAt) >= dayStart);
        const todayAgents = agentRuns.filter(r => new Date(r.StartedAt) >= dayStart);
        return this.sumCosts(todayPrompts, todayAgents);
    }
    createHourlyBuckets(start, end) {
        const buckets = [];
        const current = new Date(start);
        const duration = end.getTime() - start.getTime();
        const hours = duration / (1000 * 60 * 60);
        // Determine bucket size based on duration
        let bucketSize;
        if (hours <= 24) {
            // For up to 24 hours, use hourly buckets
            bucketSize = 1;
            current.setMinutes(0, 0, 0);
        }
        else if (hours <= 24 * 7) {
            // For up to 7 days, use 4-hour buckets
            bucketSize = 4;
            current.setHours(Math.floor(current.getHours() / 4) * 4, 0, 0, 0);
        }
        else {
            // For more than 7 days, use daily buckets
            bucketSize = 24;
            current.setHours(0, 0, 0, 0);
        }
        while (current < end) {
            buckets.push(new Date(current));
            current.setHours(current.getHours() + bucketSize);
        }
        return buckets;
    }
    async getTopModel(promptRuns) {
        const modelCounts = new Map();
        const modelNames = new Map();
        // Count models and track their names from the Model field
        for (const run of promptRuns) {
            if (run.ModelID && run.Model) {
                const count = modelCounts.get(run.ModelID) || 0;
                modelCounts.set(run.ModelID, count + 1);
                modelNames.set(run.ModelID, run.Model);
            }
        }
        if (modelCounts.size === 0)
            return 'N/A';
        const topModelId = Array.from(modelCounts.entries())
            .sort(([, a], [, b]) => b - a)[0][0];
        return modelNames.get(topModelId) || 'Unknown Model';
    }
    async getTopAgent(agentRuns) {
        const agentCounts = new Map();
        const agentNames = new Map();
        // Count agents and track their names from the Agent field
        for (const run of agentRuns) {
            if (run.AgentID && run.Agent) {
                const count = agentCounts.get(run.AgentID) || 0;
                agentCounts.set(run.AgentID, count + 1);
                agentNames.set(run.AgentID, run.Agent);
            }
        }
        if (agentCounts.size === 0)
            return 'N/A';
        const topAgentId = Array.from(agentCounts.entries())
            .sort(([, a], [, b]) => b - a)[0][0];
        return agentNames.get(topAgentId) || 'Unknown Agent';
    }
    async analyzeCostByModel(promptRuns) {
        const modelStats = new Map();
        for (const run of promptRuns) {
            if (run.ModelID && run.Model) {
                const existing = modelStats.get(run.ModelID) || { cost: 0, tokens: 0, name: run.Model };
                existing.cost += run.Cost || 0;
                existing.tokens += run.TokensUsed || 0;
                modelStats.set(run.ModelID, existing);
            }
        }
        const results = [];
        for (const [modelId, stats] of modelStats.entries()) {
            results.push({
                model: stats.name,
                cost: stats.cost,
                tokens: stats.tokens
            });
        }
        return results.sort((a, b) => b.cost - a.cost);
    }
    async analyzePerformanceMatrix(promptRuns) {
        const combinations = new Map();
        for (const run of promptRuns) {
            if (run.AgentID && run.ModelID && run.ExecutionTimeMS) {
                const key = `${run.AgentID}:${run.ModelID}`;
                const existing = combinations.get(key) || {
                    times: [],
                    successes: 0,
                    total: 0,
                    agentName: run.Agent || 'Unknown Agent',
                    modelName: run.Model || 'Unknown Model'
                };
                existing.times.push(run.ExecutionTimeMS);
                existing.total += 1;
                if (run.Success)
                    existing.successes += 1;
                combinations.set(key, existing);
            }
        }
        const results = [];
        for (const [key, data] of combinations.entries()) {
            results.push({
                agent: data.agentName,
                model: data.modelName,
                avgTime: data.times.reduce((sum, time) => sum + time, 0) / data.times.length,
                successRate: data.successes / data.total
            });
        }
        return results;
    }
    async analyzeTokenEfficiency(promptRuns) {
        const modelEfficiency = new Map();
        for (const run of promptRuns) {
            if (run.ModelID && run.Model && run.TokensPrompt && run.TokensCompletion) {
                const existing = modelEfficiency.get(run.ModelID) || { input: 0, output: 0, cost: 0, name: run.Model };
                existing.input += run.TokensPrompt;
                existing.output += run.TokensCompletion;
                existing.cost += run.Cost || 0;
                modelEfficiency.set(run.ModelID, existing);
            }
        }
        const results = [];
        for (const [modelId, data] of modelEfficiency.entries()) {
            results.push({
                inputTokens: data.input,
                outputTokens: data.output,
                cost: data.cost,
                model: data.name
            });
        }
        return results;
    }
    async getExecutionDetails(executionId, type) {
        try {
            if (type === 'prompt') {
                return await this.getPromptExecutionDetails(executionId);
            }
            else {
                return await this.getAgentExecutionDetails(executionId);
            }
        }
        catch (error) {
            console.error('Error loading execution details:', error);
            return null;
        }
    }
    async getPromptExecutionDetails(promptRunId) {
        const rv = new RunView();
        const result = await rv.RunView({
            EntityName: 'MJ: AI Prompt Runs',
            ExtraFilter: `ID = '${promptRunId}'`
        });
        const run = result.Results[0];
        if (!run)
            throw new Error('Prompt run not found');
        const childrenResult = await rv.RunView({
            EntityName: 'MJ: AI Prompt Runs',
            ExtraFilter: `ParentID = '${promptRunId}'`
        });
        const children = await Promise.all(childrenResult.Results.map(child => this.getPromptExecutionDetails(child.ID)));
        return {
            id: run.ID,
            type: 'prompt',
            name: run.Prompt || 'Unnamed Prompt',
            status: run.Success ? 'completed' : 'failed',
            startTime: new Date(run.RunAt),
            endTime: run.CompletedAt ? new Date(run.CompletedAt) : undefined,
            cost: run.Cost || 0,
            tokens: run.TokensUsed || 0,
            success: run.Success || false,
            errorMessage: run.ErrorMessage || undefined,
            parentId: run.ParentID || undefined,
            children,
            model: run.Model || undefined
        };
    }
    async getAgentExecutionDetails(agentRunId) {
        const rv = new RunView();
        const result = await rv.RunView({
            EntityName: 'MJ: AI Agent Runs',
            ExtraFilter: `ID = '${agentRunId}'`
        });
        const run = result.Results[0];
        if (!run)
            throw new Error('Agent run not found');
        const childrenResult = await rv.RunView({
            EntityName: 'MJ: AI Agent Runs',
            ExtraFilter: `ParentRunID = '${agentRunId}'`
        });
        const children = await Promise.all(childrenResult.Results.map(child => this.getAgentExecutionDetails(child.ID)));
        return {
            id: run.ID,
            type: 'agent',
            name: run.Agent || 'Unnamed Agent',
            status: run.Status.toLowerCase(),
            startTime: new Date(run.StartedAt),
            endTime: run.CompletedAt ? new Date(run.CompletedAt) : undefined,
            cost: run.TotalCost || 0,
            tokens: run.TotalTokensUsed || 0,
            success: run.Success || false,
            errorMessage: run.ErrorMessage || undefined,
            parentId: run.ParentRunID || undefined,
            children
        };
    }
    static ɵfac = function AIInstrumentationService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AIInstrumentationService)(); };
    static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: AIInstrumentationService, factory: AIInstrumentationService.ɵfac, providedIn: 'root' });
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AIInstrumentationService, [{
        type: Injectable,
        args: [{
                providedIn: 'root'
            }]
    }], () => [], null); })();
//# sourceMappingURL=ai-instrumentation.service.js.map