export interface ModelPrice {
  model: string;
  inputPerKTok: number;
  outputPerKTok: number;
  cachedInputPerKTok: number;
  cacheCreatePerKTok: number;
  bytesPerToken: number;
}

export const BYTES_PER_TOKEN_FALLBACK = 3.5;

export interface CostInput {
  inputTokens: number;
  cachedInputTokens?: number;
  outputTokens?: number;
}

export interface CounterfactualInput {
  rawInputTokens: number;
  compressedInputTokens: number;
  cacheHitInputTokens: number;
  cacheCreationInputTokens: number;
  rawOutputTokens: number;
}

export interface CounterfactualCosts {
  withoutFtUsd: number;
  withFtUsd: number;
  netSavingsUsd: number;
}

export function bytesPerToken(price: ModelPrice | null | undefined): number {
  return price?.bytesPerToken ?? BYTES_PER_TOKEN_FALLBACK;
}

export function costUsd(price: ModelPrice, input: CostInput): number {
  const cachedInputTokens = input.cachedInputTokens ?? 0;
  const outputTokens = input.outputTokens ?? 0;
  const uncachedInputTokens = Math.max(0, input.inputTokens - cachedInputTokens);
  return (uncachedInputTokens / 1000) * price.inputPerKTok
    + (cachedInputTokens / 1000) * price.cachedInputPerKTok
    + (outputTokens / 1000) * price.outputPerKTok;
}

/** @deprecated Use dollarsSavedRow (mirror counterfactual). Kept for legacy callers. */
export function counterfactual(price: ModelPrice, input: CounterfactualInput): CounterfactualCosts {
  const cachedInputTokens = input.cacheHitInputTokens + input.cacheCreationInputTokens;
  const withFtUsd = costUsd(price, {
    inputTokens: input.compressedInputTokens,
    cachedInputTokens,
    outputTokens: input.rawOutputTokens,
  });
  const withoutFtUsd = costUsd(price, {
    inputTokens: input.rawInputTokens,
    outputTokens: input.rawOutputTokens,
  });
  return {
    withoutFtUsd,
    withFtUsd,
    netSavingsUsd: netSavings(withoutFtUsd, withFtUsd),
  };
}

export function netSavings(withoutFtUsd: number, withFtUsd: number): number {
  return withoutFtUsd - withFtUsd;
}

export interface MirrorRowInput {
  rawInputTokens: number;
  compressedInputTokens: number;
  cacheHitInputTokens: number;
  cacheCreationInputTokens: number;
  rawOutputTokens: number;
}

/**
 * Mirror counterfactual with per-bucket cache pricing.
 *
 * Anthropic billing:
 *   uncached input → inputPerKTok        (1.0×)
 *   cache_read     → cachedInputPerKTok  (0.1×)
 *   cache_create   → cacheCreatePerKTok  (1.25×)
 *
 * compressed_input_tokens = uncached + cache_read + cache_create (post-T2 revert).
 * uncached = compressed - cache_read - cache_create.
 *
 * Mirror assumption: cache hit/create RATIOS observed on the with-FT row apply
 * to the raw (uncompressed) prompt — Anthropic's cache is server-side prefix-
 * hashed and works regardless of FT presence. Pessimistic baseline (raw=all-
 * uncached) would falsely credit FT with the cache benefit Anthropic provides.
 *
 * Returns signed USD: positive = FT saved, negative = FT cost.
 * Edge: billed = 0 (phantom slipped through) → pessimistic no-cache fallback.
 */
/**
 * Per-bucket with-FT billing cost. Single source of truth for the
 * "what FT actually cost us" number. Used by dollarsSavedRow AND
 * getSessionGain to keep withFt math identical in both call sites.
 */
export function withFtCostRow(price: ModelPrice, r: MirrorRowInput): number {
  const ftUncached = Math.max(0, r.compressedInputTokens - r.cacheHitInputTokens - r.cacheCreationInputTokens);
  return (ftUncached                  / 1000) * price.inputPerKTok
       + (r.cacheHitInputTokens       / 1000) * price.cachedInputPerKTok
       + (r.cacheCreationInputTokens  / 1000) * price.cacheCreatePerKTok
       + (r.rawOutputTokens           / 1000) * price.outputPerKTok;
}

export function dollarsSavedRow(price: ModelPrice, r: MirrorRowInput): number {
  const billed = r.compressedInputTokens;
  const outUsd = (r.rawOutputTokens / 1000) * price.outputPerKTok;

  const withFt = withFtCostRow(price, r);

  let withoutFt: number;
  if (billed === 0) {
    withoutFt = (r.rawInputTokens / 1000) * price.inputPerKTok + outUsd;
  } else {
    const rRead   = r.cacheHitInputTokens      / billed;
    const rCreate = r.cacheCreationInputTokens / billed;
    const rUncached = Math.max(0, 1 - rRead - rCreate);
    withoutFt =
      (r.rawInputTokens * rUncached / 1000) * price.inputPerKTok +
      (r.rawInputTokens * rRead     / 1000) * price.cachedInputPerKTok +
      (r.rawInputTokens * rCreate   / 1000) * price.cacheCreatePerKTok +
      outUsd;
  }
  return withoutFt - withFt;
}
