/**
 * Billing & Cost Calculation Library
 */

/**
 * Default model pricing (USD per million tokens).
 *
 * Entries optionally carry `cache_write` and `cache_read` rates for models
 * that support prompt caching. When an entry omits these, callers that
 * supply `extras` to `estimateCost` see a 0 contribution for the cache
 * components — preserving the legacy 4-arg behaviour bit-for-bit.
 *
 * The cache rates added here are sourced from `app/lib/claude-code/pricing.js`
 * (the AgentLens-derived 4-column table). They populate only the three
 * models that have published cache-pricing for Code Sessions analytics:
 * opus-4-7, sonnet-4-6, haiku-4-5. Other entries are left to default to 0.
 */
export const DEFAULT_PRICING = [
  // Versioned rows in the marker block below are AUTO-GENERATED by
  // `npm run pricing:refresh` from LiteLLM's community-maintained pricing
  // JSON. Anything outside the markers — unversioned family defaults
  // ('opus', 'sonnet', 'haiku') and models LiteLLM doesn't track (Codex,
  // Llama) — stays HAND-CURATED. Run the refresh script after Anthropic /
  // OpenAI / Google publish new rates and review the diff.
  // MODEL_PRICING_GENERATED:BILLING:START
  { pattern: 'opus-4-7', label: 'Claude Opus 4.7', input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 }, // claude-opus-4-7
  { pattern: 'opus-4-6', label: 'Claude Opus 4.6', input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 }, // claude-opus-4-6
  { pattern: 'opus-4-5', label: 'Claude Opus 4.5', input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 }, // claude-opus-4-5
  { pattern: 'opus-4-1', label: 'Claude Opus 4.1 (legacy)', input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 }, // claude-opus-4-1-20250805
  { pattern: 'sonnet-4-6', label: 'Claude Sonnet 4.6', input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 }, // claude-sonnet-4-6
  { pattern: 'sonnet-4-5', label: 'Claude Sonnet 4.5', input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 }, // claude-sonnet-4-5-20250929
  { pattern: 'haiku-4-5', label: 'Claude Haiku 4.5', input: 1, output: 5, cache_write: 1.25, cache_read: 0.1 }, // claude-haiku-4-5-20251001
  { pattern: 'gpt-4.1', label: 'GPT-4.1', input: 2, output: 8, cache_write: 0, cache_read: 0.5 }, // gpt-4.1-2025-04-14
  { pattern: 'gpt-4.1-mini', label: 'GPT-4.1 Mini', input: 0.4, output: 1.6, cache_write: 0, cache_read: 0.1 }, // gpt-4.1-mini-2025-04-14
  { pattern: 'gpt-4.1-nano', label: 'GPT-4.1 Nano', input: 0.1, output: 0.4, cache_write: 0, cache_read: 0.025 }, // gpt-4.1-nano-2025-04-14
  { pattern: 'gpt-4o', label: 'GPT-4o', input: 2.5, output: 10, cache_write: 0, cache_read: 1.25 }, // gpt-4o-2024-08-06
  { pattern: 'gpt-4o-mini', label: 'GPT-4o Mini', input: 0.15, output: 0.6, cache_write: 0, cache_read: 0.075 }, // gpt-4o-mini-2024-07-18
  { pattern: 'o3', label: 'o3', input: 2, output: 8, cache_write: 0, cache_read: 0.5 }, // o3-2025-04-16
  { pattern: 'o3-mini', label: 'o3-mini', input: 1.1, output: 4.4, cache_write: 0, cache_read: 0.55 }, // o3-mini-2025-01-31
  { pattern: 'o3-pro', label: 'o3-pro', input: 20, output: 80, cache_write: 0, cache_read: 0 }, // o3-pro-2025-06-10
  { pattern: 'o4-mini', label: 'o4-mini', input: 1.1, output: 4.4, cache_write: 0, cache_read: 0.275 }, // o4-mini-2025-04-16
  { pattern: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', input: 1.25, output: 10, cache_write: 0, cache_read: 0.125 }, // gemini/gemini-2.5-pro
  { pattern: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', input: 0.3, output: 2.5, cache_write: 0, cache_read: 0.03 }, // gemini/gemini-2.5-flash
// MODEL_PRICING_GENERATED:BILLING:END
  // Unversioned family defaults — frozen so historical re-pricing doesn't
  // drift silently when family rates change.
  { pattern: 'opus', label: 'Claude Opus (legacy default)', input: 15, output: 75, cache_write: 18.75, cache_read: 1.50 },
  { pattern: 'sonnet', label: 'Claude Sonnet (default)', input: 3, output: 15, cache_write: 3.75, cache_read: 0.30 },
  { pattern: 'haiku', label: 'Claude Haiku (default)', input: 1, output: 5, cache_write: 1.25, cache_read: 0.10 },
  // Models LiteLLM doesn't track (deprecated / DashClaw-internal aliases).
  { pattern: 'codex-5.4', label: 'Codex 5.4', input: 3, output: 15 },
  { pattern: 'codex', label: 'Codex (default)', input: 3, output: 15 },
  { pattern: 'llama-4-maverick', label: 'Llama 4 Maverick', input: 0.50, output: 0.77 },
  { pattern: 'llama-4-scout', label: 'Llama 4 Scout', input: 0.17, output: 0.35 },
];

const _warnedUnknownModels = new Set();

/**
 * Estimate cost based on token usage and model.
 *
 * Backwards-compatible 4-arg form is preserved bit-for-bit. The optional
 * 5th `extras` argument adds cache-aware components without changing the
 * unknown-model contract (still $0 + one-time warn).
 *
 * @param {number} tokensIn - Input tokens.
 * @param {number} tokensOut - Output tokens.
 * @param {string|null|undefined} model - Model identifier. Falsy/unknown
 *        returns 0 — we refuse to guess because a wrong guess pollutes
 *        cost dashboards. Historically this fell back to the first
 *        (Opus-tier) pricing row, which priced cheap open-source models
 *        (Llama, Groq, Sonar, DeepSeek) at ~1000x their real cost.
 * @param {Array<{pattern: string, input: number, output: number, cache_write?: number, cache_read?: number}>|null} customPricing
 *        Optional custom pricing table from org settings.
 * @param {{ cache_creation_tokens?: number, cache_read_tokens?: number }|null} extras
 *        Optional cache-token components. When non-null and the model has
 *        cache pricing, adds `(cw * cache_write + cr * cache_read) / 1e6`.
 *        Entries without cache columns contribute 0 — so 4-arg callers and
 *        unknown models behave exactly as before.
 * @returns {number} Estimated cost in USD.
 */
export function estimateCost(tokensIn, tokensOut, model, customPricing = null, extras = null) {
  if (!model) return 0;
  const m = String(model).toLowerCase();
  const pricing = customPricing || DEFAULT_PRICING;

  for (const entry of pricing) {
    if (m.includes(entry.pattern)) {
      const base = (tokensIn * entry.input / 1_000_000) + (tokensOut * entry.output / 1_000_000);
      if (!extras) return base;
      const cw = Number(extras.cache_creation_tokens) || 0;
      const cr = Number(extras.cache_read_tokens) || 0;
      const cwRate = Number(entry.cache_write) || 0;
      const crRate = Number(entry.cache_read) || 0;
      return base + (cw * cwRate + cr * crRate) / 1_000_000;
    }
  }

  if (!_warnedUnknownModels.has(m)) {
    _warnedUnknownModels.add(m);
    console.warn('[billing] Unknown model, pricing as $0 (extend DEFAULT_PRICING or set org custom pricing):', model);
  }
  return 0;
}
