import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { z } from "zod";
import { AgentProviderRuntimeSettingsMapSchema } from "./agent/provider-launch-config.js";
const LogLevelSchema = z.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
const LogFormatSchema = z.enum(["pretty", "json"]);
const LogConfigSchema = z
    .object({
    // Legacy global log settings (kept for backwards compatibility).
    level: LogLevelSchema.optional(),
    format: LogFormatSchema.optional(),
    console: z
        .object({
        level: LogLevelSchema.optional(),
        format: LogFormatSchema.optional(),
    })
        .strict()
        .optional(),
    file: z
        .object({
        level: LogLevelSchema.optional(),
        path: z.string().min(1).optional(),
        rotate: z
            .object({
            maxSize: z.string().min(1).optional(),
            maxFiles: z.number().int().positive().optional(),
        })
            .strict()
            .optional(),
    })
        .strict()
        .optional(),
})
    .strict();
const ProviderCredentialsSchema = z
    .object({
    apiKey: z.string().min(1).optional(),
})
    .strict();
const LocalSpeechProviderSchema = z
    .object({
    modelsDir: z.string().min(1).optional(),
})
    .strict();
const ProvidersSchema = z
    .object({
    openai: ProviderCredentialsSchema.optional(),
    local: LocalSpeechProviderSchema.optional(),
})
    .strict();
const SpeechProviderIdSchema = z
    .string()
    .trim()
    .toLowerCase()
    .pipe(z.enum(["openai", "local"]));
const FeatureDictationSchema = z
    .object({
    enabled: z.boolean().optional(),
    stt: z
        .object({
        provider: SpeechProviderIdSchema.optional(),
        model: z.string().min(1).optional(),
        confidenceThreshold: z.number().optional(),
    })
        .strict()
        .optional(),
})
    .strict();
const FeatureVoiceModeSchema = z
    .object({
    enabled: z.boolean().optional(),
    llm: z
        .object({
        provider: z.string().optional(),
        model: z.string().min(1).optional(),
    })
        .strict()
        .optional(),
    stt: z
        .object({
        provider: SpeechProviderIdSchema.optional(),
        model: z.string().min(1).optional(),
    })
        .strict()
        .optional(),
    turnDetection: z
        .object({
        provider: SpeechProviderIdSchema.optional(),
    })
        .strict()
        .optional(),
    tts: z
        .object({
        provider: SpeechProviderIdSchema.optional(),
        model: z.string().min(1).optional(),
        voice: z.enum(["alloy", "echo", "fable", "onyx", "nova", "shimmer"]).optional(),
        speakerId: z.number().int().optional(),
        speed: z.number().optional(),
    })
        .strict()
        .optional(),
})
    .strict();
export const PersistedConfigSchema = z
    .object({
    // v1 schema marker
    version: z.literal(1).optional(),
    // v1 config layout
    daemon: z
        .object({
        listen: z.string().optional(),
        allowedHosts: z.union([z.literal(true), z.array(z.string())]).optional(),
        mcp: z
            .object({
            enabled: z.boolean().optional(),
        })
            .strict()
            .optional(),
        cors: z
            .object({
            allowedOrigins: z.array(z.string()).optional(),
        })
            .strict()
            .optional(),
        relay: z
            .object({
            enabled: z.boolean().optional(),
            endpoint: z.string().optional(),
            publicEndpoint: z.string().optional(),
        })
            .strict()
            .optional(),
    })
        .strict()
        .optional(),
    app: z
        .object({
        baseUrl: z.string().optional(),
    })
        .strict()
        .optional(),
    providers: ProvidersSchema.optional(),
    agents: z
        .object({
        providers: AgentProviderRuntimeSettingsMapSchema.optional(),
    })
        .strict()
        .optional(),
    features: z
        .object({
        dictation: FeatureDictationSchema.optional(),
        voiceMode: FeatureVoiceModeSchema.optional(),
    })
        .strict()
        .optional(),
    log: LogConfigSchema.optional(),
})
    .strict();
const CONFIG_FILENAME = "config.json";
const DEFAULT_PERSISTED_CONFIG = PersistedConfigSchema.parse({
    version: 1,
    daemon: {
        listen: "127.0.0.1:6767",
        cors: {
            allowedOrigins: ["https://app.paseo.sh"],
        },
        relay: {
            enabled: true,
        },
    },
    app: {
        baseUrl: "https://app.paseo.sh",
    },
});
function getConfigPath(paseoHome) {
    return path.join(paseoHome, CONFIG_FILENAME);
}
function getLogger(logger) {
    return logger?.child({ module: "config" });
}
function stripDeprecatedLocalSpeechConfigFields(parsed) {
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
        return parsed;
    }
    const root = { ...parsed };
    const providers = root.providers;
    if (!providers || typeof providers !== "object" || Array.isArray(providers)) {
        return root;
    }
    const providersRecord = { ...providers };
    const local = providersRecord.local;
    if (!local || typeof local !== "object" || Array.isArray(local)) {
        root.providers = providersRecord;
        return root;
    }
    const localRecord = { ...local };
    if ("autoDownload" in localRecord) {
        delete localRecord.autoDownload;
    }
    providersRecord.local = localRecord;
    root.providers = providersRecord;
    return root;
}
export function loadPersistedConfig(paseoHome, logger) {
    const log = getLogger(logger);
    const configPath = getConfigPath(paseoHome);
    if (!existsSync(configPath)) {
        try {
            mkdirSync(path.dirname(configPath), { recursive: true });
            writeFileSync(configPath, JSON.stringify(DEFAULT_PERSISTED_CONFIG, null, 2) + "\n");
            log?.info(`Initialized config file at ${configPath}`);
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            throw new Error(`[Config] Failed to initialize ${configPath}: ${message}`);
        }
    }
    let raw;
    try {
        raw = readFileSync(configPath, "utf-8");
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`[Config] Failed to read ${configPath}: ${message}`);
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`[Config] Invalid JSON in ${configPath}: ${message}`);
    }
    const migrated = stripDeprecatedLocalSpeechConfigFields(parsed);
    const result = PersistedConfigSchema.safeParse(migrated);
    if (!result.success) {
        const issues = result.error.issues
            .map((i) => `  - ${i.path.join(".")}: ${i.message}`)
            .join("\n");
        throw new Error(`[Config] Invalid config in ${configPath}:\n${issues}`);
    }
    log?.info(`Loaded from ${configPath}`);
    return result.data;
}
export function savePersistedConfig(paseoHome, config, logger) {
    const log = getLogger(logger);
    const configPath = getConfigPath(paseoHome);
    const result = PersistedConfigSchema.safeParse(config);
    if (!result.success) {
        const issues = result.error.issues
            .map((i) => `  - ${i.path.join(".")}: ${i.message}`)
            .join("\n");
        throw new Error(`[Config] Invalid config to save:\n${issues}`);
    }
    try {
        writeFileSync(configPath, JSON.stringify(result.data, null, 2) + "\n");
        log?.info(`Saved to ${configPath}`);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`[Config] Failed to write ${configPath}: ${message}`);
    }
}
//# sourceMappingURL=persisted-config.js.map