/**
 * Cost-CLI persistence helpers (CLI.8) — split out of cost.ts for the
 * file-size budget.
 *
 * Three concerns:
 *
 *   1. `defaultConfigPath` / `readSubscriptionPools` — load configured
 *      subscription pools from `~/.opensquid/config.yaml` (`subscription_pools:`
 *      block keyed by tier). Empty file / missing block → empty record (the
 *      CLI surfaces this as "no pools configured" rather than crashing —
 *      `cost subscriptions` should still print a clean message before any
 *      pools have been declared). Malformed YAML throws (no silent fail-open).
 *
 *   2. `CostRoutingLog` — libsql-backed audit table for AUTO.7 `CostRouter`
 *      routing decisions. The router itself takes a caller-wired
 *      `audit: (entry) => void` sink (see `src/models/cost_router.ts`); the
 *      daemon wires that sink to `CostRoutingLog.append`. The CLI's
 *      `cost routing` / `cost` (default summary) read from this table.
 *
 *      Schema mirrors the `CostRoutingAuditEntry` shape: (tier, alias,
 *      success, reason, timestamp). Idempotent `CREATE TABLE IF NOT EXISTS`
 *      on first init — matches the AUTO.2 `rate_limit_buckets` + CLI.5
 *      `audit_log` posture (no versioned migration framework in opensquid
 *      yet).
 *
 *   3. `formatTimestamp` — re-exports `audit_state.ts`'s ISO formatter to
 *      keep cost-side imports inside one helper file. Same precedent as
 *      `cache.ts` importing `parseDurationToMs` from `audit_state.ts`.
 *
 * Imports from: node:fs/promises, yaml, @libsql/client,
 *   ../../models/cost_router, ../../runtime/paths.
 * Imported by: src/setup/cli/cost.ts + src/setup/cli/cost.test.ts.
 */
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { OPENSQUID_HOME } from '../../runtime/paths.js';
import { formatTimestamp } from './audit_state.js';
export { formatTimestamp };
export const defaultConfigPath = () => join(OPENSQUID_HOME(), 'config.yaml');
/** Read configured pools. Missing file / missing block → empty record. */
export async function readSubscriptionPools(path) {
    let raw;
    try {
        raw = await readFile(path, 'utf8');
    }
    catch (e) {
        if (e.code === 'ENOENT')
            return {};
        throw e;
    }
    let parsed;
    try {
        parsed = parseYaml(raw);
    }
    catch (e) {
        throw new Error(`config.yaml is malformed (${path}): ${e instanceof Error ? e.message : String(e)}`);
    }
    if (parsed === null || parsed === undefined)
        return {};
    if (typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error(`config.yaml must be a mapping (${path})`);
    }
    const pools = parsed.subscription_pools;
    if (pools === undefined || pools === null)
        return {};
    if (typeof pools !== 'object' || Array.isArray(pools)) {
        throw new Error(`config.yaml: \`subscription_pools\` must be a mapping (${path})`);
    }
    const result = {};
    for (const tier of ['cheap', 'balanced', 'premium']) {
        const list = pools[tier];
        if (list === undefined)
            continue;
        if (!Array.isArray(list)) {
            throw new Error(`config.yaml: \`subscription_pools.${tier}\` must be a list (${path})`);
        }
        result[tier] = list
            .filter((p) => typeof p === 'object' && p !== null)
            .map((p) => normalizePool(p));
    }
    return result;
}
function normalizePool(p) {
    const base = {
        alias: typeof p.alias === 'string' ? p.alias : '',
        provider: typeof p.provider === 'string' ? p.provider : '',
        model: typeof p.model === 'string' ? p.model : '',
    };
    if (typeof p.rateLimit === 'object' && p.rateLimit !== null) {
        const rl = p.rateLimit;
        const rpm = Number(rl.rpm);
        if (Number.isFinite(rpm)) {
            base.rateLimit = typeof rl.tpm === 'number' ? { rpm, tpm: rl.tpm } : { rpm };
        }
    }
    return base;
}
const CREATE_TABLE_SQL = `
  CREATE TABLE IF NOT EXISTS cost_routing_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    occurred_at_ms INTEGER NOT NULL,
    tier TEXT NOT NULL,
    alias TEXT,
    success INTEGER NOT NULL,
    reason TEXT
  );
`;
const CREATE_INDEX_SQL = `
  CREATE INDEX IF NOT EXISTS idx_cost_routing_occurred
    ON cost_routing_log(occurred_at_ms);
`;
export class CostRoutingLog {
    db;
    initialized = false;
    constructor(db) {
        this.db = db;
    }
    async init() {
        if (this.initialized)
            return;
        await this.db.execute(CREATE_TABLE_SQL);
        await this.db.execute(CREATE_INDEX_SQL);
        this.initialized = true;
    }
    /** Append one decision. `occurredAtMs` from caller-supplied clock. */
    async append(entry) {
        await this.init();
        await this.db.execute({
            sql: `INSERT INTO cost_routing_log (occurred_at_ms, tier, alias, success, reason)
            VALUES (?, ?, ?, ?, ?)`,
            args: [
                entry.occurredAtMs,
                entry.tier,
                entry.alias,
                entry.success ? 1 : 0,
                entry.reason ?? null,
            ],
        });
    }
    /** Most-recent N decisions, newest-first. */
    async recent(limit) {
        await this.init();
        const rs = await this.db.execute({
            sql: `SELECT id, occurred_at_ms, tier, alias, success, reason
            FROM cost_routing_log
            ORDER BY occurred_at_ms DESC, id DESC
            LIMIT ?`,
            args: [Math.max(1, Math.floor(limit))],
        });
        return rs.rows.map((r) => ({
            id: Number(r.id),
            occurredAtMs: Number(r.occurred_at_ms),
            tier: String(r.tier),
            alias: r.alias === null ||
                r.alias === undefined
                ? null
                : String(r.alias),
            success: Number(r.success) === 1,
            reason: r.reason === null ||
                r.reason === undefined
                ? null
                : String(r.reason),
        }));
    }
    /** Aggregate row counts per (tier, alias). Used by the default `cost`
     *  summary verb to render pool usage across tiers. */
    async summary() {
        await this.init();
        const rs = await this.db.execute(`SELECT tier, alias, COUNT(*) AS picks
       FROM cost_routing_log
       WHERE success = 1 AND alias IS NOT NULL
       GROUP BY tier, alias
       ORDER BY tier ASC, picks DESC`);
        return rs.rows.map((r) => ({
            tier: String(r.tier),
            alias: String(r.alias),
            picks: Number(r.picks),
        }));
    }
}
function pad(s, w) {
    if (s.length >= w)
        return s.slice(0, w - 1) + ' ';
    return s + ' '.repeat(w - s.length);
}
export function renderSummary(rows) {
    const lines = [pad('TIER', 12) + pad('ALIAS', 24) + pad('PICKS', 10)];
    for (const r of rows) {
        lines.push(pad(r.tier, 12) + pad(r.alias, 24) + pad(String(r.picks), 10));
    }
    return lines.join('\n');
}
export function renderRouting(rows) {
    const lines = [
        pad('TIMESTAMP', 28) +
            pad('TIER', 10) +
            pad('ALIAS', 20) +
            pad('STATUS', 10) +
            pad('REASON', 22),
    ];
    for (const r of rows) {
        lines.push(pad(formatTimestamp(r.occurredAtMs), 28) +
            pad(r.tier, 10) +
            pad(r.alias ?? '-', 20) +
            pad(r.success ? 'picked' : 'failed', 10) +
            pad(r.reason ?? '-', 22));
    }
    return lines.join('\n');
}
export function renderSubscriptions(pools) {
    const lines = [
        pad('TIER', 12) + pad('ALIAS', 24) + pad('PROVIDER', 14) + pad('MODEL', 28),
    ];
    for (const tier of ['cheap', 'balanced', 'premium']) {
        const list = pools[tier] ?? [];
        for (const p of list) {
            lines.push(pad(tier, 12) + pad(p.alias, 24) + pad(p.provider, 14) + pad(p.model, 28));
        }
    }
    return lines.join('\n');
}
//# sourceMappingURL=cost_state.js.map