/**
 * User-level configuration for Studiograph
 *
 * Stored at ~/.studiograph/user.json — never committed to git.
 * Holds personal credentials and preferences that should not be shared
 * with the team (API keys, preferred model, etc.).
 *
 * This mirrors the pattern established by ~/.studiograph/auth.json
 * for GitHub tokens.
 */
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { createAnthropic } from '@ai-sdk/anthropic';
import { createOpenAI } from '@ai-sdk/openai';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { tryResolve } from './secrets/read-flip.js';
const USER_CONFIG_DIR = join(homedir(), '.studiograph');
export const USER_CONFIG_PATH = join(USER_CONFIG_DIR, 'user.json');
/**
 * Load the user config from ~/.studiograph/user.json.
 * Returns an empty object if the file does not exist or cannot be parsed.
 */
export function loadUserConfig() {
    if (!existsSync(USER_CONFIG_PATH))
        return {};
    try {
        return JSON.parse(readFileSync(USER_CONFIG_PATH, 'utf-8'));
    }
    catch {
        return {};
    }
}
/**
 * Persist the user config to ~/.studiograph/user.json with mode 0600
 * (owner read/write only — keeps API keys off shared file systems).
 *
 * When the encrypted store has been initialized by serve.ts, the
 * registry-mapped fields are mirrored into the store on a best-effort
 * basis (dual-write). The legacy file remains authoritative through
 * Phase 5; Phase 6 flipped the read path. Failures never block the
 * legacy write — see dual-write.ts for the rationale.
 */
