import { existsSync, mkdirSync } from 'node:fs';
import { chmod, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { basename, dirname, extname, join, normalize, resolve } from 'node:path';
import { StructuredError } from '@agentuity/core';
import { BuildMetadataSchema, getServiceUrls, APIClient as ServerAPIClient, } from '@agentuity/server';
import { YAML } from 'bun';
import { parseJSONC } from './utils/jsonc';
import { z } from 'zod';
import { clearProfileCache } from './cache';
import { getCatalystUrl } from './catalyst';
import { readEnvFile, writeEnvFile } from './env-util';
import { ErrorCode } from './errors';
import { deleteAuthFromKeychain, getAuthFromKeychain, isMacOS, saveAuthToKeychain, } from './keychain';
import * as tui from './tui';
import { ConfigSchema, ProjectSchema } from './types';
export const defaultProfileName = 'production';
export function getDefaultConfigDir() {
    const configDirOverride = process.env.AGENTUITY_CONFIG_DIR?.trim();
    if (configDirOverride) {
        if (configDirOverride.startsWith('~/')) {
            return resolve(join(homedir(), configDirOverride.slice(2)));
        }
        return resolve(configDirOverride);
    }
    return join(homedir(), '.config', 'agentuity');
}
export function getDefaultConfigPath() {
    return join(getDefaultConfigDir(), defaultProfileName + '.yaml');
}
export function getProfilePath() {
    return join(getDefaultConfigDir(), 'profile');
}
export async function ensureConfigDir() {
    const dir = getDefaultConfigDir();
    try {
        await mkdir(dir, { recursive: true, mode: 0o700 });
    }
    catch {
        // Ignore if already exists
    }
}
export async function saveProfile(path) {
    await ensureConfigDir();
    await writeFile(getProfilePath(), path, { mode: 0o600 });
}
export async function getProfile(profileFromFlag) {
    // Check --profile flag first (highest priority)
    if (profileFromFlag) {
        const flagProfilePath = join(getDefaultConfigDir(), `${profileFromFlag}.yaml`);
        const flagFile = Bun.file(flagProfilePath);
        if (await flagFile.exists()) {
            return flagProfilePath;
        }
        // If --profile flag was explicitly provided but file doesn't exist, throw an error
        throw new Error(`Profile '${profileFromFlag}' not found. Expected file at: ${flagProfilePath}`);
    }
    // Check environment variable second
    if (process.env.AGENTUITY_PROFILE) {
        const profileName = process.env.AGENTUITY_PROFILE;
        const envProfilePath = join(getDefaultConfigDir(), `${profileName}.yaml`);
        const envFile = Bun.file(envProfilePath);
        if (await envFile.exists()) {
            return envProfilePath;
        }
    }
    const profilePath = getProfilePath();
    const defaultConfigPath = getDefaultConfigPath();
    try {
        const file = Bun.file(profilePath);
        if (await file.exists()) {
            const content = await file.text();
            const savedPath = content.trim();
            const savedFile = Bun.file(savedPath);
            if (await savedFile.exists()) {
                return savedPath;
            }
        }
    }
    catch {
        // Fall back to default
    }
    return defaultConfigPath;
}
export async function fetchProfiles() {
    const configDir = getDefaultConfigDir();
    const currentConfigPath = await getProfile();
    const profiles = [];
    const nameRegex = /\bname:\s+["']?([\w-_]+)["']?/;
    try {
        const entries = await readdir(configDir);
        for (const entry of entries) {
            if (extname(entry) === '.yaml' && !entry.includes('templates/')) {
                const filePath = join(configDir, entry);
                try {
                    const content = await readFile(filePath, 'utf-8');
                    const match = nameRegex.exec(content);
                    if (match?.[1]) {
                        profiles.push({
                            name: match[1],
                            filename: filePath,
                            selected: filePath === currentConfigPath,
                        });
                    }
                }
                catch {
                    // Skip files we can't read
                }
            }
        }
    }
    catch {
        // Directory doesn't exist or can't be read
    }
    return profiles;
}
function expandTilde(path) {
    if (path.startsWith('~/')) {
        return join(homedir(), path.slice(2));
    }
    return path;
}
let cachedConfig;
// Track the resolved config path so saveConfig writes back to the same file
let cachedConfigPath;
const loadedProjectConfigPaths = new Map();
export function resetConfigCache() {
    cachedConfig = undefined;
    cachedConfigPath = undefined;
    loadedProjectConfigPaths.clear();
}
export async function loadConfig(customPath, skipCache = false, profileFromFlag) {
    const configPath = customPath ? expandTilde(customPath) : await getProfile(profileFromFlag);
    // Use cache if available and not skipped, but only if the path matches
    if (!skipCache && cachedConfig !== undefined && cachedConfigPath === configPath) {
        return cachedConfig;
    }
    try {
        const file = Bun.file(configPath);
        const exists = await file.exists();
        let result;
        if (exists) {
            const content = await file.text();
            const config = YAML.parse(content);
            // check to see if this is a legacy config file that might not have the required name
            // and in this case we can just use the filename
            const _config = config;
            if (!_config.name) {
                _config.name = basename(configPath).replace(extname(configPath), '');
            }
            result = ConfigSchema.safeParse(config);
        }
        else {
            result = ConfigSchema.safeParse({ name: defaultProfileName });
        }
        if (!result.success) {
            tui.error(`Invalid config in ${configPath}:`);
            for (const issue of result.error.issues) {
                const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
                tui.bullet(`${path}: ${issue.message}`);
            }
            process.exit(1);
        }
        // allow environment variables to override
        const overrides = result.data.overrides ?? ConfigSchema.shape.overrides.parse({});
        if (overrides) {
            if (process.env.AGENTUITY_API_URL) {
                overrides.api_url = process.env.AGENTUITY_API_URL;
            }
            if (process.env.AGENTUITY_APP_URL) {
                overrides.app_url = process.env.AGENTUITY_APP_URL;
            }
            if (process.env.AGENTUITY_CATALYST_URL) {
                overrides.catalyst_url = process.env.AGENTUITY_CATALYST_URL;
            }
            if (process.env.AGENTUITY_TRANSPORT_URL) {
                overrides.transport_url = process.env.AGENTUITY_TRANSPORT_URL;
            }
            if (process.env.AGENTUITY_KEYVALUE_URL) {
                overrides.kv_url = process.env.AGENTUITY_KEYVALUE_URL;
            }
            if (process.env.AGENTUITY_SANDBOX_URL) {
                overrides.sandbox_url = process.env.AGENTUITY_SANDBOX_URL;
            }
            if (process.env.AGENTUITY_VECTOR_URL) {
                overrides.vector_url = process.env.AGENTUITY_VECTOR_URL;
            }
            if (process.env.AGENTUITY_AIGATEWAY_URL) {
                overrides.aigateway_url = process.env.AGENTUITY_AIGATEWAY_URL;
            }
            if (process.env.AGENTUITY_STREAM_URL) {
                overrides.stream_url = process.env.AGENTUITY_STREAM_URL;
            }
            result.data.overrides = overrides;
        }
        // Cache the loaded config (whether default or custom path)
        // This ensures --config flag is respected across all commands
        if (!skipCache) {
            cachedConfig = result.data;
            cachedConfigPath = configPath;
        }
        return result.data;
    }
    catch (error) {
        tui.error(`Error loading config from ${configPath}: ${error}`);
        // Cache null on error to prevent retry attempts.
        // This is acceptable for CLI context where process typically exits on config errors.
        // Note: For long-running processes, consider time-based cache expiry for transient failures.
        if (!skipCache) {
            cachedConfig = null;
            cachedConfigPath = configPath;
        }
        return null;
    }
}
function formatYAML(obj, indent = 0) {
    const spaces = '    '.repeat(indent);
    const lines = [];
    if (typeof obj !== 'object' || obj === null) {
        return String(obj);
    }
    for (const [key, value] of Object.entries(obj)) {
        if (value === null || value === undefined) {
            continue;
        }
        if (typeof value === 'object' && !Array.isArray(value)) {
            lines.push(`${spaces}${key}:`);
            lines.push(formatYAML(value, indent + 1));
        }
        else if (Array.isArray(value)) {
            lines.push(`${spaces}${key}:`);
            for (const item of value) {
                if (typeof item === 'object') {
                    lines.push(`${spaces}    -`);
                    lines.push(formatYAML(item, indent + 2));
                }
                else {
                    lines.push(`${spaces}    - ${item}`);
                }
            }
        }
        else if (typeof value === 'string') {
            if (value === '') {
                lines.push(`${spaces}${key}: ""`);
            }
            else if (value.includes(':') ||
                value.includes('#') ||
                value.includes(' ') ||
                value.includes('\\')) {
                // Use single quotes to avoid YAML escape-sequence processing.
                // Double-quoted YAML strings interpret backslash sequences (\n, \t, etc.),
                // which breaks Windows paths like C:\Users\... where \U would be invalid.
                // Single-quoted strings treat backslashes literally.
                // Escape any embedded single quotes by doubling them (YAML spec).
                const escaped = value.replace(/'/g, "''");
                lines.push(`${spaces}${key}: '${escaped}'`);
            }
            else {
                lines.push(`${spaces}${key}: ${value}`);
            }
        }
        else {
            lines.push(`${spaces}${key}: ${value}`);
        }
    }
    return lines.join('\n');
}
export async function saveConfig(config, customPath) {
    // Use the path the config was originally loaded from (cachedConfigPath) so that
    // saves go back to the correct profile even when --profile was used to load it.
    // Falls back to getProfile() if no config has been loaded yet.
    const configPath = customPath || cachedConfigPath || (await getProfile());
    await ensureConfigDir();
    const content = formatYAML(config);
    await writeFile(configPath, content + '\n', { mode: 0o600 });
    // Ensure existing files get correct permissions on upgrade
    await chmod(configPath, 0o600);
    // Only cache the default profile, not custom path saves.
    // Note: This creates potential cache staleness - if a custom path is saved then later
    // loaded without skipCache, it will use the cached default profile instead.
    // Consider clearing cache on custom path saves: if (customPath) { cachedConfig = undefined; }
    if (!customPath) {
        cachedConfig = config;
    }
}
export async function getOrInitConfig() {
    const config = await loadConfig();
    if (config) {
        return config;
    }
    const profilePath = await getProfile();
    const name = basename(profilePath, '.yaml');
    return { name };
}
export async function saveAuth(auth) {
    const config = await getOrInitConfig();
    const profileName = config.name || defaultProfileName;
    const authData = {
        api_key: auth.apiKey,
        user_id: auth.userId,
        expires: auth.expires.getTime(),
    };
    // On macOS, store in Keychain for better security
    if (isMacOS()) {
        try {
            await saveAuthToKeychain(profileName, authData);
            // Successfully stored in keychain, remove auth from config if present
            // but always save config to ensure profile file exists
            if (config.auth) {
                delete config.auth;
            }
            await saveConfig(config);
            return;
        }
        catch (error) {
            // Keychain failed, fall back to config file
            tui.warning(`Failed to store auth in keychain, falling back to config file: ${error}`);
        }
    }
    // Store in config file (non-macOS or keychain failed)
    config.auth = authData;
    config.preferences = config.preferences || {};
    config.preferences.orgId = '';
    await saveConfig(config);
}
export async function clearAuth() {
    const config = await getOrInitConfig();
    const profileName = config.name || defaultProfileName;
    // On macOS, clear from Keychain
    if (isMacOS()) {
        try {
            await deleteAuthFromKeychain(profileName);
        }
        catch {
            // Ignore errors - keychain entry may not exist
        }
    }
    // Clear resource cache for this profile
    await clearProfileCache(profileName);
    // Also clear from config file (for backwards compatibility)
    if (config.auth) {
        delete config.auth;
        config.preferences = config.preferences || {};
        config.preferences.orgId = '';
        await saveConfig(config);
    }
}
export async function saveProjectDir(projectDir) {
    const config = await getOrInitConfig();
    config.preferences = config.preferences || {};
    const normalized = resolve(normalize(projectDir));
    config.preferences.project_dir = normalized;
    await saveConfig(config);
}
export async function saveOrgId(orgId) {
    const config = await getOrInitConfig();
    config.preferences = config.preferences || {};
    config.preferences.orgId = orgId;
    await saveConfig(config);
}
export async function saveProjectId(projectId) {
    const config = await getOrInitConfig();
    config.preferences = config.preferences || {};
    config.preferences.projectId = projectId;
    await saveConfig(config);
}
export async function clearOrgId() {
    const config = await getOrInitConfig();
    if (config.preferences) {
        delete config.preferences.orgId;
        await saveConfig(config);
    }
}
export async function clearProjectId() {
    const config = await getOrInitConfig();
    if (config.preferences) {
        delete config.preferences.projectId;
        await saveConfig(config);
    }
}
export async function saveRegion(region) {
    const config = await getOrInitConfig();
    config.preferences = config.preferences || {};
    config.preferences.region = region;
    await saveConfig(config);
}
export async function clearRegion() {
    const config = await getOrInitConfig();
    if (config.preferences) {
        delete config.preferences.region;
        await saveConfig(config);
    }
}
export async function getAuth() {
    const config = await loadConfig();
    const profileName = config?.name || defaultProfileName;
    // Priority 1: Allow automated login from environment variables
    if (process.env.AGENTUITY_CLI_API_KEY && process.env.AGENTUITY_USER_ID) {
        return {
            apiKey: process.env.AGENTUITY_CLI_API_KEY,
            userId: process.env.AGENTUITY_USER_ID,
            expires: new Date(Date.now() + 30 * 60_000),
        };
    }
    // Priority 1a: Allow automated login from environment variables (this is set in deployment)
    if (process.env.AGENTUITY_API_KEY) {
        return {
            apiKey: process.env.AGENTUITY_API_KEY,
            userId: '',
            expires: new Date(Date.now() + 30 * 60_000),
        };
    }
    // Priority 2: On macOS, try to read from Keychain
    if (isMacOS()) {
        try {
            const keychainAuth = await getAuthFromKeychain(profileName);
            if (keychainAuth) {
                // If there's auth api_key in the config file, remove it since we have it in keychain
                if (config?.auth?.api_key) {
                    const { api_key: _, ...authWithoutApiKey } = config.auth;
                    const configCopy = { ...config, auth: authWithoutApiKey };
                    cachedConfig = null; // Force cache refresh
                    await saveConfig(configCopy);
                }
                return {
                    apiKey: keychainAuth.api_key,
                    userId: keychainAuth.user_id,
                    expires: new Date(keychainAuth.expires),
                };
            }
        }
        catch {
            // Keychain read failed, fall through to config file
        }
    }
    // Priority 3: Read from config file (non-macOS or keychain failed)
    if (!config)
        return null;
    const auth = config.auth;
    if (!auth || !auth.api_key || !auth.user_id) {
        return null;
    }
    const expiresDate = new Date(auth.expires || 0);
    return {
        apiKey: auth.api_key,
        userId: auth.user_id,
        expires: expiresDate,
    };
}
function getSchemaDescription(schema) {
    return schema.description;
}
function getPlaceholderValue(schema) {
    // Unwrap optional to get to the inner type
    let unwrapped = schema;
    if (schema instanceof z.ZodOptional) {
        unwrapped = schema._def.innerType;
    }
    // Check the type using constructor name
    const typeName = unwrapped.constructor.name;
    switch (typeName) {
        case 'ZodString':
            return '""';
        case 'ZodNumber':
            return '0';
        case 'ZodBoolean':
            return 'false';
        default:
            return '""';
    }
}
export function generateYAMLTemplate(name) {
    const lines = [];
    // Add name (required)
    lines.push(`name: "${name}"`);
    lines.push('');
    // Get schema shape
    const shape = ConfigSchema.shape;
    // Only include user-configurable sections
    // Skip: auth (managed by login), devmode (internal), preferences (managed by CLI)
    const userConfigurableSections = ['overrides'];
    // Process each top-level field
    for (const [key, value] of Object.entries(shape)) {
        if (key === 'name')
            continue;
        if (!userConfigurableSections.includes(key))
            continue;
        const schema = value;
        // Unwrap optional and nullable to get to the inner schema
        // Note: .optional().nullable() creates ZodNullable(ZodOptional(ZodObject))
        let innerSchema = schema;
        if (innerSchema instanceof z.ZodNullable) {
            innerSchema = innerSchema._def.innerType;
        }
        if (innerSchema instanceof z.ZodOptional) {
            innerSchema = innerSchema._def.innerType;
        }
        const description = getSchemaDescription(schema);
        // Add section comment
        if (description) {
            lines.push(`# ${description}`);
        }
        // For object schemas, expand their properties
        if (innerSchema instanceof z.ZodObject) {
            const innerShape = innerSchema.shape;
            lines.push(`# ${key}:`);
            for (const [subKey, subValue] of Object.entries(innerShape)) {
                const subSchema = subValue;
                const subDesc = getSchemaDescription(subSchema);
                const placeholder = getPlaceholderValue(subSchema);
                if (subDesc) {
                    lines.push(`#     ${subKey}: ${placeholder}  # ${subDesc}`);
                }
                else {
                    lines.push(`#     ${subKey}: ${placeholder}`);
                }
            }
        }
        else {
            const placeholder = getPlaceholderValue(schema);
            lines.push(`# ${key}: ${placeholder}`);
        }
        lines.push('');
    }
    return lines.join('\n');
}
export const ProjectConfigNotFoundException = StructuredError('ProjectConfigNotFoundException')();
export async function resolveProjectConfigPaths(path, config) {
    const resolvedPath = resolve(expandTilde(path));
    const pathStats = await stat(resolvedPath).catch(() => null);
    const isExplicitJsonFile = (pathStats?.isFile() || pathStats === null) && extname(resolvedPath) === '.json';
    if (isExplicitJsonFile) {
        return {
            projectDir: dirname(resolvedPath),
            configPath: resolvedPath,
            explicitConfigFile: true,
        };
    }
    const projectDir = resolvedPath;
    let configPath = join(projectDir, 'agentuity.json');
    if (config?.name) {
        const profileConfigPath = join(projectDir, `agentuity.${config.name}.json`);
        if (await Bun.file(profileConfigPath).exists()) {
            configPath = profileConfigPath;
        }
    }
    return {
        projectDir,
        configPath,
        explicitConfigFile: false,
    };
}
export async function loadProjectConfig(dir, config) {
    const { projectDir, configPath, explicitConfigFile } = await resolveProjectConfigPaths(dir, config);
    const file = Bun.file(configPath);
    if (!(await file.exists())) {
        // TODO: check to see if a valid project that was created unauthenticated
        // and then if so:
        // 1. if authentication, offer to import the project
        // 2. tell them that they need to login to use the command and import the project
        throw new ProjectConfigNotFoundException({
            message: explicitConfigFile
                ? `Project config not found at ${configPath}`
                : 'project config not found',
            configPath,
            explicit: explicitConfigFile,
        });
    }
    const text = await file.text();
    const parsedConfig = parseJSONC(text);
    const result = ProjectSchema.safeParse(parsedConfig);
    if (!result.success) {
        tui.error(`Invalid project config at ${configPath}:`);
        for (const issue of result.error.issues) {
            const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
            tui.bullet(`${path}: ${issue.message}`);
        }
        process.exit(1);
    }
    loadedProjectConfigPaths.set(projectDir, configPath);
    return result.data;
}
export const InitialProjectConfigSchema = z.intersection(ProjectSchema, z.object({
    sdkKey: z.string().describe('the project specific SDK key'),
    $schema: z.string().optional(),
}));
export async function createProjectConfig(dir, config) {
    const { sdkKey, skipGitSetup: _skipGitSetup, ...sanitizedConfig } = config;
    // generate the project config
    const configPath = join(dir, 'agentuity.json');
    const configData = {
        $schema: 'https://agentuity.dev/schema/cli/v1/agentuity.json',
        ...sanitizedConfig,
        deployment: {
            ...sanitizedConfig.deployment,
            resources: {
                memory: '500Mi',
                cpu: '500m',
                disk: '500Mi',
                ...sanitizedConfig.deployment?.resources,
            },
            domains: sanitizedConfig.deployment?.domains ?? [],
        },
    };
    await Bun.write(configPath, JSON.stringify(configData, null, 2) + '\n');
    // generate or update the .env file with SDK key
    const envPath = join(dir, '.env');
    const envFile = Bun.file(envPath);
    if (await envFile.exists()) {
        // Preserve existing .env content, just update SDK key
        const existing = await readEnvFile(envPath);
        existing.AGENTUITY_SDK_KEY = sdkKey;
        await writeEnvFile(envPath, existing, { preserveExisting: false });
    }
    else {
        const comment = '# AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
        const content = `${comment}\nAGENTUITY_SDK_KEY=${sdkKey}\n`;
        await Bun.write(envPath, content);
    }
    await chmod(envPath, 0o600);
    // generate the vscode settings (only if they don't already exist)
    const vscodeDir = join(dir, '.vscode');
    const settingsPath = join(vscodeDir, 'settings.json');
    if (!(await Bun.file(settingsPath).exists())) {
        mkdirSync(vscodeDir, { recursive: true });
        const settings = {
            'search.exclude': {
                '**/.git/**': true,
                '**/node_modules/**': true,
                '**/bun.lock': true,
                '**/.agentuity/**': true,
            },
            'json.schemas': [
                {
                    fileMatch: ['agentuity.json'],
                    url: 'https://agentuity.dev/schema/cli/v1/agentuity.json',
                },
            ],
        };
        await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
    }
}
export async function updateProjectConfig(dir, updates, config) {
    const resolved = await resolveProjectConfigPaths(dir, config);
    const configPath = resolved.explicitConfigFile
        ? resolved.configPath
        : (loadedProjectConfigPaths.get(resolved.projectDir) ?? resolved.configPath);
    const file = Bun.file(configPath);
    if (!(await file.exists())) {
        throw new ProjectConfigNotFoundException({
            code: ErrorCode.PROJECT_NOT_FOUND,
            message: `Project config not found at ${configPath}`,
            configPath: resolved.configPath,
            explicit: resolved.explicitConfigFile,
        });
    }
    const text = await file.text();
    const existing = parseJSONC(text);
    const updated = { ...existing, ...updates };
    const result = ProjectSchema.safeParse(updated);
    if (!result.success) {
        const issues = result.error.issues
            .map((i) => `${i.path.join('.') || 'root'}: ${i.message}`)
            .join(', ');
        throw new Error(`Invalid project config after update: ${issues}`);
    }
    await Bun.write(configPath, JSON.stringify(updated, null, 2) + '\n');
}
const BuildMetadataNotFoundError = StructuredError('BuildMetadataNotFoundError');
export async function loadBuildMetadata(dir) {
    const filename = join(dir, 'agentuity.metadata.json');
    const file = Bun.file(filename);
    if (!(await file.exists())) {
        throw new BuildMetadataNotFoundError({ message: `couldn't find ${filename}` });
    }
    const buffer = await file.text();
    const config = JSON.parse(buffer);
    const result = BuildMetadataSchema.safeParse(config);
    if (!result.success) {
        tui.error(`Invalid build metadata at ${filename}:`);
        for (const issue of result.error.issues) {
            const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
            tui.bullet(`${path}: ${issue.message}`);
        }
        process.exit(1);
    }
    return result.data;
}
export async function loadProjectSDKKey(logger, projectDir) {
    const c = await getOrInitConfig();
    const files = process.env.NODE_ENV === 'production' || c?.name !== 'local'
        ? ['.env', '.env.production']
        : ['.env.development', '.env'];
    if (c) {
        files.unshift(`.env.${c.name}`);
    }
    logger.trace(`[SDK_KEY] Searching for AGENTUITY_SDK_KEY in files: ${files.join(', ')}`);
    logger.trace(`[SDK_KEY] Project directory: ${projectDir}`);
    logger.trace(`[SDK_KEY] NODE_ENV: ${process.env.NODE_ENV}`);
    for (const filename of files) {
        const fn = join(projectDir, filename);
        logger.trace(`[SDK_KEY] Checking file: ${fn}`);
        if (existsSync(fn)) {
            logger.trace(`[SDK_KEY] File exists: ${fn}`);
            const buf = await Bun.file(fn).text();
            const tok = buf.split(/\n/);
            for (const t of tok) {
                if (t.charAt(0) !== '#' && t.startsWith('AGENTUITY_SDK_KEY=')) {
                    const i = t.indexOf('=');
                    const key = t.substring(i + 1).trim();
                    logger.trace(`[SDK_KEY] Found AGENTUITY_SDK_KEY in: ${fn}`);
                    logger.trace(`[SDK_KEY] Key value: ${key.substring(0, 10)}...`);
                    return key;
                }
            }
            logger.trace(`[SDK_KEY] No AGENTUITY_SDK_KEY found in: ${fn}`);
        }
        else {
            logger.trace(`[SDK_KEY] File does not exist: ${fn}`);
        }
    }
    logger.trace('[SDK_KEY] AGENTUITY_SDK_KEY not found in any file');
}
export function getCatalystAPIClient(logger, auth, region, orgId, config) {
    const catalystUrl = getCatalystUrl(region, config?.overrides);
    const headers = {};
    if (orgId) {
        headers['x-agentuity-orgid'] = orgId;
    }
    return new ServerAPIClient(catalystUrl, logger, auth.apiKey, { headers });
}
/**
 * Get the default region using priority ordering:
 * 1. AGENTUITY_REGION environment variable
 * 2. 'local' for local profile
 * 3. Saved region preference (config.preferences.region)
 * 4. First entry in region-{profile}.json (nearest region, sorted by distance)
 * 5. 'usc' fallback
 *
 * Used for API calls that can hit any Catalyst instance (global database operations).
 * Note: This is NOT called when --region flag is provided (handled at command level).
 */
export async function getDefaultRegion(profileName = 'production', config) {
    // 1. Check environment variable first
    if (process.env.AGENTUITY_REGION) {
        return process.env.AGENTUITY_REGION;
    }
    // 2. Local profile always uses 'local' region
    if (profileName === 'local') {
        return 'local';
    }
    // 3. Check saved region preference
    if (config?.preferences?.region) {
        return config.preferences.region;
    }
    // 4. Check cached regions file (sorted by distance)
    try {
        const cachePath = join(getDefaultConfigDir(), `regions-${profileName}.json`);
        const file = Bun.file(cachePath);
        if (await file.exists()) {
            const data = await file.json();
            const firstRegion = data.regions?.[0];
            if (firstRegion) {
                return firstRegion.region;
            }
        }
    }
    catch {
        // Fall through to default
    }
    // 5. Final fallback
    return 'usc';
}
/**
 * Get a Catalyst API client for global database operations.
 * Uses the default region since the admin DB is global.
 *
 * @param logger - Logger instance
 * @param auth - Authentication data
 * @param profileName - Profile name (default: 'production')
 * @param orgId - Optional organization ID for CLI key authentication
 * @param config - Optional config for region preference lookup
 */
export async function getGlobalCatalystAPIClient(logger, auth, profileName = 'production', orgId, config) {
    const region = await getDefaultRegion(profileName, config);
    return getCatalystAPIClient(logger, auth, region, orgId, config);
}
export function getIONHost(config, region) {
    if (config?.overrides?.ion_url) {
        const url = new URL(config.overrides.ion_url);
        return url.hostname;
    }
    if (config?.name === 'local' || region === 'local') {
        return 'ion.agentuity.io';
    }
    // Validate region is a non-empty string to prevent malformed hostnames
    if (!region || typeof region !== 'string' || region.trim() === '') {
        throw new Error(`Invalid region: '${region}'. Region must be a non-empty string. Use --region flag to specify a valid region.`);
    }
    return `ion-${region}.agentuity.cloud`;
}
export function getStreamURL(region, config) {
    if (config?.name === 'local') {
        return 'https://streams.agentuity.io';
    }
    const serviceUrls = getServiceUrls(region);
    return serviceUrls.stream;
}
//# sourceMappingURL=config.js.map