#!/usr/bin/env npx tsx

/**
 * VTOP statusline for Claude Code.
 *
 * Shows: VTOP · Sonnet · 12,40 ₽ · баланс 4987 ₽ · контекст 42%
 *
 * Setup — add to ~/.claude/settings.json:
 * {
 *   "statusLine": {
 *     "type": "command",
 *     "command": "/path/to/statusline.sh"
 *   }
 * }
 *
 * Keep this file next to statusline.sh.
 *
 * How the spend is measured: VTOP returns a price in `usage.cost_rub`, but
 * only on non-streaming responses — and Claude Code always streams. So instead
 * of pricing requests, this reads the account balance (GET /vtop/balance)
 * at most once every POLL_MS and sums up how much it dropped. The number is
 * exact rubles, with two caveats worth knowing:
 *   - anything else spending on the same key lands in the total too;
 *   - top-ups are ignored, so a mid-session payment won't rewind the counter.
 *
 * The key is read from ANTHROPIC_AUTH_TOKEN (or ANTHROPIC_API_KEY). Everything
 * else — model, context usage — comes from the JSON Claude Code puts on stdin.
 */

import { existsSync, readFileSync, writeFileSync } from "node:fs";

const BALANCE_URL = "https://api.daaj.ru/v1/vtop/balance";
/** The statusline re-renders constantly; don't poll the balance every time. */
const POLL_MS = 10_000;

interface State {
  /** Rubles spent since the session's first balance reading. */
  spent: number;
  last_balance: number | null;
  checked_at: number;
}

interface StatuslineInput {
  session_id?: string;
  model?: { id?: string; display_name?: string };
  context_window?: { used_percentage?: number };
}

const rubles = new Intl.NumberFormat("ru-RU", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

function num(value: unknown): number {
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
}

function loadState(path: string): State {
  const fallback: State = { spent: 0, last_balance: null, checked_at: 0 };
  if (!existsSync(path)) return fallback;

  try {
    const parsed = JSON.parse(readFileSync(path, "utf-8"));
    return {
      spent: num(parsed.spent),
      last_balance:
        typeof parsed.last_balance === "number" ? parsed.last_balance : null,
      checked_at: num(parsed.checked_at),
    };
  } catch {
    return fallback;
  }
}

async function fetchBalance(apiKey: string): Promise<number | null> {
  try {
    const res = await fetch(BALANCE_URL, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    if (!res.ok) return null;
    const data = (await res.json()) as { balance?: number };
    return typeof data.balance === "number" ? data.balance : null;
  } catch {
    return null;
  }
}

async function readStdin(): Promise<string> {
  let data = "";
  for await (const chunk of process.stdin) data += chunk;
  return data;
}

async function main(): Promise<void> {
  let input: StatuslineInput;
  try {
    input = JSON.parse(await readStdin());
  } catch {
    process.stdout.write("VTOP — invalid statusline input");
    return;
  }

  const sessionId = input.session_id;
  if (typeof sessionId !== "string") {
    process.stdout.write("VTOP — invalid statusline input");
    return;
  }

  const apiKey =
    process.env.ANTHROPIC_AUTH_TOKEN ?? process.env.ANTHROPIC_API_KEY ?? "";
  const statePath = `/tmp/claude-vtop-cost-${sessionId}.json`;
  const state = loadState(statePath);

  if (apiKey && Date.now() - state.checked_at >= POLL_MS) {
    const balance = await fetchBalance(apiKey);
    state.checked_at = Date.now();
    if (balance !== null) {
      if (state.last_balance !== null && balance < state.last_balance) {
        state.spent += state.last_balance - balance;
      }
      state.last_balance = balance;
    }
    writeFileSync(statePath, JSON.stringify(state, null, 2));
  }

  const parts = ["VTOP"];

  const model = input.model?.display_name || input.model?.id;
  if (model) parts.push(model);

  if (!apiKey) {
    parts.push("ключ не найден");
  } else if (state.last_balance === null) {
    parts.push("баланс недоступен");
  } else {
    parts.push(`${rubles.format(state.spent)} ₽`);
    parts.push(`баланс ${Math.round(state.last_balance)} ₽`);
  }

  const context = input.context_window?.used_percentage;
  if (typeof context === "number") {
    parts.push(`контекст ${Math.round(context)}%`);
  }

  process.stdout.write(parts.join(" · "));
}

main().catch((err) => {
  process.stdout.write(`VTOP — ${err.message}`);
});
