/**
 * Charm Hyper (https://hyper.charm.land) provider identity and auth for Prime Agent.
 */

import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { getJson } from "./http.js";

export const PROVIDER = "hyper";
export const DISPLAY_NAME = "Charm Hyper";
export const SITE_URL = "https://hyper.charm.land";
export const API_BASE_URL = `${SITE_URL}/v1`;
export const ENV_VAR = "HYPER_API_KEY";

/**
 * What Prime Agent resolves when no credential is stored. `!`-prefixed values run through
 * the shell (bash -c), and an empty result counts as an unresolved key rather than an
 * empty string, which turns "no key" into a readable error instead of a 401.
 */
export const API_KEY_CONFIG = `!printf %s "$${ENV_VAR}"`;

/**
 * Prime Agent's agent dir, mirrored here: importing the runtime for it drags a
 * jiti-hostile module graph into the extension load path.
 */
export function agentDir(): string {
	const configured = process.env.PRIME_AGENT_CODING_AGENT_DIR ?? process.env.PI_CODING_AGENT_DIR;
	if (!configured) return join(homedir(), ".prime", "agent");
	if (configured === "~") return homedir();
	return configured.startsWith("~/") ? join(homedir(), configured.slice(2)) : configured;
}

const AUTH_FILE = (): string => join(agentDir(), "auth.json");

export const cachePath = (): string => join(agentDir(), "hyper-catalog.json");

export const isOffline = (): boolean => {
	const value = process.env.PI_OFFLINE;
	return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
};

/**
 * The API-key credential in an auth.json payload. A provider entry is one credential or a
 * list of them (Prime Agent pools accounts: a subscription and an API key coexist), so both
 * shapes resolve here.
 */
export function storedKeyFrom(payload: unknown): string | undefined {
	if (typeof payload !== "object" || payload === null) return undefined;
	const entry = Object.getOwnPropertyDescriptor(payload, PROVIDER)?.value;
	for (const credential of Array.isArray(entry) ? entry : [entry]) {
		if (typeof credential !== "object" || credential === null) continue;
		const key = Object.getOwnPropertyDescriptor(credential, "key")?.value;
		if (typeof key === "string" && key !== "") return key;
	}
	return undefined;
}

/**
 * Key saved by `/login hyper` - a pasted key, or the device-code subscription login. This
 * and `HYPER_API_KEY` are the whole story: no key file, so the credential has one home, and
 * a missing key is reported as unresolved rather than sent as a placeholder.
 */
export function storedKey(): string | undefined {
	try {
		return storedKeyFrom(JSON.parse(readFileSync(AUTH_FILE(), "utf-8")));
	} catch {
		return undefined;
	}
}

/** Key for the probe scripts: `HYPER_API_KEY`, else the stored credential. */
export const resolveKey = (): string | undefined => process.env[ENV_VAR] ?? storedKey();

/** The account's Hypercredit balance. `GET /v1/credits` is the only account endpoint. */
export async function fetchBalance(apiKey: string): Promise<number | undefined> {
	const payload = await getJson(`${SITE_URL}/v1/credits`, { Authorization: `Bearer ${apiKey}` });
	if (typeof payload !== "object" || payload === null) return undefined;
	for (const field of ["balance", "balance_usd"]) {
		const value = Object.getOwnPropertyDescriptor(payload, field)?.value;
		if (typeof value === "number") return value;
	}
	return undefined;
}
