import { join } from "path";
import { getAgentConfigPaths, getAgentDir, getCodexFastModeEnvironmentSettings, getProjectConfigPaths, } from "../config.js";
import { parseJsonFileContent } from "../utils/json.js";
import { parseContextWindowValue, validateContextWindowValue } from "./context-window.js";
import { deepMergeSettings } from "./settings-merge.js";
import { FileSettingsStorage, InMemorySettingsStorage } from "./settings-storage.js";
export class SettingsManager {
    constructor(storage, initialGlobal, initialProject, globalLoadError = null, projectLoadError = null, initialErrors = [], projectTrusted = true) {
        this.modifiedFields = new Set(); // Track global fields modified during session
        this.modifiedNestedFields = new Map(); // Track global nested field modifications
        this.modifiedProjectFields = new Set(); // Track project fields modified during session
        this.modifiedProjectNestedFields = new Map(); // Track project nested field modifications
        this.globalSettingsLoadError = null; // Track if global settings file had parse errors
        this.projectSettingsLoadError = null; // Track if project settings file had parse errors
        this.writeQueue = Promise.resolve();
        this.storage = storage;
        this.globalSettings = initialGlobal;
        this.projectSettings = initialProject;
        this.projectTrusted = projectTrusted;
        this.globalSettingsLoadError = globalLoadError;
        this.projectSettingsLoadError = projectLoadError;
        this.errors = [...initialErrors];
        this.runtimeSettingsOverrides = SettingsManager.getRuntimeSettingsOverrides();
        this.settings = this.mergeEffectiveSettings();
        this.touchSplitAccessorMethods();
    }
    static getRuntimeSettingsOverrides() {
        const codexFastMode = getCodexFastModeEnvironmentSettings();
        return codexFastMode ? { codexFastMode } : {};
    }
    mergeEffectiveSettings() {
        return deepMergeSettings(deepMergeSettings(this.globalSettings, this.projectSettings), this.runtimeSettingsOverrides);
    }
    /** Create a SettingsManager that loads from files */
    static create(cwd, agentDir = getAgentDir(), options = {}) {
        const storage = new FileSettingsStorage(cwd, agentDir, {
            globalReadPaths: agentDir === getAgentDir() ? getAgentConfigPaths("settings.json") : [join(agentDir, "settings.json")],
            projectReadPaths: getProjectConfigPaths(cwd, "settings.json"),
        });
        return SettingsManager.fromStorage(storage, options);
    }
    /** Create a SettingsManager from an arbitrary storage backend */
    static fromStorage(storage, options = {}) {
        const projectTrusted = options.projectTrusted ?? true;
        const globalLoad = SettingsManager.tryLoadFromStorage(storage, "global");
        const projectLoad = SettingsManager.tryLoadFromStorage(storage, "project", projectTrusted);
        const initialErrors = [];
        if (globalLoad.error) {
            initialErrors.push({ scope: "global", error: globalLoad.error });
        }
        else {
            initialErrors.push(...SettingsManager.validateLoadedSettings("global", globalLoad.settings));
        }
        if (projectLoad.error) {
            initialErrors.push({ scope: "project", error: projectLoad.error });
        }
        else {
            initialErrors.push(...SettingsManager.validateLoadedSettings("project", projectLoad.settings));
        }
        return new SettingsManager(storage, globalLoad.settings, projectLoad.settings, globalLoad.error, projectLoad.error, initialErrors, projectTrusted);
    }
    /** Create an in-memory SettingsManager (no file I/O) */
    static inMemory(settings = {}) {
        const storage = new InMemorySettingsStorage();
        const initialSettings = SettingsManager.migrateSettings(structuredClone(settings));
        storage.withLock("global", () => JSON.stringify(initialSettings, null, 2));
        return SettingsManager.fromStorage(storage);
    }
    static loadFromStorage(storage, scope, projectTrusted = true) {
        if (scope === "project" && !projectTrusted) {
            return {};
        }
        let content;
        storage.withLock(scope, (current) => {
            content = current;
            return undefined;
        });
        if (!content) {
            return {};
        }
        const settings = parseJsonFileContent(content);
        return SettingsManager.migrateSettings(settings);
    }
    static tryLoadFromStorage(storage, scope, projectTrusted = true) {
        try {
            return { settings: SettingsManager.loadFromStorage(storage, scope, projectTrusted), error: null };
        }
        catch (error) {
            return { settings: {}, error: error };
        }
    }
    static validateLoadedSettings(scope, settings) {
        const errors = [];
        const configured = settings.defaultContextWindow;
        if (configured !== undefined) {
            const error = SettingsManager.validateContextWindowSettingValue(configured, "defaultContextWindow");
            if (error) {
                errors.push({ scope, error });
            }
        }
        const configuredByModel = settings.defaultContextWindows;
        if (configuredByModel !== undefined) {
            if (!SettingsManager.isPlainRecord(configuredByModel)) {
                errors.push({
                    scope,
                    error: new Error(`Invalid defaultContextWindows: expected an object keyed by "provider/modelId", got ${SettingsManager.formatSettingValue(configuredByModel)}.`),
                });
            }
            else {
                for (const [key, value] of Object.entries(configuredByModel)) {
                    const error = SettingsManager.validateContextWindowSettingValue(value, `defaultContextWindows.${key}`);
                    if (error) {
                        errors.push({ scope, error });
                    }
                }
            }
        }
        return errors;
    }
    static isPlainRecord(value) {
        return typeof value === "object" && value !== null && !Array.isArray(value);
    }
    static validateContextWindowSettingValue(value, fieldName) {
        if (typeof value === "number") {
            const validationError = validateContextWindowValue(value);
            return validationError ? new Error(`Invalid ${fieldName}: ${validationError}.`) : undefined;
        }
        if (typeof value === "string") {
            const parsed = parseContextWindowValue(value);
            return parsed.error ? new Error(`Invalid ${fieldName}: ${parsed.error}`) : undefined;
        }
        return new Error(`Invalid ${fieldName}: expected a positive integer token count or compact string like "400k" or "1m", got ${SettingsManager.formatSettingValue(value)}.`);
    }
    static formatSettingValue(value) {
        try {
            const serialized = JSON.stringify(value);
            return serialized ?? String(value);
        }
        catch {
            return String(value);
        }
    }
    /** Migrate old settings format to new format */
    static migrateSettings(settings) {
        // Migrate queueMode -> steeringMode
        if ("queueMode" in settings && !("steeringMode" in settings)) {
            settings.steeringMode = settings.queueMode;
            delete settings.queueMode;
        }
        // Migrate legacy websockets boolean -> transport enum
        if (!("transport" in settings) && typeof settings.websockets === "boolean") {
            settings.transport = settings.websockets ? "websocket" : "sse";
            delete settings.websockets;
        }
        // Migrate old skills object format to new array format
        if ("skills" in settings &&
            typeof settings.skills === "object" &&
            settings.skills !== null &&
            !Array.isArray(settings.skills)) {
            const skillsSettings = settings.skills;
            if (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {
                settings.enableSkillCommands = skillsSettings.enableSkillCommands;
            }
            if (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {
                settings.skills = skillsSettings.customDirectories;
            }
            else {
                delete settings.skills;
            }
        }
        // Migrate retry.maxDelayMs -> retry.provider.maxRetryDelayMs
        if ("retry" in settings &&
            typeof settings.retry === "object" &&
            settings.retry !== null &&
            !Array.isArray(settings.retry)) {
            const retrySettings = settings.retry;
            const providerSettings = typeof retrySettings.provider === "object" && retrySettings.provider !== null
                ? retrySettings.provider
                : undefined;
            if (typeof retrySettings.maxDelayMs === "number" &&
                (providerSettings?.maxRetryDelayMs === undefined || providerSettings?.maxRetryDelayMs === null)) {
                retrySettings.provider = {
                    ...(providerSettings ?? {}),
                    maxRetryDelayMs: retrySettings.maxDelayMs,
                };
            }
            delete retrySettings.maxDelayMs;
        }
        return settings;
    }
    getGlobalSettings() {
        return structuredClone(this.globalSettings);
    }
    getProjectSettings() {
        return structuredClone(this.projectSettings);
    }
    isProjectTrusted() {
        return this.projectTrusted;
    }
    setProjectTrusted(trusted) {
        if (this.projectTrusted === trusted) {
            return;
        }
        this.projectTrusted = trusted;
        this.modifiedProjectFields.clear();
        this.modifiedProjectNestedFields.clear();
        if (!trusted) {
            this.projectSettings = {};
            this.projectSettingsLoadError = null;
            this.settings = this.mergeEffectiveSettings();
            return;
        }
        const projectLoad = SettingsManager.tryLoadFromStorage(this.storage, "project", trusted);
        this.projectSettings = projectLoad.settings;
        this.projectSettingsLoadError = projectLoad.error;
        if (projectLoad.error) {
            this.recordError("project", projectLoad.error);
        }
        else {
            for (const { error } of SettingsManager.validateLoadedSettings("project", projectLoad.settings)) {
                this.recordError("project", error);
            }
        }
        this.settings = this.mergeEffectiveSettings();
    }
    async reload() {
        await this.writeQueue;
        const globalLoad = SettingsManager.tryLoadFromStorage(this.storage, "global");
        if (!globalLoad.error) {
            this.globalSettings = globalLoad.settings;
            this.globalSettingsLoadError = null;
            for (const { error } of SettingsManager.validateLoadedSettings("global", globalLoad.settings)) {
                this.recordError("global", error);
            }
        }
        else {
            this.globalSettingsLoadError = globalLoad.error;
            this.recordError("global", globalLoad.error);
        }
        this.modifiedFields.clear();
        this.modifiedNestedFields.clear();
        this.modifiedProjectFields.clear();
        this.modifiedProjectNestedFields.clear();
        const projectLoad = SettingsManager.tryLoadFromStorage(this.storage, "project", this.projectTrusted);
        if (!projectLoad.error) {
            this.projectSettings = projectLoad.settings;
            this.projectSettingsLoadError = null;
            for (const { error } of SettingsManager.validateLoadedSettings("project", projectLoad.settings)) {
                this.recordError("project", error);
            }
        }
        else {
            this.projectSettingsLoadError = projectLoad.error;
            this.recordError("project", projectLoad.error);
        }
        this.runtimeSettingsOverrides = SettingsManager.getRuntimeSettingsOverrides();
        this.settings = this.mergeEffectiveSettings();
    }
    /** Apply additional overrides on top of current settings */
    applyOverrides(overrides) {
        this.settings = deepMergeSettings(this.settings, overrides);
    }
    /** Mark a global field as modified during this session */
    markModified(field, nestedKey) {
        this.modifiedFields.add(field);
        if (nestedKey) {
            if (!this.modifiedNestedFields.has(field)) {
                this.modifiedNestedFields.set(field, new Set());
            }
            this.modifiedNestedFields.get(field).add(nestedKey);
        }
    }
    assertProjectTrustedForWrite() {
        if (!this.projectTrusted) {
            throw new Error("Project is not trusted; refusing to write project settings");
        }
    }
    /** Mark a project field as modified during this session */
    markProjectModified(field, nestedKey) {
        this.assertProjectTrustedForWrite();
        this.modifiedProjectFields.add(field);
        if (nestedKey) {
            if (!this.modifiedProjectNestedFields.has(field)) {
                this.modifiedProjectNestedFields.set(field, new Set());
            }
            this.modifiedProjectNestedFields.get(field).add(nestedKey);
        }
    }
    recordError(scope, error) {
        const normalizedError = error instanceof Error ? error : new Error(String(error));
        this.errors.push({ scope, error: normalizedError });
    }
    clearModifiedScope(scope) {
        if (scope === "global") {
            this.modifiedFields.clear();
            this.modifiedNestedFields.clear();
            return;
        }
        this.modifiedProjectFields.clear();
        this.modifiedProjectNestedFields.clear();
    }
    enqueueWrite(scope, task) {
        this.writeQueue = this.writeQueue
            .then(() => {
            if (scope === "project") {
                this.assertProjectTrustedForWrite();
            }
            task();
            this.clearModifiedScope(scope);
        })
            .catch((error) => {
            this.recordError(scope, error);
        });
    }
    cloneModifiedNestedFields(source) {
        const snapshot = new Map();
        for (const [key, value] of source.entries()) {
            snapshot.set(key, new Set(value));
        }
        return snapshot;
    }
    persistScopedSettings(scope, snapshotSettings, modifiedFields, modifiedNestedFields) {
        this.storage.withLock(scope, (current) => {
            const currentFileSettings = current
                ? SettingsManager.migrateSettings(parseJsonFileContent(current))
                : {};
            const mergedSettings = { ...currentFileSettings };
            for (const field of modifiedFields) {
                const value = snapshotSettings[field];
                if (modifiedNestedFields.has(field) && typeof value === "object" && value !== null) {
                    const nestedModified = modifiedNestedFields.get(field);
                    const baseNested = currentFileSettings[field] ?? {};
                    const inMemoryNested = value;
                    const mergedNested = { ...baseNested };
                    for (const nestedKey of nestedModified) {
                        mergedNested[nestedKey] = inMemoryNested[nestedKey];
                    }
                    mergedSettings[field] = mergedNested;
                }
                else {
                    mergedSettings[field] = value;
                }
            }
            return JSON.stringify(mergedSettings, null, 2);
        });
    }
    save() {
        this.settings = this.mergeEffectiveSettings();
        if (this.globalSettingsLoadError) {
            return;
        }
        const snapshotGlobalSettings = structuredClone(this.globalSettings);
        const modifiedFields = new Set(this.modifiedFields);
        const modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedNestedFields);
        this.enqueueWrite("global", () => {
            this.persistScopedSettings("global", snapshotGlobalSettings, modifiedFields, modifiedNestedFields);
        });
    }
    saveProjectSettings(settings) {
        this.assertProjectTrustedForWrite();
        this.projectSettings = structuredClone(settings);
        this.settings = this.mergeEffectiveSettings();
        if (this.projectSettingsLoadError) {
            return;
        }
        const snapshotProjectSettings = structuredClone(this.projectSettings);
        const modifiedFields = new Set(this.modifiedProjectFields);
        const modifiedNestedFields = this.cloneModifiedNestedFields(this.modifiedProjectNestedFields);
        this.enqueueWrite("project", () => {
            this.persistScopedSettings("project", snapshotProjectSettings, modifiedFields, modifiedNestedFields);
        });
    }
    async flush() {
        await this.writeQueue;
    }
    drainErrors() {
        const drained = [...this.errors];
        this.errors = [];
        return drained;
    }
    touchSplitAccessorMethods() {
        void this.markModified;
        void this.markProjectModified;
        void this.save;
        void this.saveProjectSettings;
    }
}
//# sourceMappingURL=settings-manager-core.js.map