/**
 * @fileoverview Cost & Budget Analytics -- Option B.
 *
 * Displays cost KPIs with period deltas, a daily cost bar chart with anomaly detection,
 * a cost breakdown treemap, and a detailed cost-by-model table with CSV export.
 */
import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
import { Subject } from 'rxjs';
import { RunView } from '@memberjunction/core';
import { NormalizeUUID } from '@memberjunction/global';
import { cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
import * as i0 from "@angular/core";
import * as i1 from "@memberjunction/ng-shared-generic";
import * as i2 from "@angular/common";
const _forTrack0 = ($index, $item) => $item.Label;
const _forTrack1 = ($index, $item) => $item.Model;
const _forTrack2 = ($index, $item) => $item.Date;
function AnalyticsCostBudgetComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 0);
    i0.ɵɵelement(1, "mj-loading", 1);
    i0.ɵɵelementEnd();
} }
function AnalyticsCostBudgetComponent_Conditional_1_For_2_Conditional_8_Conditional_1_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelement(0, "i", 32);
} }
function AnalyticsCostBudgetComponent_Conditional_1_For_2_Conditional_8_Conditional_2_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelement(0, "i", 33);
} }
function AnalyticsCostBudgetComponent_Conditional_1_For_2_Conditional_8_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 31);
    i0.ɵɵconditionalCreate(1, AnalyticsCostBudgetComponent_Conditional_1_For_2_Conditional_8_Conditional_1_Template, 1, 0, "i", 32)(2, AnalyticsCostBudgetComponent_Conditional_1_For_2_Conditional_8_Conditional_2_Template, 1, 0, "i", 33);
    i0.ɵɵtext(3);
    i0.ɵɵpipe(4, "number");
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const kpi_r2 = i0.ɵɵnextContext().$implicit;
    i0.ɵɵclassProp("kpi-delta--up", kpi_r2.DeltaDirection === "up")("kpi-delta--down", kpi_r2.DeltaDirection === "down");
    i0.ɵɵadvance();
    i0.ɵɵconditional(kpi_r2.DeltaDirection === "up" ? 1 : kpi_r2.DeltaDirection === "down" ? 2 : -1);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1(" ", i0.ɵɵpipeBind2(4, 6, kpi_r2.Delta, "1.1-1"), "% vs prev ");
} }
function AnalyticsCostBudgetComponent_Conditional_1_For_2_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 25)(1, "div", 26);
    i0.ɵɵelement(2, "i");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(3, "div", 27)(4, "div", 28);
    i0.ɵɵtext(5);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(6, "div", 29);
    i0.ɵɵtext(7);
    i0.ɵɵelementEnd();
    i0.ɵɵconditionalCreate(8, AnalyticsCostBudgetComponent_Conditional_1_For_2_Conditional_8_Template, 5, 9, "div", 30);
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const kpi_r2 = ctx.$implicit;
    i0.ɵɵclassProp("kpi-card--highlighted", kpi_r2.Highlighted);
    i0.ɵɵadvance(2);
    i0.ɵɵclassMap(kpi_r2.Icon);
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(kpi_r2.Label);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(kpi_r2.Value);
    i0.ɵɵadvance();
    i0.ɵɵconditional(kpi_r2.Delta != null ? 8 : -1);
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_10_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 10);
    i0.ɵɵtext(1, "No cost data for selected period");
    i0.ɵɵelementEnd();
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_11_Conditional_2_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 37)(1, "span", 38);
    i0.ɵɵtext(2, "avg");
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const ctx_r2 = i0.ɵɵnextContext(3);
    i0.ɵɵstyleProp("bottom", ctx_r2.AvgLinePercent, "%");
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_11_For_4_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 36);
    i0.ɵɵelement(1, "div", 39);
    i0.ɵɵelementStart(2, "div", 40);
    i0.ɵɵtext(3);
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const bar_r4 = ctx.$implicit;
    const ctx_r2 = i0.ɵɵnextContext(3);
    i0.ɵɵproperty("title", bar_r4.Label + ": " + ctx_r2.FormatCurrency(bar_r4.Cost));
    i0.ɵɵadvance();
    i0.ɵɵstyleProp("height", bar_r4.HeightPercent, "%");
    i0.ɵɵclassProp("bar--anomaly", bar_r4.IsAnomaly);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(bar_r4.Label);
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_11_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 11)(1, "div", 34);
    i0.ɵɵconditionalCreate(2, AnalyticsCostBudgetComponent_Conditional_1_Conditional_11_Conditional_2_Template, 3, 2, "div", 35);
    i0.ɵɵrepeaterCreate(3, AnalyticsCostBudgetComponent_Conditional_1_Conditional_11_For_4_Template, 4, 6, "div", 36, _forTrack2);
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const ctx_r2 = i0.ɵɵnextContext(2);
    i0.ɵɵadvance(2);
    i0.ɵɵconditional(ctx_r2.AvgLinePercent > 0 ? 2 : -1);
    i0.ɵɵadvance();
    i0.ɵɵrepeater(ctx_r2.DailyBars);
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_18_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 10);
    i0.ɵɵtext(1, "No vendor cost data");
    i0.ɵɵelementEnd();
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_19_For_2_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 42);
    i0.ɵɵpipe(1, "number");
    i0.ɵɵelementStart(2, "span", 43);
    i0.ɵɵtext(3);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(4, "span", 44);
    i0.ɵɵtext(5);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(6, "span", 45);
    i0.ɵɵtext(7);
    i0.ɵɵpipe(8, "number");
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const cell_r5 = ctx.$implicit;
    const ctx_r2 = i0.ɵɵnextContext(3);
    i0.ɵɵstyleProp("background", cell_r5.Color)("flex-basis", cell_r5.Percent, "%");
    i0.ɵɵproperty("title", cell_r5.Label + ": " + ctx_r2.FormatCurrency(cell_r5.Cost) + " (" + i0.ɵɵpipeBind2(1, 8, cell_r5.Percent, "1.0-0") + "%)");
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(cell_r5.Label);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(cell_r5.Cost));
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(8, 11, cell_r5.Percent, "1.0-0"), "%");
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_19_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "div", 15);
    i0.ɵɵrepeaterCreate(1, AnalyticsCostBudgetComponent_Conditional_1_Conditional_19_For_2_Template, 9, 14, "div", 41, _forTrack0);
    i0.ɵɵelementEnd();
} if (rf & 2) {
    const ctx_r2 = i0.ɵɵnextContext(2);
    i0.ɵɵadvance();
    i0.ɵɵrepeater(ctx_r2.TreemapCells);
} }
function AnalyticsCostBudgetComponent_Conditional_1_Conditional_55_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "tr")(1, "td", 46);
    i0.ɵɵtext(2, "No data available");
    i0.ɵɵelementEnd()();
} }
function AnalyticsCostBudgetComponent_Conditional_1_For_57_Template(rf, ctx) { if (rf & 1) {
    i0.ɵɵelementStart(0, "tr")(1, "td", 47);
    i0.ɵɵtext(2);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(3, "td", 48);
    i0.ɵɵtext(4);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(5, "td", 49);
    i0.ɵɵtext(6);
    i0.ɵɵpipe(7, "number");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(8, "td", 49);
    i0.ɵɵtext(9);
    i0.ɵɵpipe(10, "number");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(11, "td", 49);
    i0.ɵɵtext(12);
    i0.ɵɵpipe(13, "number");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(14, "td", 49);
    i0.ɵɵtext(15);
    i0.ɵɵpipe(16, "number");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(17, "td", 49);
    i0.ɵɵtext(18);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(19, "td", 49);
    i0.ɵɵtext(20);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(21, "td", 50);
    i0.ɵɵtext(22);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(23, "td", 49);
    i0.ɵɵtext(24);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(25, "td", 49);
    i0.ɵɵtext(26);
    i0.ɵɵpipe(27, "number");
    i0.ɵɵelementEnd()();
} if (rf & 2) {
    const row_r6 = ctx.$implicit;
    const ctx_r2 = i0.ɵɵnextContext(2);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(row_r6.Model);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(row_r6.Vendor);
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(7, 11, row_r6.Runs));
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(10, 13, row_r6.InputTokens));
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(13, 15, row_r6.OutputTokens));
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(row_r6.CacheReadTokens + row_r6.CacheWriteTokens > 0 ? i0.ɵɵpipeBind2(16, 17, row_r6.CacheHitRate * 100, "1.0-0") + "%" : "\u2014");
    i0.ɵɵadvance(3);
    i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(row_r6.InputCost, 4));
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(row_r6.OutputCost, 4));
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(ctx_r2.FormatCurrency(row_r6.TotalCost));
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate(row_r6.CacheSavings > 0 ? ctx_r2.FormatCurrency(row_r6.CacheSavings, 4) : "\u2014");
    i0.ɵɵadvance(2);
    i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(27, 20, row_r6.PercentOfTotal, "1.1-1"), "%");
} }
function AnalyticsCostBudgetComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
    const _r1 = i0.ɵɵgetCurrentView();
    i0.ɵɵelementStart(0, "div", 2);
    i0.ɵɵrepeaterCreate(1, AnalyticsCostBudgetComponent_Conditional_1_For_2_Template, 9, 7, "div", 3, _forTrack0);
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(3, "div", 4)(4, "div", 5)(5, "div", 6)(6, "div", 7);
    i0.ɵɵelement(7, "i", 8);
    i0.ɵɵtext(8, " Daily Cost Trend ");
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(9, "div", 9);
    i0.ɵɵconditionalCreate(10, AnalyticsCostBudgetComponent_Conditional_1_Conditional_10_Template, 2, 0, "div", 10)(11, AnalyticsCostBudgetComponent_Conditional_1_Conditional_11_Template, 5, 1, "div", 11);
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(12, "div", 12)(13, "div", 6)(14, "div", 7);
    i0.ɵɵelement(15, "i", 13);
    i0.ɵɵtext(16, " Cost by Vendor ");
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(17, "div", 14);
    i0.ɵɵconditionalCreate(18, AnalyticsCostBudgetComponent_Conditional_1_Conditional_18_Template, 2, 0, "div", 10)(19, AnalyticsCostBudgetComponent_Conditional_1_Conditional_19_Template, 3, 0, "div", 15);
    i0.ɵɵelementEnd()()();
    i0.ɵɵelementStart(20, "div", 16)(21, "div", 6)(22, "div", 7);
    i0.ɵɵelement(23, "i", 17);
    i0.ɵɵtext(24, " Cost by Model ");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(25, "button", 18);
    i0.ɵɵlistener("click", function AnalyticsCostBudgetComponent_Conditional_1_Template_button_click_25_listener() { i0.ɵɵrestoreView(_r1); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.ExportCSV()); });
    i0.ɵɵelement(26, "i", 19);
    i0.ɵɵtext(27, " Export CSV ");
    i0.ɵɵelementEnd()();
    i0.ɵɵelementStart(28, "div", 20)(29, "table", 21)(30, "thead")(31, "tr")(32, "th");
    i0.ɵɵtext(33, "Model");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(34, "th");
    i0.ɵɵtext(35, "Vendor");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(36, "th", 22);
    i0.ɵɵtext(37, "Runs");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(38, "th", 22);
    i0.ɵɵtext(39, "Input Tokens");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(40, "th", 22);
    i0.ɵɵtext(41, "Output Tokens");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(42, "th", 23);
    i0.ɵɵtext(43, "Cache Hit");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(44, "th", 22);
    i0.ɵɵtext(45, "Input Cost");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(46, "th", 22);
    i0.ɵɵtext(47, "Output Cost");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(48, "th", 22);
    i0.ɵɵtext(49, "Total Cost");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(50, "th", 24);
    i0.ɵɵtext(51, "Cache Saved");
    i0.ɵɵelementEnd();
    i0.ɵɵelementStart(52, "th", 22);
    i0.ɵɵtext(53, "% of Total");
    i0.ɵɵelementEnd()()();
    i0.ɵɵelementStart(54, "tbody");
    i0.ɵɵconditionalCreate(55, AnalyticsCostBudgetComponent_Conditional_1_Conditional_55_Template, 3, 0, "tr");
    i0.ɵɵrepeaterCreate(56, AnalyticsCostBudgetComponent_Conditional_1_For_57_Template, 28, 23, "tr", null, _forTrack1);
    i0.ɵɵelementEnd()()()();
} if (rf & 2) {
    const ctx_r2 = i0.ɵɵnextContext();
    i0.ɵɵadvance();
    i0.ɵɵrepeater(ctx_r2.CostKpis);
    i0.ɵɵadvance(9);
    i0.ɵɵconditional(ctx_r2.DailyBars.length === 0 ? 10 : 11);
    i0.ɵɵadvance(8);
    i0.ɵɵconditional(ctx_r2.TreemapCells.length === 0 ? 18 : 19);
    i0.ɵɵadvance(37);
    i0.ɵɵconditional(ctx_r2.CostByModelRows.length === 0 ? 55 : -1);
    i0.ɵɵadvance();
    i0.ɵɵrepeater(ctx_r2.CostByModelRows);
} }
const FIELDS = [
    'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',
    'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'
];
// AIModelCost UnitType name -> token divisor, matching the BasePriceUnitType driver classes.
const UNIT_DIVISORS = {
    'Per Million Tokens': 1_000_000,
    'Per Hundred Thousand Tokens': 100_000,
    'Per Thousand Tokens': 1_000
};
const TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];
const TREEMAP_COLORS = [
    'var(--mj-brand-primary)',
    'var(--mj-brand-accent, var(--mj-brand-primary-hover))',
    'var(--mj-status-info)',
    'var(--mj-status-success)',
    'var(--mj-status-warning)',
    'var(--mj-text-disabled)'
];
export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
    _timeRange = '7d';
    set TimeRange(value) {
        const prev = this._timeRange;
        this._timeRange = value;
        if (prev !== value && this.initialized)
            this.LoadData();
    }
    get TimeRange() { return this._timeRange; }
    _filters = { Models: [], Agents: [], Prompts: [], Statuses: [] };
    set Filters(value) {
        const next = value ?? { Models: [], Agents: [], Prompts: [], Statuses: [] };
        const changed = !this.shallowFiltersEqual(this._filters, next);
        this._filters = next;
        if (changed && this.initialized)
            this.LoadData();
    }
    get Filters() { return this._filters; }
    shallowFiltersEqual(a, b) {
        const sameArr = (x, y) => x.length === y.length && x.every((v, i) => v === y[i]);
        return sameArr(a.Models, b.Models) && sameArr(a.Agents, b.Agents) && sameArr(a.Prompts, b.Prompts) && sameArr(a.Statuses, b.Statuses);
    }
    TimeRangeChange = new EventEmitter();
    FiltersChange = new EventEmitter();
    initialized = false;
    cdr = inject(ChangeDetectorRef);
    destroy$ = new Subject();
    IsLoading = false;
    TimeRangeOptionsList = TIME_RANGE_OPTIONS;
    CostKpis = [];
    DailyBars = [];
    AvgLinePercent = 0;
    TreemapCells = [];
    CostByModelRows = [];
    allRuns = [];
    previousPeriodRuns = [];
    // Per model+vendor cache pricing (rates already normalized to currency-per-token). Empty until
    // AIModelCost cache rates are configured — savings then stays 0 (surfaced as "Set rates").
    cacheRates = new Map();
    ngOnInit() {
        this.initialized = true;
        this.LoadData();
    }
    ngOnDestroy() {
        this.destroy$.next();
        this.destroy$.complete();
    }
    // ── Public Handlers ──
    OnTimeRangeChange(range) {
        this.TimeRange = range;
        this.TimeRangeChange.emit(range);
        this.LoadData();
    }
    OnFiltersChange(filters) {
        this.Filters = filters;
        this.FiltersChange.emit(filters);
        this.LoadData();
    }
    FormatCurrency(value, decimals = 2) {
        if (value === 0)
            return '$0.00';
        if (value < 0.01 && decimals < 4)
            decimals = 4;
        return '$' + value.toFixed(decimals);
    }
    ExportCSV() {
        const header = 'Model,Vendor,Runs,Input Tokens,Output Tokens,Cache Read Tokens,Cache Write Tokens,Cache Hit Rate %,Input Cost,Output Cost,Total Cost,Cache Saved,% of Total';
        const rows = this.CostByModelRows.map(r => `"${r.Model}","${r.Vendor}",${r.Runs},${r.InputTokens},${r.OutputTokens},${r.CacheReadTokens},${r.CacheWriteTokens},${(r.CacheHitRate * 100).toFixed(1)},${r.InputCost.toFixed(6)},${r.OutputCost.toFixed(6)},${r.TotalCost.toFixed(6)},${r.CacheSavings.toFixed(6)},${r.PercentOfTotal.toFixed(1)}`);
        const csv = [header, ...rows].join('\n');
        this.downloadCSV(csv, 'cost-by-model.csv');
    }
    // ── Data Loading ──
    async LoadData() {
        this.IsLoading = true;
        this.cdr.detectChanges();
        try {
            const rv = RunView.FromMetadataProvider(this.ProviderToUse);
            const { currentStart, previousStart } = this.getDateBounds();
            const now = new Date();
            const modelFilter = this.buildModelFilter();
            const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);
            const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);
            const [currentResult, prevResult, rateResult] = await rv.RunViews([
                {
                    EntityName: 'MJ: AI Prompt Runs',
                    ExtraFilter: currentFilter,
                    Fields: FIELDS,
                    OrderBy: 'RunAt ASC',
                    ResultType: 'simple'
                },
                {
                    EntityName: 'MJ: AI Prompt Runs',
                    ExtraFilter: prevFilter,
                    Fields: FIELDS,
                    OrderBy: 'RunAt ASC',
                    ResultType: 'simple'
                },
                {
                    EntityName: 'MJ: AI Model Costs',
                    ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,
                    Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitType'],
                    ResultType: 'simple'
                }
            ]);
            this.allRuns = (currentResult?.Results ?? []);
            this.previousPeriodRuns = (prevResult?.Results ?? []);
            this.buildCacheRateMap(rateResult?.Results ?? []);
            this.computeKpis();
            this.computeDailyBars();
            this.computeTreemap();
            this.computeCostByModel();
        }
        catch (e) {
            console.error('Cost & Budget load error:', e);
        }
        finally {
            this.IsLoading = false;
            this.cdr.detectChanges();
        }
    }
    // ── Cache pricing ──
    /** Stable map key for a model+vendor pair (UUIDs normalized for case-insensitive matching). */
    rateKey(modelID, vendorID) {
        return `${NormalizeUUID(modelID ?? '')}|${NormalizeUUID(vendorID ?? '')}`;
    }
    /** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */
    rateFor(run) {
        return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));
    }
    /** Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token. */
    buildCacheRateMap(rows) {
        this.cacheRates.clear();
        for (const row of rows) {
            const divisor = UNIT_DIVISORS[row.UnitType ?? ''] ?? 1_000_000;
            const inputRate = (row.InputPricePerUnit ?? 0) / divisor;
            // Cache read/write fall back to the input rate when no distinct rate is recorded — exactly
            // as the server-side cost calculator does — which makes the corresponding savings term 0.
            const cacheReadRate = (row.CacheReadPricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;
            const cacheWriteRate = (row.CacheWritePricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;
            this.cacheRates.set(this.rateKey(row.ModelID, row.VendorID), { inputRate, cacheReadRate, cacheWriteRate });
        }
    }
    /** Sum net cache savings across a set of runs using each run's model+vendor rate. */
    sumCacheSavings(runs) {
        return runs.reduce((total, run) => total + netCacheSavings({
            uncachedInputTokens: 0,
            cacheReadTokens: run.TokensCacheRead ?? 0,
            cacheWriteTokens: run.TokensCacheWrite ?? 0
        }, this.rateFor(run)), 0);
    }
    // ── Computations ──
    computeKpis() {
        const now = new Date();
        const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        const weekStart = new Date(todayStart.getTime() - 6 * 86400000);
        const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
        const todaySpend = this.sumCostInRange(this.allRuns, todayStart, now);
        const weekSpend = this.sumCostInRange(this.allRuns, weekStart, now);
        const monthSpend = this.sumCostInRange(this.allRuns, monthStart, now);
        const prevTotalCost = this.previousPeriodRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);
        const currentTotalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);
        // Project monthly cost based on current daily average
        const daysIntoMonth = Math.max(1, now.getDate());
        const projectedMonthly = (monthSpend / daysIntoMonth) * new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
        const delta = prevTotalCost > 0 ? ((currentTotalCost - prevTotalCost) / prevTotalCost) * 100 : null;
        this.CostKpis = [
            {
                Label: "Today's Spend",
                Value: this.FormatCurrency(todaySpend),
                Delta: null,
                DeltaDirection: 'stable',
                Highlighted: false,
                Icon: 'fa-solid fa-calendar-day'
            },
            {
                Label: 'This Week',
                Value: this.FormatCurrency(weekSpend),
                Delta: null,
                DeltaDirection: 'stable',
                Highlighted: false,
                Icon: 'fa-solid fa-calendar-week'
            },
            {
                Label: 'This Month',
                Value: this.FormatCurrency(monthSpend),
                Delta: delta,
                DeltaDirection: delta != null ? (delta > 0 ? 'up' : delta < 0 ? 'down' : 'stable') : 'stable',
                Highlighted: false,
                Icon: 'fa-solid fa-calendar'
            },
            {
                Label: 'Projected Monthly',
                Value: this.FormatCurrency(projectedMonthly),
                Delta: null,
                DeltaDirection: 'stable',
                Highlighted: true,
                Icon: 'fa-solid fa-chart-line'
            }
        ];
        this.appendCacheKpis();
    }
    /** Append the cache hit-rate and cache-savings KPIs (computed from the current-period runs). */
    appendCacheKpis() {
        const totals = { uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
        for (const r of this.allRuns) {
            totals.uncachedInputTokens += r.TokensPrompt ?? 0;
            totals.cacheReadTokens += r.TokensCacheRead ?? 0;
            totals.cacheWriteTokens += r.TokensCacheWrite ?? 0;
        }
        const savings = this.sumCacheSavings(this.allRuns);
        const activity = hasCacheActivity(totals);
        this.CostKpis.push({
            Label: 'Cache Hit Rate',
            Value: (cacheHitRate(totals) * 100).toFixed(1) + '%',
            Delta: null,
            DeltaDirection: 'stable',
            Highlighted: false,
            Icon: 'fa-solid fa-bolt'
        });
        // Savings requires cache rates on AIModelCost. When cache engaged but no savings computed,
        // it means rates aren't configured yet — say so rather than implying $0 was saved.
        const savingsValue = savings > 0
            ? this.FormatCurrency(savings)
            : (activity ? 'Set rates' : '$0.00');
        this.CostKpis.push({
            Label: 'Saved via Cache',
            Value: savingsValue,
            Delta: null,
            DeltaDirection: 'stable',
            Highlighted: savings > 0,
            Icon: 'fa-solid fa-piggy-bank'
        });
    }
    computeDailyBars() {
        const buckets = new Map();
        for (const run of this.allRuns) {
            const date = new Date(run.RunAt);
            const key = date.toISOString().slice(0, 10);
            buckets.set(key, (buckets.get(key) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));
        }
        const sortedKeys = Array.from(buckets.keys()).sort();
        const values = sortedKeys.map(k => buckets.get(k) ?? 0);
        const maxVal = Math.max(...values, 0.001);
        // Anomaly detection: > 2 standard deviations from mean
        const mean = values.length > 0 ? values.reduce((s, v) => s + v, 0) / values.length : 0;
        const variance = values.length > 1
            ? values.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / values.length
            : 0;
        const stdDev = Math.sqrt(variance);
        const anomalyThreshold = mean + 2 * stdDev;
        this.DailyBars = sortedKeys.map((key, i) => ({
            Date: key,
            Label: this.formatBarLabel(key),
            Cost: values[i],
            HeightPercent: maxVal > 0 ? (values[i] / maxVal) * 100 : 0,
            IsAnomaly: stdDev > 0 && values[i] > anomalyThreshold
        }));
        this.AvgLinePercent = maxVal > 0 ? (mean / maxVal) * 100 : 0;
    }
    computeTreemap() {
        const vendorCosts = new Map();
        for (const run of this.allRuns) {
            const vendor = run.Vendor ?? 'Other';
            vendorCosts.set(vendor, (vendorCosts.get(vendor) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));
        }
        const total = Array.from(vendorCosts.values()).reduce((s, v) => s + v, 0);
        const sorted = Array.from(vendorCosts.entries()).sort((a, b) => b[1] - a[1]);
        this.TreemapCells = sorted.map(([vendor, cost], i) => ({
            Label: vendor,
            Cost: cost,
            Percent: total > 0 ? (cost / total) * 100 : 0,
            Color: TREEMAP_COLORS[i % TREEMAP_COLORS.length],
            GridArea: ''
        }));
    }
    computeCostByModel() {
        const groups = new Map();
        for (const run of this.allRuns) {
            const key = run.ModelID ?? 'unknown';
            if (!groups.has(key))
                groups.set(key, []);
            groups.get(key).push(run);
        }
        const totalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);
        const rows = [];
        for (const [, modelRuns] of groups) {
            const inputTokens = modelRuns.reduce((s, r) => s + (r.TokensPrompt ?? 0), 0);
            const outputTokens = modelRuns.reduce((s, r) => s + (r.TokensCompletion ?? 0), 0);
            const cacheReadTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheRead ?? 0), 0);
            const cacheWriteTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheWrite ?? 0), 0);
            const cost = modelRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);
            // Approximate input/output cost split based on token ratio
            const totalTk = inputTokens + outputTokens;
            const inputCost = totalTk > 0 ? cost * (inputTokens / totalTk) : 0;
            const outputCost = totalTk > 0 ? cost * (outputTokens / totalTk) : 0;
            rows.push({
                Model: modelRuns[0].Model ?? 'Unknown',
                Vendor: modelRuns[0].Vendor ?? 'Unknown',
                Runs: modelRuns.length,
                InputTokens: inputTokens,
                OutputTokens: outputTokens,
                CacheReadTokens: cacheReadTokens,
                CacheWriteTokens: cacheWriteTokens,
                CacheHitRate: cacheHitRate({ uncachedInputTokens: inputTokens, cacheReadTokens, cacheWriteTokens }),
                CacheSavings: this.sumCacheSavings(modelRuns),
                InputCost: inputCost,
                OutputCost: outputCost,
                TotalCost: cost,
                PercentOfTotal: totalCost > 0 ? (cost / totalCost) * 100 : 0
            });
        }
        this.CostByModelRows = rows.sort((a, b) => b.TotalCost - a.TotalCost);
    }
    // ── Helpers ──
    sumCostInRange(runs, start, end) {
        return runs
            .filter(r => {
            const d = new Date(r.RunAt);
            return d >= start && d <= end;
        })
            .reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);
    }
    getDateBounds() {
        const now = new Date();
        const msMap = {
            'Today': 86400000,
            '7d': 604800000,
            '30d': 2592000000,
            'MTD': now.getDate() * 86400000
        };
        const ms = msMap[this.TimeRange] ?? 604800000;
        const currentStart = new Date(now.getTime() - ms);
        const previousStart = new Date(currentStart.getTime() - ms);
        return { currentStart, previousStart };
    }
    buildModelFilter() {
        if (this.Filters.Models.length === 0)
            return '';
        const ids = this.Filters.Models.map(id => `'${id}'`).join(',');
        return `ModelID IN (${ids})`;
    }
    combineDateAndModelFilter(start, end, modelFilter) {
        const parts = [
            `RunAt >= '${start.toISOString()}'`,
            `RunAt <= '${end.toISOString()}'`
        ];
        if (modelFilter)
            parts.push(modelFilter);
        return parts.join(' AND ');
    }
    formatBarLabel(dateStr) {
        const d = new Date(dateStr + 'T00:00:00');
        const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
        return months[d.getMonth()] + ' ' + d.getDate();
    }
    downloadCSV(csv, filename) {
        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.click();
        URL.revokeObjectURL(url);
    }
    static ɵfac = /*@__PURE__*/ (() => { let ɵAnalyticsCostBudgetComponent_BaseFactory; return function AnalyticsCostBudgetComponent_Factory(__ngFactoryType__) { return (ɵAnalyticsCostBudgetComponent_BaseFactory || (ɵAnalyticsCostBudgetComponent_BaseFactory = i0.ɵɵgetInheritedFactory(AnalyticsCostBudgetComponent)))(__ngFactoryType__ || AnalyticsCostBudgetComponent); }; })();
    static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsCostBudgetComponent, selectors: [["app-analytics-cost-budget"]], inputs: { TimeRange: "TimeRange", Filters: "Filters" }, outputs: { TimeRangeChange: "TimeRangeChange", FiltersChange: "FiltersChange" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 2, vars: 1, consts: [[1, "loading-container"], ["text", "Loading cost data..."], [1, "kpi-row"], [1, "kpi-card", 3, "kpi-card--highlighted"], [1, "two-col"], [1, "panel", "panel-chart"], [1, "panel-header"], [1, "panel-header__title"], [1, "fa-solid", "fa-chart-column", "panel-header__icon"], [1, "chart-body"], [1, "panel-empty"], [1, "bar-chart"], [1, "panel", "panel-treemap"], [1, "fa-solid", "fa-chart-pie", "panel-header__icon"], [1, "treemap-body"], [1, "treemap-grid"], [1, "panel"], [1, "fa-solid", "fa-table", "panel-header__icon"], [1, "export-btn", 3, "click"], [1, "fa-solid", "fa-download"], [1, "table-wrapper"], [1, "data-table"], [1, "col-numeric"], ["title", "Share of input tokens served from the provider's prompt cache", 1, "col-numeric"], ["title", "Net dollars saved by caching vs. full input pricing (requires cache rates on AI Model Costs)", 1, "col-numeric"], [1, "kpi-card"], [1, "kpi-icon"], [1, "kpi-content"], [1, "kpi-label"], [1, "kpi-value"], [1, "kpi-delta", 3, "kpi-delta--up", "kpi-delta--down"], [1, "kpi-delta"], [1, "fa-solid", "fa-arrow-up"], [1, "fa-solid", "fa-arrow-down"], [1, "bar-chart-area"], [1, "avg-line", 3, "bottom"], [1, "bar-col", 3, "title"], [1, "avg-line"], [1, "avg-label"], [1, "bar"], [1, "bar-label"], [1, "treemap-cell", 3, "background", "flex-basis", "title"], [1, "treemap-cell", 3, "title"], [1, "treemap-label"], [1, "treemap-value"], [1, "treemap-pct"], ["colspan", "11", 1, "empty-row"], [1, "cell-model"], [1, "cell-vendor"], [1, "cell-numeric"], [1, "cell-numeric", "cell-cost"]], template: function AnalyticsCostBudgetComponent_Template(rf, ctx) { if (rf & 1) {
            i0.ɵɵconditionalCreate(0, AnalyticsCostBudgetComponent_Conditional_0_Template, 2, 0, "div", 0)(1, AnalyticsCostBudgetComponent_Conditional_1_Template, 58, 3);
        } if (rf & 2) {
            i0.ɵɵconditional(ctx.IsLoading ? 0 : 1);
        } }, dependencies: [i1.LoadingComponent, i2.DecimalPipe], styles: ["[_nghost-%COMP%] { display: block; }\n\n        .loading-container[_ngcontent-%COMP%] {\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            min-height: 300px;\n        }\n\n        \n\n        .kpi-row[_ngcontent-%COMP%] {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n            gap: 12px;\n            margin: 16px 0;\n        }\n\n        .kpi-card[_ngcontent-%COMP%] {\n            background: var(--mj-bg-surface);\n            border: 1px solid var(--mj-border-default);\n            border-radius: 10px;\n            padding: 16px;\n            display: flex;\n            align-items: flex-start;\n            gap: 12px;\n            transition: transform 0.2s ease, box-shadow 0.2s ease;\n        }\n\n        .kpi-card[_ngcontent-%COMP%]:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n        }\n\n        .kpi-card--highlighted[_ngcontent-%COMP%] {\n            border-color: var(--mj-brand-primary);\n            background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n        }\n\n        .kpi-icon[_ngcontent-%COMP%] {\n            width: 36px;\n            height: 36px;\n            border-radius: 10px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n            color: var(--mj-brand-primary);\n            font-size: 14px;\n            flex-shrink: 0;\n        }\n\n        .kpi-card--highlighted[_ngcontent-%COMP%]   .kpi-icon[_ngcontent-%COMP%] {\n            background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n        }\n\n        .kpi-label[_ngcontent-%COMP%] {\n            font-size: 11px;\n            font-weight: 600;\n            color: var(--mj-text-muted);\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n\n        .kpi-value[_ngcontent-%COMP%] {\n            font-size: 22px;\n            font-weight: 700;\n            color: var(--mj-text-primary);\n            margin: 2px 0;\n            letter-spacing: -0.02em;\n        }\n\n        .kpi-delta[_ngcontent-%COMP%] {\n            font-size: 11px;\n            color: var(--mj-text-muted);\n            display: flex;\n            align-items: center;\n            gap: 3px;\n        }\n\n        .kpi-delta--up[_ngcontent-%COMP%] {\n            color: var(--mj-status-error);\n        }\n\n        .kpi-delta--down[_ngcontent-%COMP%] {\n            color: var(--mj-status-success);\n        }\n\n        \n\n        .two-col[_ngcontent-%COMP%] {\n            display: grid;\n            grid-template-columns: 3fr 2fr;\n            gap: 16px;\n            margin-bottom: 16px;\n        }\n\n        \n\n        .panel[_ngcontent-%COMP%] {\n            background: var(--mj-bg-surface);\n            border: 1px solid var(--mj-border-default);\n            border-radius: 12px;\n            overflow: hidden;\n            margin-bottom: 16px;\n        }\n\n        .panel-header[_ngcontent-%COMP%] {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            padding: 14px 18px;\n            border-bottom: 1px solid var(--mj-border-subtle);\n        }\n\n        .panel-header__title[_ngcontent-%COMP%] {\n            font-size: 14px;\n            font-weight: 600;\n            color: var(--mj-text-primary);\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .panel-header__icon[_ngcontent-%COMP%] {\n            font-size: 13px;\n            color: var(--mj-brand-primary);\n        }\n\n        .panel-empty[_ngcontent-%COMP%] {\n            text-align: center;\n            padding: 32px;\n            color: var(--mj-text-disabled);\n            font-size: 13px;\n        }\n\n        .export-btn[_ngcontent-%COMP%] {\n            display: flex;\n            align-items: center;\n            gap: 6px;\n            padding: 5px 12px;\n            border: 1px solid var(--mj-border-default);\n            border-radius: 6px;\n            background: var(--mj-bg-surface);\n            color: var(--mj-text-secondary);\n            font-size: 13px;\n            cursor: pointer;\n            transition: background 0.15s, color 0.15s;\n        }\n\n        .export-btn[_ngcontent-%COMP%]:hover {\n            background: var(--mj-bg-surface-hover);\n            color: var(--mj-text-primary);\n        }\n\n        \n\n        .chart-body[_ngcontent-%COMP%] {\n            padding: 16px 18px;\n        }\n\n        .bar-chart[_ngcontent-%COMP%] {\n            width: 100%;\n        }\n\n        .bar-chart-area[_ngcontent-%COMP%] {\n            display: flex;\n            align-items: flex-end;\n            gap: 4px;\n            height: 180px;\n            position: relative;\n            padding-bottom: 24px;\n        }\n\n        .bar-col[_ngcontent-%COMP%] {\n            flex: 1;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            height: 100%;\n            justify-content: flex-end;\n        }\n\n        .bar[_ngcontent-%COMP%] {\n            width: 100%;\n            max-width: 32px;\n            border-radius: 4px 4px 0 0;\n            background: var(--mj-brand-primary);\n            transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n            min-height: 2px;\n        }\n\n        .bar--anomaly[_ngcontent-%COMP%] {\n            background: var(--mj-status-error);\n        }\n\n        .bar-label[_ngcontent-%COMP%] {\n            font-size: 10px;\n            color: var(--mj-text-muted);\n            margin-top: 4px;\n            white-space: nowrap;\n        }\n\n        .avg-line[_ngcontent-%COMP%] {\n            position: absolute;\n            left: 0;\n            right: 0;\n            border-top: 2px dashed var(--mj-text-disabled);\n            z-index: 1;\n            pointer-events: none;\n        }\n\n        .avg-label[_ngcontent-%COMP%] {\n            position: absolute;\n            right: 0;\n            top: -14px;\n            font-size: 10px;\n            color: var(--mj-text-disabled);\n            font-weight: 600;\n        }\n\n        \n\n        .treemap-body[_ngcontent-%COMP%] {\n            padding: 16px 18px;\n        }\n\n        .treemap-grid[_ngcontent-%COMP%] {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 4px;\n            min-height: 160px;\n        }\n\n        .treemap-cell[_ngcontent-%COMP%] {\n            border-radius: 8px;\n            padding: 12px;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            min-width: 80px;\n            min-height: 70px;\n            flex-grow: 1;\n            color: var(--mj-text-inverse, white);\n            transition: opacity 0.2s;\n        }\n\n        .treemap-cell[_ngcontent-%COMP%]:hover {\n            opacity: 0.85;\n        }\n\n        .treemap-label[_ngcontent-%COMP%] {\n            font-size: 12px;\n            font-weight: 600;\n            opacity: 0.95;\n        }\n\n        .treemap-value[_ngcontent-%COMP%] {\n            font-size: 16px;\n            font-weight: 700;\n            margin-top: 2px;\n        }\n\n        .treemap-pct[_ngcontent-%COMP%] {\n            font-size: 11px;\n            opacity: 0.8;\n        }\n\n        \n\n        .table-wrapper[_ngcontent-%COMP%] {\n            overflow-x: auto;\n        }\n\n        .data-table[_ngcontent-%COMP%] {\n            width: 100%;\n            border-collapse: collapse;\n            font-size: 13px;\n        }\n\n        .data-table[_ngcontent-%COMP%]   th[_ngcontent-%COMP%], \n   .data-table[_ngcontent-%COMP%]   td[_ngcontent-%COMP%] {\n            padding: 10px 14px;\n            text-align: left;\n            border-bottom: 1px solid var(--mj-border-subtle);\n        }\n\n        .data-table[_ngcontent-%COMP%]   th[_ngcontent-%COMP%] {\n            font-size: 11px;\n            font-weight: 600;\n            color: var(--mj-text-muted);\n            text-transform: uppercase;\n            letter-spacing: 0.4px;\n            background: var(--mj-bg-surface-card);\n            position: sticky;\n            top: 0;\n            white-space: nowrap;\n        }\n\n        .col-numeric[_ngcontent-%COMP%] { text-align: right; }\n\n        .data-table[_ngcontent-%COMP%]   tbody[_ngcontent-%COMP%]   tr[_ngcontent-%COMP%] {\n            transition: background 0.15s;\n        }\n\n        .data-table[_ngcontent-%COMP%]   tbody[_ngcontent-%COMP%]   tr[_ngcontent-%COMP%]:hover {\n            background: var(--mj-bg-surface-hover);\n        }\n\n        .cell-model[_ngcontent-%COMP%] {\n            font-weight: 500;\n            color: var(--mj-text-primary);\n        }\n\n        .cell-vendor[_ngcontent-%COMP%] {\n            color: var(--mj-text-secondary);\n        }\n\n        .cell-numeric[_ngcontent-%COMP%] {\n            text-align: right;\n            font-variant-numeric: tabular-nums;\n            color: var(--mj-text-secondary);\n        }\n\n        .cell-cost[_ngcontent-%COMP%] {\n            font-weight: 600;\n            color: var(--mj-text-primary);\n        }\n\n        .empty-row[_ngcontent-%COMP%] {\n            text-align: center;\n            color: var(--mj-text-disabled);\n            padding: 24px;\n        }\n\n        \n\n        @media (max-width: 1200px) {\n            .two-col[_ngcontent-%COMP%] {\n                grid-template-columns: 1fr;\n            }\n        }\n\n        @media (max-width: 768px) {\n            .kpi-row[_ngcontent-%COMP%] {\n                grid-template-columns: repeat(2, 1fr);\n            }\n\n            .kpi-value[_ngcontent-%COMP%] {\n                font-size: 18px;\n            }\n        }"] });
}
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsCostBudgetComponent, [{
        type: Component,
        args: [{ standalone: false, selector: 'app-analytics-cost-budget', template: `

        @if (IsLoading) {
            <div class="loading-container">
                <mj-loading text="Loading cost data..."></mj-loading>
            </div>
        } @else {
            <!-- Cost KPI Row -->
            <div class="kpi-row">
                @for (kpi of CostKpis; track kpi.Label) {
                    <div class="kpi-card" [class.kpi-card--highlighted]="kpi.Highlighted">
                        <div class="kpi-icon">
                            <i [class]="kpi.Icon"></i>
                        </div>
                        <div class="kpi-content">
                            <div class="kpi-label">{{ kpi.Label }}</div>
                            <div class="kpi-value">{{ kpi.Value }}</div>
                            @if (kpi.Delta != null) {
                                <div class="kpi-delta"
                                     [class.kpi-delta--up]="kpi.DeltaDirection === 'up'"
                                     [class.kpi-delta--down]="kpi.DeltaDirection === 'down'">
                                    @if (kpi.DeltaDirection === 'up') {
                                        <i class="fa-solid fa-arrow-up"></i>
                                    } @else if (kpi.DeltaDirection === 'down') {
                                        <i class="fa-solid fa-arrow-down"></i>
                                    }
                                    {{ kpi.Delta | number:'1.1-1' }}% vs prev
                                </div>
                            }
                        </div>
                    </div>
                }
            </div>

            <!-- Two-Column Layout -->
            <div class="two-col">
                <!-- Daily Cost Trend -->
                <div class="panel panel-chart">
                    <div class="panel-header">
                        <div class="panel-header__title">
                            <i class="fa-solid fa-chart-column panel-header__icon"></i>
                            Daily Cost Trend
                        </div>
                    </div>
                    <div class="chart-body">
                        @if (DailyBars.length === 0) {
                            <div class="panel-empty">No cost data for selected period</div>
                        } @else {
                            <div class="bar-chart">
                                <div class="bar-chart-area">
                                    @if (AvgLinePercent > 0) {
                                        <div class="avg-line" [style.bottom.%]="AvgLinePercent">
                                            <span class="avg-label">avg</span>
                                        </div>
                                    }
                                    @for (bar of DailyBars; track bar.Date) {
                                        <div class="bar-col" [title]="bar.Label + ': ' + FormatCurrency(bar.Cost)">
                                            <div
                                                class="bar"
                                                [class.bar--anomaly]="bar.IsAnomaly"
                                                [style.height.%]="bar.HeightPercent"
                                            ></div>
                                            <div class="bar-label">{{ bar.Label }}</div>
                                        </div>
                                    }
                                </div>
                            </div>
                        }
                    </div>
                </div>

                <!-- Cost Breakdown Treemap -->
                <div class="panel panel-treemap">
                    <div class="panel-header">
                        <div class="panel-header__title">
                            <i class="fa-solid fa-chart-pie panel-header__icon"></i>
                            Cost by Vendor
                        </div>
                    </div>
                    <div class="treemap-body">
                        @if (TreemapCells.length === 0) {
                            <div class="panel-empty">No vendor cost data</div>
                        } @else {
                            <div class="treemap-grid">
                                @for (cell of TreemapCells; track cell.Label) {
                                    <div
                                        class="treemap-cell"
                                        [style.background]="cell.Color"
                                        [style.flex-basis.%]="cell.Percent"
                                        [title]="cell.Label + ': ' + FormatCurrency(cell.Cost) + ' (' + (cell.Percent | number:'1.0-0') + '%)'">
                                        <span class="treemap-label">{{ cell.Label }}</span>
                                        <span class="treemap-value">{{ FormatCurrency(cell.Cost) }}</span>
                                        <span class="treemap-pct">{{ cell.Percent | number:'1.0-0' }}%</span>
                                    </div>
                                }
                            </div>
                        }
                    </div>
                </div>
            </div>

            <!-- Cost by Model Table -->
            <div class="panel">
                <div class="panel-header">
                    <div class="panel-header__title">
                        <i class="fa-solid fa-table panel-header__icon"></i>
                        Cost by Model
                    </div>
                    <button class="export-btn" (click)="ExportCSV()">
                        <i class="fa-solid fa-download"></i>
                        Export CSV
                    </button>
                </div>
                <div class="table-wrapper">
                    <table class="data-table">
                        <thead>
                            <tr>
                                <th>Model</th>
                                <th>Vendor</th>
                                <th class="col-numeric">Runs</th>
                                <th class="col-numeric">Input Tokens</th>
                                <th class="col-numeric">Output Tokens</th>
                                <th class="col-numeric" title="Share of input tokens served from the provider's prompt cache">Cache Hit</th>
                                <th class="col-numeric">Input Cost</th>
                                <th class="col-numeric">Output Cost</th>
                                <th class="col-numeric">Total Cost</th>
                                <th class="col-numeric" title="Net dollars saved by caching vs. full input pricing (requires cache rates on AI Model Costs)">Cache Saved</th>
                                <th class="col-numeric">% of Total</th>
                            </tr>
                        </thead>
                        <tbody>
                            @if (CostByModelRows.length === 0) {
                                <tr><td colspan="11" class="empty-row">No data available</td></tr>
                            }
                            @for (row of CostByModelRows; track row.Model) {
                                <tr>
                                    <td class="cell-model">{{ row.Model }}</td>
                                    <td class="cell-vendor">{{ row.Vendor }}</td>
                                    <td class="cell-numeric">{{ row.Runs | number }}</td>
                                    <td class="cell-numeric">{{ row.InputTokens | number }}</td>
                                    <td class="cell-numeric">{{ row.OutputTokens | number }}</td>
                                    <td class="cell-numeric">{{ (row.CacheReadTokens + row.CacheWriteTokens) > 0 ? ((row.CacheHitRate * 100 | number:'1.0-0') + '%') : '—' }}</td>
                                    <td class="cell-numeric">{{ FormatCurrency(row.InputCost, 4) }}</td>
                                    <td class="cell-numeric">{{ FormatCurrency(row.OutputCost, 4) }}</td>
                                    <td class="cell-numeric cell-cost">{{ FormatCurrency(row.TotalCost) }}</td>
                                    <td class="cell-numeric">{{ row.CacheSavings > 0 ? FormatCurrency(row.CacheSavings, 4) : '—' }}</td>
                                    <td class="cell-numeric">{{ row.PercentOfTotal | number:'1.1-1' }}%</td>
                                </tr>
                            }
                        </tbody>
                    </table>
                </div>
            </div>
        }
    `, styles: ["\n        :host { display: block; }\n\n        .loading-container {\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            min-height: 300px;\n        }\n\n        /* \u2500\u2500 KPI Row \u2500\u2500 */\n        .kpi-row {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n            gap: 12px;\n            margin: 16px 0;\n        }\n\n        .kpi-card {\n            background: var(--mj-bg-surface);\n            border: 1px solid var(--mj-border-default);\n            border-radius: 10px;\n            padding: 16px;\n            display: flex;\n            align-items: flex-start;\n            gap: 12px;\n            transition: transform 0.2s ease, box-shadow 0.2s ease;\n        }\n\n        .kpi-card:hover {\n            transform: translateY(-2px);\n            box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n        }\n\n        .kpi-card--highlighted {\n            border-color: var(--mj-brand-primary);\n            background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n        }\n\n        .kpi-icon {\n            width: 36px;\n            height: 36px;\n            border-radius: 10px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n            color: var(--mj-brand-primary);\n            font-size: 14px;\n            flex-shrink: 0;\n        }\n\n        .kpi-card--highlighted .kpi-icon {\n            background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n        }\n\n        .kpi-label {\n            font-size: 11px;\n            font-weight: 600;\n            color: var(--mj-text-muted);\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n\n        .kpi-value {\n            font-size: 22px;\n            font-weight: 700;\n            color: var(--mj-text-primary);\n            margin: 2px 0;\n            letter-spacing: -0.02em;\n        }\n\n        .kpi-delta {\n            font-size: 11px;\n            color: var(--mj-text-muted);\n            display: flex;\n            align-items: center;\n            gap: 3px;\n        }\n\n        .kpi-delta--up {\n            color: var(--mj-status-error);\n        }\n\n        .kpi-delta--down {\n            color: var(--mj-status-success);\n        }\n\n        /* \u2500\u2500 Two-Column Layout \u2500\u2500 */\n        .two-col {\n            display: grid;\n            grid-template-columns: 3fr 2fr;\n            gap: 16px;\n            margin-bottom: 16px;\n        }\n\n        /* \u2500\u2500 Panel \u2500\u2500 */\n        .panel {\n            background: var(--mj-bg-surface);\n            border: 1px solid var(--mj-border-default);\n            border-radius: 12px;\n            overflow: hidden;\n            margin-bottom: 16px;\n        }\n\n        .panel-header {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            padding: 14px 18px;\n            border-bottom: 1px solid var(--mj-border-subtle);\n        }\n\n        .panel-header__title {\n            font-size: 14px;\n            font-weight: 600;\n            color: var(--mj-text-primary);\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .panel-header__icon {\n            font-size: 13px;\n            color: var(--mj-brand-primary);\n        }\n\n        .panel-empty {\n            text-align: center;\n            padding: 32px;\n            color: var(--mj-text-disabled);\n            font-size: 13px;\n        }\n\n        .export-btn {\n            display: flex;\n            align-items: center;\n            gap: 6px;\n            padding: 5px 12px;\n            border: 1px solid var(--mj-border-default);\n            border-radius: 6px;\n            background: var(--mj-bg-surface);\n            color: var(--mj-text-secondary);\n            font-size: 13px;\n            cursor: pointer;\n            transition: background 0.15s, color 0.15s;\n        }\n\n        .export-btn:hover {\n            background: var(--mj-bg-surface-hover);\n            color: var(--mj-text-primary);\n        }\n\n        /* \u2500\u2500 Bar Chart \u2500\u2500 */\n        .chart-body {\n            padding: 16px 18px;\n        }\n\n        .bar-chart {\n            width: 100%;\n        }\n\n        .bar-chart-area {\n            display: flex;\n            align-items: flex-end;\n            gap: 4px;\n            height: 180px;\n            position: relative;\n            padding-bottom: 24px;\n        }\n\n        .bar-col {\n            flex: 1;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            height: 100%;\n            justify-content: flex-end;\n        }\n\n        .bar {\n            width: 100%;\n            max-width: 32px;\n            border-radius: 4px 4px 0 0;\n            background: var(--mj-brand-primary);\n            transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n            min-height: 2px;\n        }\n\n        .bar--anomaly {\n            background: var(--mj-status-error);\n        }\n\n        .bar-label {\n            font-size: 10px;\n            color: var(--mj-text-muted);\n            margin-top: 4px;\n            white-space: nowrap;\n        }\n\n        .avg-line {\n            position: absolute;\n            left: 0;\n            right: 0;\n            border-top: 2px dashed var(--mj-text-disabled);\n            z-index: 1;\n            pointer-events: none;\n        }\n\n        .avg-label {\n            position: absolute;\n            right: 0;\n            top: -14px;\n            font-size: 10px;\n            color: var(--mj-text-disabled);\n            font-weight: 600;\n        }\n\n        /* \u2500\u2500 Treemap \u2500\u2500 */\n        .treemap-body {\n            padding: 16px 18px;\n        }\n\n        .treemap-grid {\n            display: flex;\n            flex-wrap: wrap;\n            gap: 4px;\n            min-height: 160px;\n        }\n\n        .treemap-cell {\n            border-radius: 8px;\n            padding: 12px;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            min-width: 80px;\n            min-height: 70px;\n            flex-grow: 1;\n            color: var(--mj-text-inverse, white);\n            transition: opacity 0.2s;\n        }\n\n        .treemap-cell:hover {\n            opacity: 0.85;\n        }\n\n        .treemap-label {\n            font-size: 12px;\n            font-weight: 600;\n            opacity: 0.95;\n        }\n\n        .treemap-value {\n            font-size: 16px;\n            font-weight: 700;\n            margin-top: 2px;\n        }\n\n        .treemap-pct {\n            font-size: 11px;\n            opacity: 0.8;\n        }\n\n        /* \u2500\u2500 Table \u2500\u2500 */\n        .table-wrapper {\n            overflow-x: auto;\n        }\n\n        .data-table {\n            width: 100%;\n            border-collapse: collapse;\n            font-size: 13px;\n        }\n\n        .data-table th,\n        .data-table td {\n            padding: 10px 14px;\n            text-align: left;\n            border-bottom: 1px solid var(--mj-border-subtle);\n        }\n\n        .data-table th {\n            font-size: 11px;\n            font-weight: 600;\n            color: var(--mj-text-muted);\n            text-transform: uppercase;\n            letter-spacing: 0.4px;\n            background: var(--mj-bg-surface-card);\n            position: sticky;\n            top: 0;\n            white-space: nowrap;\n        }\n\n        .col-numeric { text-align: right; }\n\n        .data-table tbody tr {\n            transition: background 0.15s;\n        }\n\n        .data-table tbody tr:hover {\n            background: var(--mj-bg-surface-hover);\n        }\n\n        .cell-model {\n            font-weight: 500;\n            color: var(--mj-text-primary);\n        }\n\n        .cell-vendor {\n            color: var(--mj-text-secondary);\n        }\n\n        .cell-numeric {\n            text-align: right;\n            font-variant-numeric: tabular-nums;\n            color: var(--mj-text-secondary);\n        }\n\n        .cell-cost {\n            font-weight: 600;\n            color: var(--mj-text-primary);\n        }\n\n        .empty-row {\n            text-align: center;\n            color: var(--mj-text-disabled);\n            padding: 24px;\n        }\n\n        /* \u2500\u2500 Responsive \u2500\u2500 */\n        @media (max-width: 1200px) {\n            .two-col {\n                grid-template-columns: 1fr;\n            }\n        }\n\n        @media (max-width: 768px) {\n            .kpi-row {\n                grid-template-columns: repeat(2, 1fr);\n            }\n\n            .kpi-value {\n                font-size: 18px;\n            }\n        }\n    "] }]
    }], null, { TimeRange: [{
            type: Input
        }], Filters: [{
            type: Input
        }], TimeRangeChange: [{
            type: Output
        }], FiltersChange: [{
            type: Output
        }] }); })();
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsCostBudgetComponent, { className: "AnalyticsCostBudgetComponent", filePath: "src/AI/components/analytics/cost-budget/cost-budget.component.ts", lineNumber: 618 }); })();
export function LoadAnalyticsCostBudget() { }
//# sourceMappingURL=cost-budget.component.js.map