import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
//#region src/providers/auth-runtime/auth-profile-store.ts
/**
* {@link AuthProfileStore} implementations.
*
* - {@link NoopAuthProfileStore}: returns nothing; used by tests and as a safe
*   fallback when no agent context is available.
* - {@link DiskAuthProfileStore}: persists per-agent profiles under
*   `<stateDir>/agents/<agentId>/auth-profiles.json` with a lazy in-memory
*   cache and atomic writes. Used by the gateway and CLI to host OAuth
*   tokens (Codex / Anthropic) without leaking them through env vars.
*
* Capability providers stay synchronous: `getApiKeySync` / `hasCredentialSync`
* read the cached snapshot. Async members (`save`, `refresh`) are only used
* by vendor-specific code that explicitly opts in to OAuth flows.
*/
init_logger();
const log = createLogger("AuthProfileStore");
const STORE_FILENAME = "auth-profiles.json";
var NoopAuthProfileStore = class {
	getApiKeySync() {}
	hasCredentialSync() {
		return false;
	}
	list() {
		return [];
	}
	get() {}
	async save() {}
	async refresh(profile) {
		return profile;
	}
};
function emptyFile() {
	return {
		version: 1,
		profiles: []
	};
}
function isDiskFile(v) {
	if (!v || typeof v !== "object") return false;
	const o = v;
	return o.version === 1 && Array.isArray(o.profiles);
}
/**
* Persistent per-agent credential store.
*
* File layout (`<stateDir>/agents/<agentId>/auth-profiles.json`):
*
* ```json
* {
*   "version": 1,
*   "profiles": [
*     { "provider": "openai", "profileId": "default", "mode": "api-key", "apiKey": "sk-..." },
*     { "provider": "openai", "profileId": "codex",   "mode": "oauth",
*       "oauthAccessToken": "...", "oauthRefreshToken": "...", "expiresAt": 1714000000000 }
*   ]
* }
* ```
*
* - Constructor is non-blocking; the first read triggers a sync load.
* - Writes go through a temp file + `renameSync` so partial writes never
*   leave the JSON corrupted.
*/
var DiskAuthProfileStore = class {
	path;
	cache;
	writeChain = Promise.resolve();
	constructor(filePath) {
		this.path = filePath;
	}
	/** Absolute path to the backing JSON file. */
	get filePath() {
		return this.path;
	}
	getApiKeySync(providerId, profile) {
		const p = this.findProfile(providerId, profile);
		if (!p) return void 0;
		if (typeof p.apiKey === "string" && p.apiKey.length > 0) return p.apiKey;
		if (typeof p.oauthAccessToken === "string" && p.oauthAccessToken.length > 0) return p.oauthAccessToken;
	}
	hasCredentialSync(providerId, profile) {
		return Boolean(this.getApiKeySync(providerId, profile));
	}
	list(providerId) {
		return this.ensureLoaded().profiles.filter((p) => p.provider === providerId).map((p) => ({ ...p }));
	}
	get(providerId, profileId) {
		const p = this.findProfile(providerId, profileId);
		return p ? { ...p } : void 0;
	}
	/**
	* Persist a profile. Uses a serial write chain so concurrent saves cannot
	* race each other on the JSON document.
	*/
	save(profile) {
		const next = this.writeChain.then(() => {
			const file = this.ensureLoaded();
			const idx = file.profiles.findIndex((p) => p.provider === profile.provider && p.profileId === profile.profileId);
			if (idx >= 0) file.profiles[idx] = { ...profile };
			else file.profiles.push({ ...profile });
			if (profile.default) {
				for (const p of file.profiles) if (p.provider === profile.provider && p.profileId !== profile.profileId && p.default) p.default = false;
			}
			this.persist(file);
		});
		this.writeChain = next.catch(() => void 0);
		return next;
	}
	/**
	* Default `refresh()` is a no-op. Vendor-specific OAuth refresh logic
	* (Codex / Anthropic) lives in `src/providers/auth-runtime/oauth.ts` and
	* calls `save()` here once it has a fresh token.
	*/
	async refresh(profile) {
		return profile;
	}
	ensureLoaded() {
		if (this.cache) return this.cache;
		if (!existsSync(this.path)) {
			this.cache = emptyFile();
			return this.cache;
		}
		try {
			const raw = readFileSync(this.path, "utf8");
			const parsed = JSON.parse(raw);
			if (isDiskFile(parsed)) this.cache = {
				version: 1,
				profiles: parsed.profiles.filter(isAuthProfileLike)
			};
			else {
				log.warn({
					path: this.path,
					phase: "load"
				}, "auth-profiles.json has unexpected shape; resetting to empty (file is preserved on disk)");
				this.cache = emptyFile();
			}
		} catch (err) {
			log.warn({
				err,
				path: this.path,
				phase: "load"
			}, `Failed to read auth-profiles.json: ${err instanceof Error ? err.message : String(err)}`);
			this.cache = emptyFile();
		}
		return this.cache;
	}
	findProfile(providerId, profileId) {
		const matches = this.ensureLoaded().profiles.filter((p) => p.provider === providerId);
		if (matches.length === 0) return void 0;
		const wanted = profileId?.trim();
		if (wanted) return matches.find((p) => p.profileId === wanted);
		return matches.find((p) => p.default) ?? matches.find((p) => p.profileId === "default") ?? matches[0];
	}
	persist(file) {
		mkdirSync(dirname(this.path), { recursive: true });
		const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}`;
		writeFileSync(tmp, JSON.stringify(file, null, 2), {
			encoding: "utf8",
			mode: 384
		});
		renameSync(tmp, this.path);
		this.cache = file;
	}
};
function isAuthProfileLike(v) {
	if (!v || typeof v !== "object") return false;
	const o = v;
	return typeof o.provider === "string" && typeof o.profileId === "string" && typeof o.mode === "string";
}
let defaultStore = new NoopAuthProfileStore();
const diskStores = /* @__PURE__ */ new Map();
function getDefaultAuthProfileStore() {
	return defaultStore;
}
/**
* Override the default store. Returns a function that restores the previous
* store; tests typically call it in `afterEach`.
*/
function setDefaultAuthProfileStore(store) {
	const prev = defaultStore;
	defaultStore = store;
	return () => {
		defaultStore = prev;
	};
}
/**
* Get (or create) a {@link DiskAuthProfileStore} for the given agent
* directory. Same `agentDir` always returns the same store instance so the
* in-memory cache stays consistent within one process.
*/
function ensureDiskAuthProfileStore(agentDir) {
	const key = agentDir;
	let store = diskStores.get(key);
	if (!store) {
		store = new DiskAuthProfileStore(join(agentDir, STORE_FILENAME));
		diskStores.set(key, store);
	}
	return store;
}
/**
* Convenience: list every profile for a provider via either an explicit
* store or the default store.
*/
function listProfilesForProvider(store, providerId) {
	return (store ?? getDefaultAuthProfileStore()).list(providerId);
}
//#endregion
export { DiskAuthProfileStore, NoopAuthProfileStore, ensureDiskAuthProfileStore, getDefaultAuthProfileStore, listProfilesForProvider, setDefaultAuthProfileStore };

//# sourceMappingURL=auth-profile-store.js.map