export function saveUserConfig(config) {
    mkdirSync(USER_CONFIG_DIR, { recursive: true });
    writeFileSync(USER_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
    // Phase 5 dual-write. No-op when the store was never initialized at boot.
    void mirrorUserConfigToStore(config);
}
/**
 * Lazy require of the dual-write helper. Avoids pulling the secrets
 * store into the import graph for tools that read user-config without
 * a running serve.ts boot path. The dual-write helper itself short-
 * circuits when the store hasn't been initialized.
 */
async function mirrorUserConfigToStore(config) {
    try {
        const { mirrorUserConfig } = await import('./secrets/dual-write.js');
        await mirrorUserConfig(config);
    }
    catch (err) {
        // eslint-disable-next-line no-console
        console.warn(`[secrets] dual-write user.json mirror failed: ${err instanceof Error ? err.message : String(err)}`);
    }
}
/**
 * Save model provider, model ID, and optional API key to user config.
 * Merges with any existing user config so other fields are preserved.
 */
export function setUserModelConfig(provider, modelId, apiKey) {
    const current = loadUserConfig();
    current.model_provider = provider;
    current.model_id = modelId;
    if (apiKey)
        current.api_key = apiKey;
    saveUserConfig(current);
}
/** Normalize a remote URL to a stable lookup key (lowercase origin, no trailing slash). */
function tokenKey(remote) {
    try {
        const u = new URL(remote);
        return `${u.protocol}//${u.host}`.toLowerCase();
    }
    catch {
        return remote.replace(/\/$/, '').toLowerCase();
    }
}
/** Read the user's personal Studiograph token for a given remote, if any. */
export function getStudiographToken(remote) {
    const map = loadUserConfig().studiograph_tokens;
    return map?.[tokenKey(remote)];
}
/** Persist the user's personal Studiograph token for a given remote. */
export function setStudiographToken(remote, token) {
    const current = loadUserConfig();
    current.studiograph_tokens = { ...(current.studiograph_tokens ?? {}), [tokenKey(remote)]: token };
    saveUserConfig(current);
}
/**
 * Mint a personal sg_ API token via POST /api/auth/tokens and persist it to
 * `~/.studiograph/user.json` keyed by remote. Used after a successful
 * email/password login (clone, the interactive resolveRemoteAuth fallback)
 * so subsequent CLI calls go straight to the token-only auth path.
 *
 * Failure is non-fatal: returns false. The caller already holds a usable
 * JWT cookie for the current session; the owner can re-mint later via
 * `studiograph admin token issue` (A4).
 *
 * Exported (not just internal to sync/clone) so future commands and tests
 * exercise the same minted-token shape.
 */
export async function mintAndStoreCliToken(serverUrl, cookieHeader, opts = {}) {
    const fetchFn = opts.fetchFn ?? fetch;
    const name = opts.name ?? `cli-${new URL(serverUrl).hostname}-${new Date().toISOString().slice(0, 10)}`;
    try {
        const res = await fetchFn(`${serverUrl}/api/auth/tokens`, {
            method: 'POST',
            headers: { 'Cookie': cookieHeader, 'Content-Type': 'application/json' },
            body: JSON.stringify({ name }),
        });
        if (!res.ok) {
            return { ok: false, reason: `mint failed: ${res.status} ${res.statusText}` };
        }
        const body = await res.json();
        if (!body.token) {
            return { ok: false, reason: 'mint response missing token field' };
        }
        setStudiographToken(serverUrl, body.token);
        return { ok: true, token: body.token };
    }
    catch (err) {
        return { ok: false, reason: err instanceof Error ? err.message : String(err) };
    }
}
/** Remove the stored token for a remote (e.g. after a 401). */
export function clearStudiographToken(remote) {
    const current = loadUserConfig();
    if (!current.studiograph_tokens)
        return;
    delete current.studiograph_tokens[tokenKey(remote)];
    saveUserConfig(current);
}
/**
 * Resolve the Anthropic credential to pass to the API client.
 * Returns the API key from console.anthropic.com.
 * Note: OAuth tokens are no longer supported per Anthropic's third-party policy.
 */
export function getAnthropicCredential(config) {
    return config.api_key;
}
/** Primary env var name for each supported provider (user-facing, simple) */
const PROVIDER_ENV_KEYS = {
    anthropic: 'ANTHROPIC_API_KEY',
    openai: 'OPENAI_API_KEY',
    google: 'GOOGLE_API_KEY',
    xai: 'XAI_API_KEY',
    groq: 'GROQ_API_KEY',
    openrouter: 'OPENROUTER_API_KEY',
};
/** Additional env var names that SDKs read internally (aliases to also propagate) */
const PROVIDER_ENV_ALIASES = {
    google: ['GOOGLE_GENERATIVE_AI_API_KEY'],
};
/**
 * Resolve the model provider.
 *
 * Phase 6 read-flip: try the encrypted store first via the resolver
 * (env > store > default). The legacy chain (workspace config → user
 * config → env → default) is the transitional fallback for when the
 * importer hasn't run yet OR when a workspace config carries an
 * explicit override that the store doesn't know about.
 *
 * Note: `workspaceConfig` here is the LLM-config shape that callers
 * pass through, not the secrets-store workspace scope. We keep it as
 * a fallback because workspace.json may legitimately carry
 * `model_provider`/`model_id` overrides for now (until Phase 9 routes
 * those through the schema validator).
 */
export function resolveProvider(userConfig, workspaceConfig) {
    const fromStore = tryResolve('model.provider');
    if (fromStore)
        return fromStore;
    return workspaceConfig?.model_provider || userConfig.model_provider || process.env.MODEL_PROVIDER || 'anthropic';
}
/**
 * Resolve the model ID.
 *
 * Same Phase 6 ordering as resolveProvider — store first, legacy
 * chain as transitional fallback.
 */
export function resolveModelId(userConfig, workspaceConfig) {
    const fromStore = tryResolve('model.id');
    if (fromStore)
        return fromStore;
    return workspaceConfig?.model_id || userConfig.model_id || process.env.MODEL_ID || 'claude-sonnet-4-5-20250929';
}
/**
 * Resolve the API key for the active model provider.
 *
 * Read order:
 *   1. Per-provider store slot — `model.<provider>.api_key`
 *   2. workspace.json → user.json (legacy file chain — local-dev only)
 *   3. env: MODEL_API_KEY → provider-specific → SDK aliases
 *
 * The legacy generic `model.api_key` store slot was decommissioned in
 * Phase 12c (2026-05-12) along with the importer that copied it into
 * per-provider slots. New writes always go through the per-provider
 * surface (UI + CLI).
 */
export function resolveApiKey(userConfig, workspaceConfig) {
    const provider = resolveProvider(userConfig, workspaceConfig);
    const perProviderKey = `model.${provider}.api_key`;
    const fromPerProviderStore = tryResolve(perProviderKey);
    if (fromPerProviderStore)
        return fromPerProviderStore;
    if (workspaceConfig?.api_key)
        return workspaceConfig.api_key;
    if (userConfig.api_key)
        return userConfig.api_key;
    // Fall back to env vars: provider-specific (ANTHROPIC_API_KEY etc.)
    // → SDK aliases. The generic MODEL_API_KEY env was retired in Phase
    // 12c (2026-05-12) alongside the legacy `model.api_key` registry slot.
    const envKey = PROVIDER_ENV_KEYS[provider] || `${provider.toUpperCase()}_API_KEY`;
    if (process.env[envKey])
        return process.env[envKey];
    for (const alias of PROVIDER_ENV_ALIASES[provider] ?? []) {
        if (process.env[alias])
            return process.env[alias];
    }
    return '';
}
/**
 * Build a Vercel AI SDK model for the given provider/model/key combination.
 * Single factory shared by background generation surfaces that don't go
 * through the agent orchestrator.
 */
export function createModel(provider, modelId, apiKey) {
    switch (provider) {
        case 'anthropic':
            return createAnthropic({ apiKey })(modelId);
        case 'openai':
            return createOpenAI({ apiKey })(modelId);
        case 'google':
            return createGoogleGenerativeAI({ apiKey })(modelId);
        case 'openrouter':
            return createOpenAI({ apiKey, baseURL: 'https://openrouter.ai/api/v1' })(modelId);
        case 'xai':
            return createOpenAI({ apiKey, baseURL: 'https://api.x.ai/v1' })(modelId);
        case 'groq':
            return createOpenAI({ apiKey, baseURL: 'https://api.groq.com/openai/v1' })(modelId);
        default:
            return createOpenAI({ apiKey })(modelId);
    }
}
/**
 * Ensure the resolved API key is available in the provider-specific env var.
 * Used by serve.ts so the orchestrator (which reads provider env vars) can find it.
 */
export function propagateApiKeyToEnv(userConfig, workspaceConfig) {
    const apiKey = resolveApiKey(userConfig, workspaceConfig);
    if (!apiKey)
        return;
    const provider = resolveProvider(userConfig, workspaceConfig);
    const envVar = PROVIDER_ENV_KEYS[provider] || `${provider.toUpperCase()}_API_KEY`;
    if (!process.env[envVar])
        process.env[envVar] = apiKey;
    // Also set SDK-specific aliases so third-party libraries find the key
    for (const alias of PROVIDER_ENV_ALIASES[provider] ?? []) {
        if (!process.env[alias])
            process.env[alias] = apiKey;
    }
}
//# sourceMappingURL=user-config.js.map