import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { homedir } from 'node:os';

//#region ../core/dist/llm/catalog-cache.js
/**
* Live model catalog cache — fetches and persists models.dev/api.json.
*
* Resolution priority:
*   1. Disk cache (versioned under `<CLEO_DATA_DIR>/llm-catalog/`)
*   2. Bundled snapshot (curated-models.json in this package)
*
* The cache is keyed by a Unix-timestamp prefix: `<ts>-models.json`. A
* stable `latest.json` symlink points to the most recent entry for quick
* access without scanning the directory.
*
* Stale-fallback behaviour (network failures):
*   - If the network fetch fails, the most-recently-written `*.json` in
*     the cache directory is returned instead.
*   - If the cache directory is empty or absent, the bundled snapshot is
*     returned so callers always get *some* data.
*
* Source: https://models.dev/api.json
*
* @module llm/catalog-cache
* @task T9314
* @epic T9261 (T-LLM-CRED-CENTRALIZATION Phase 5)
*/
/**
* Resolve the directory where catalog snapshots are stored.
*
* Respects `CLEO_DATA_DIR` env override (same convention used by the
* credentials layer), then falls back to
* `$XDG_DATA_HOME/cleo` → `~/.local/share/cleo`.
*/
function getCatalogDir() {
	if (process.env["CLEO_DATA_DIR"]) return join(process.env["CLEO_DATA_DIR"], "llm-catalog");
	return join(process.env["XDG_DATA_HOME"] ?? join(homedir(), ".local", "share"), "cleo", "llm-catalog");
}
/**
* Return the path to the most-recently-written `*-models.json` in `dir`.
*
* Entries are sorted by `mtime` descending so the first item is the
* freshest. Returns `null` when the directory is empty or does not exist.
*/
function findLatestCacheFile(dir) {
	if (!existsSync(dir)) return null;
	const files = readdirSync(dir).filter((f) => f.endsWith("-models.json")).map((f) => ({
		name: f,
		mtime: statSync(join(dir, f)).mtimeMs
	})).sort((a, b) => b.mtime - a.mtime);
	return files.length > 0 ? join(dir, files[0].name) : null;
}
/**
* Read a `ModelsCatalogFile` from `filePath`.
*
* Returns `null` on any parse or I/O error so callers can fall through to
* the next tier without crashing.
*/
function readCacheFile(filePath) {
	try {
		const raw = readFileSync(filePath, "utf-8");
		return JSON.parse(raw);
	} catch {
		return null;
	}
}

