#!/usr/bin/env node
import {
  computeCost,
  formatCost
} from "./chunk-BLRPRWZX.js";

// src/core/cost-tracker.ts
import { existsSync, readFileSync, writeFileSync, renameSync } from "fs";
import { join } from "path";
var CostTracker = class {
  filePath;
  records = [];
  dirty = false;
  constructor(configDir) {
    this.filePath = join(configDir, "cost-history.json");
    this.load();
  }
  load() {
    try {
      if (existsSync(this.filePath)) {
        const data = JSON.parse(readFileSync(this.filePath, "utf-8"));
        if (data.version === 1 && Array.isArray(data.records)) {
          this.records = [...data.records].sort((a, b) => a.date.localeCompare(b.date));
        }
      }
    } catch {
      this.records = [];
    }
  }
  /**
   * Save to disk (atomic write).
   *
   * H2: Snapshot records before writing and only clear the dirty flag after
   * the write succeeds. If writeFileSync/renameSync throws, dirty remains
   * true so the next save() retries — prevents silent data loss on transient
   * disk errors. writeFileSync is sync in Node.js so there's no interleaving
   * risk within a single save() call; the snapshot mainly protects against
   * future refactors to async I/O.
   */
  save() {
    if (!this.dirty) return;
    const snapshot = {
      version: 1,
      records: [...this.records]
      // shallow copy — records are plain data
    };
    try {
      const tmp = this.filePath + ".tmp";
      writeFileSync(tmp, JSON.stringify(snapshot, null, 2), "utf-8");
      renameSync(tmp, this.filePath);
      this.dirty = false;
    } catch (err) {
      console.error("[cost-tracker] Failed to persist cost history:", err);
    }
  }
  /**
   * Record cost from a completed session/interaction.
   */
  addCost(provider, model, usage) {
    const cost = computeCost(provider, model, usage) ?? 0;
    const inTok = usage.inputTokens || 0;
    const outTok = usage.outputTokens || 0;
    if (cost === 0 && inTok === 0 && outTok === 0) return;
    const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
    let record = this.records.find((r) => r.date === today);
    if (!record) {
      record = { date: today, cost: 0, sessions: 0, inputTokens: 0, outputTokens: 0 };
      this.records.push(record);
    }
    record.cost += cost;
    record.sessions += 1;
    record.inputTokens += inTok;
    record.outputTokens += outTok;
    const key = `${provider}/${model}`;
    record.byModel ??= {};
    const m = record.byModel[key] ??= { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
    m.cost += cost;
    m.inputTokens += inTok;
    m.outputTokens += outTok;
    m.cacheReadTokens += usage.cacheReadTokens ?? 0;
    m.calls += 1;
    this.dirty = true;
    if (this.records.length > 90) {
      this.records = this.records.slice(-90);
    }
  }
  /** Get total cost for a given month ("2026-04"). */
  getMonthlyCost(yearMonth) {
    const prefix = yearMonth ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
    return this.records.filter((r) => r.date.startsWith(prefix)).reduce((sum, r) => sum + r.cost, 0);
  }
  /** Get today's cost. */
  getTodayCost() {
    const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
    return this.records.find((r) => r.date === today)?.cost ?? 0;
  }
  /** Get total cost for last N days. */
  getRecentCost(days) {
    const cutoff = /* @__PURE__ */ new Date();
    cutoff.setDate(cutoff.getDate() - days);
    const cutoffStr = cutoff.toISOString().slice(0, 10);
    return this.records.filter((r) => r.date >= cutoffStr).reduce((sum, r) => sum + r.cost, 0);
  }
  /** Get all records for display. */
  getRecords() {
    return [...this.records];
  }
  /**
   * Aggregate usage across a date window, grouped by provider/model (v0.4.184+).
   *
   * Window selection (precedence): `month` ("2026-06") > `sinceDays` (N days back,
   * inclusive of today) > all tracked history. Legacy records without a byModel
   * breakdown fold into a single "(unattributed)" row so totals still reconcile
   * with `getMonthlyCost()` etc.
   *
   * @returns rows sorted by cost desc then total tokens desc, plus grand totals
   *   and the actual covered date range.
   */
  getUsageBreakdown(opts) {
    let filtered;
    if (opts?.month) {
      filtered = this.records.filter((r) => r.date.startsWith(opts.month));
    } else if (opts?.sinceDays && opts.sinceDays > 0) {
      const cutoff = /* @__PURE__ */ new Date();
      cutoff.setDate(cutoff.getDate() - (opts.sinceDays - 1));
      const cutoffStr = cutoff.toISOString().slice(0, 10);
      filtered = this.records.filter((r) => r.date >= cutoffStr);
    } else {
      filtered = [...this.records];
    }
    const acc = /* @__PURE__ */ new Map();
    const bump = (key, provider, model, v) => {
      const row = acc.get(key) ?? { key, provider, model, cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
      row.cost += v.cost;
      row.inputTokens += v.inputTokens;
      row.outputTokens += v.outputTokens;
      row.cacheReadTokens += v.cacheReadTokens;
      row.calls += v.calls;
      acc.set(key, row);
    };
    for (const rec of filtered) {
      if (rec.byModel && Object.keys(rec.byModel).length > 0) {
        for (const [key, m] of Object.entries(rec.byModel)) {
          const slash = key.indexOf("/");
          const provider = slash >= 0 ? key.slice(0, slash) : key;
          const model = slash >= 0 ? key.slice(slash + 1) : "";
          bump(key, provider, model, m);
        }
      } else {
        bump("(unattributed)", "(unattributed)", "", {
          cost: rec.cost,
          inputTokens: rec.inputTokens,
          outputTokens: rec.outputTokens,
          cacheReadTokens: 0,
          calls: rec.sessions
        });
      }
    }
    const rows = [...acc.values()].sort(
      (a, b) => b.cost - a.cost || b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens)
    );
    const totals = rows.reduce(
      (t, r) => {
        t.cost += r.cost;
        t.inputTokens += r.inputTokens;
        t.outputTokens += r.outputTokens;
        t.cacheReadTokens += r.cacheReadTokens;
        t.calls += r.calls;
        return t;
      },
      { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 }
    );
    return {
      rows,
      totals,
      fromDate: filtered.length > 0 ? filtered[0].date : null,
      toDate: filtered.length > 0 ? filtered[filtered.length - 1].date : null,
      dayCount: filtered.length
    };
  }
  /**
   * Check if monthly cost exceeds budget, return warning message or null.
   */
  checkBudget(monthlyBudget) {
    if (!monthlyBudget || monthlyBudget <= 0) return null;
    const monthlyCost = this.getMonthlyCost();
    const ratio = monthlyCost / monthlyBudget;
    if (ratio >= 1) {
      return `\u{1F6A8} Monthly budget exceeded: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
    }
    if (ratio >= 0.8) {
      return `\u26A0 Monthly budget warning: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
    }
    return null;
  }
  /**
   * Format a cost summary for display.
   */
  formatSummary(monthlyBudget) {
    const today = this.getTodayCost();
    const monthly = this.getMonthlyCost();
    const yearMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
    const lines = [
      `Today:      ${formatCost(today)}`,
      `This month: ${formatCost(monthly)} (${yearMonth})`
    ];
    if (monthlyBudget && monthlyBudget > 0) {
      const ratio = monthly / monthlyBudget;
      const bar = "\u2588".repeat(Math.min(20, Math.round(ratio * 20))) + "\u2591".repeat(Math.max(0, 20 - Math.round(ratio * 20)));
      lines.push(`Budget:     ${formatCost(monthlyBudget)} [${bar}] ${Math.round(ratio * 100)}%`);
    }
    const last7 = [];
    for (let i = 6; i >= 0; i--) {
      const d = /* @__PURE__ */ new Date();
      d.setDate(d.getDate() - i);
      const dateStr = d.toISOString().slice(0, 10);
      const record = this.records.find((r) => r.date === dateStr);
      const dayLabel = dateStr.slice(5);
      last7.push(`${dayLabel}: ${record ? formatCost(record.cost) : "$0.00"}`);
    }
    lines.push(`Last 7 days: ${last7.join("  ")}`);
    return lines.join("\n");
  }
};

export {
  CostTracker
};