//#region ../core/dist/llm/catalog-model-resolver.js
/**
* Catalog-driven model resolution and validation (T11773 · E8).
*
* Provides two primitives that wire `cleo llm use` / `cleo llm profile` to
* the live models.dev catalog instead of hardcoded literals:
*
*   - {@link resolveProviderDefaultModel} — derive the latest model for a
*     provider by sorting the catalog entries by `release_date` descending.
*     Falls back to `null` when the catalog has no entries for that provider.
*
*   - {@link validateModelForProvider} — assert that a model ID is present in
*     the catalog for the given provider. Used by `llmUse` / `llmProfile` to
*     reject typos / unknown model strings at configuration time rather than
*     at inference time.
*
* Both functions read the disk-cached snapshot written by
* `cleo llm refresh-catalog` — no network requests are issued at call time.
* When no disk snapshot is available (fresh install, catalog never fetched)
* the functions return `null` / `false` respectively so callers can surface
* a user-friendly "run `cleo llm refresh-catalog` first" message.
*
* @module llm/catalog-model-resolver
* @task T11773
* @epic T11694 (E8-CATALOG-CURATION)
*/
/** In-memory catalog cache for the process lifetime. */
var _catalog;
/**
* Load the full catalog from disk — no network fetch.
*
* Returns `null` when no snapshot is available so callers can surface a
* "run `cleo llm refresh-catalog` first" hint rather than crashing.
*
* Results are cached for the process lifetime. Call
* {@link _resetCatalogModelResolverCache} in tests to clear the cache.
*
* @internal
*/
function getDiskCatalog() {
	if (_catalog !== void 0) return _catalog;
	const latest = findLatestCacheFile(getCatalogDir());
	if (!latest) {
		_catalog = null;
		return null;
	}
	_catalog = readCacheFile(latest);
	return _catalog;
}
/**
* Derive the default model for a provider by finding the model with the
* latest `release_date` in the catalog.
*
* Resolution is catalog-key aware: the caller passes the models.dev provider
* key (e.g. `"openai"`, `"anthropic"`, `"google"`). Entries without a
* `release_date` field are excluded from the comparison so unpublished or
* internal-only entries do not shadow real released models.
*
* @param providerCatalogKey - The models.dev provider key (e.g. `"openai"`).
* @param catalogOverride - Optional catalog for testing; defaults to the disk
*   snapshot loaded by {@link getDiskCatalog}.
* @returns The model ID with the latest `release_date`, or `null` when the
*   catalog is unavailable or the provider has no dated entries.
*
* @task T11773
*/
function resolveProviderDefaultModel(providerCatalogKey, catalogOverride) {
	const catalog = catalogOverride ?? getDiskCatalog();
	if (!catalog) return null;
	const provider = catalog[providerCatalogKey];
	if (!provider?.models) return null;
	let latestId = null;
	let latestDate = "";
	for (const [id, entry] of Object.entries(provider.models)) {
		const rd = entry.release_date;
		if (!rd) continue;
		if (rd > latestDate) {
			latestDate = rd;
			latestId = id;
		}
	}
	return latestId;
}
/**
* Check whether a model ID is present in the catalog for a given provider.
*
* The check is provider-scoped: `gpt-4o` is valid for `openai` but would
* fail for `anthropic`. This prevents cross-provider typos such as
* `cleo llm profile extraction openai --model claude-opus-4-8`.
*
* When the catalog is unavailable (not yet fetched) the function returns
* `{ valid: true, reason: 'catalog-unavailable' }` so the caller can pass
* the model through with a soft warning rather than blocking the user.
*
* @param model - Model ID to validate (e.g. `"gpt-5.5"`).
* @param providerCatalogKey - The models.dev provider key (e.g. `"openai"`).
* @param catalogOverride - Optional catalog for testing.
* @returns Validation result with `valid` boolean and diagnostic `reason`.
*
* @task T11773
*/
function validateModelForProvider(model, providerCatalogKey, catalogOverride) {
	const catalog = getDiskCatalog();
	if (!catalog) return {
		valid: true,
		reason: "catalog-unavailable"
	};
	const provider = catalog[providerCatalogKey];
	if (!provider?.models) return {
		valid: true,
		reason: "provider-not-in-catalog"
	};
	const found = Object.hasOwn(provider.models, model);
	return {
		valid: found,
		reason: found ? "found" : "not-found"
	};
}
/**
* List the catalog model ids for a provider, newest first.
*
* Sorts by `release_date` descending so the latest models surface at the top of
* an interactive picker (e.g. the models/roles wizard section — T11726). Entries
* without a `release_date` are appended after the dated ones, in catalog order.
* Returns an empty array when the catalog is unavailable or the provider has no
* entries, so callers can fall back to a free-text prompt.
*
* @param providerCatalogKey - The models.dev provider key (e.g. `"openai"`).
* @param limit - Maximum number of model ids to return (default `12`). Use a
*   small cap so an interactive menu stays scannable.
* @param catalogOverride - Optional catalog for testing; defaults to the disk
*   snapshot loaded by {@link getDiskCatalog}.
* @returns Model ids newest-first, capped at `limit`.
*
* @task T11726
*/
function listProviderModels(providerCatalogKey, limit = 12, catalogOverride) {
	const catalog = getDiskCatalog();
	if (!catalog) return [];
	const provider = catalog[providerCatalogKey];
	if (!provider?.models) return [];
	return [...Object.entries(provider.models)].sort(([, a], [, b]) => {
		const da = a.release_date ?? "";
		const db = b.release_date ?? "";
		if (da === db) return 0;
		return db.localeCompare(da);
	}).slice(0, Math.max(0, limit)).map(([id]) => id);
}
/**
* Return the models.dev catalog key for a given CLEO provider name.
*
* CLEO provider names (e.g. `"openai"`, `"anthropic"`) usually match their
* models.dev catalog key directly. Where they diverge (Gemini is registered
* as `"google"` in models.dev) this map provides the translation.
*
* Add new mappings here when a builtin provider's `name` field does not
* match the models.dev key.
*
* @param providerName - CLEO-canonical provider name (e.g. `"gemini"`).
* @returns The models.dev catalog key (e.g. `"google"`).
*
* @task T11773
*/
function catalogKeyForProvider(providerName) {
	return { gemini: "google" }[providerName.toLowerCase()] ?? providerName.toLowerCase();
}

export { readCacheFile as a, catalogKeyForProvider as c, findLatestCacheFile as f, getCatalogDir as g, listProviderModels as l, resolveProviderDefaultModel as r, validateModelForProvider as v };
//# sourceMappingURL=catalog-model-resolver2-Cexk2TN0.js.map
