const DEFAULT_CONFIG_DIRECTORY_PATH = '~/.mcp-desktop/config';
const PERSIST_DEBOUNCE_MS = 150;
const hasRequiredFileSystemBridge = (bridge) => {
    return (!!bridge &&
        typeof bridge.listDirectory === 'function' &&
        typeof bridge.readFile === 'function' &&
        typeof bridge.writeFile === 'function');
};
const isMissingConfigError = (error) => {
    if (!error) {
        return false;
    }
    if (typeof error === 'object' && error !== null) {
        const { code, cause } = error;
        if (typeof code === 'string' && code === 'ENOENT') {
            return true;
        }
        if (cause && isMissingConfigError(cause)) {
            return true;
        }
    }
    if (error instanceof Error && /ENOENT/.test(error.message)) {
        return true;
    }
    return false;
};
const createFileBridge = (configDirectoryPath) => {
    if (typeof window === 'undefined') {
        return null;
    }
    const fsBridgeCandidate = window.filesystem;
    const fsPromisesBridge = window.desktopFsPromises;
    if (!hasRequiredFileSystemBridge(fsBridgeCandidate)) {
        return null;
    }
    const fsBridge = fsBridgeCandidate;
    const hasRm = typeof fsPromisesBridge?.rm === 'function';
    const hasMkdir = typeof fsPromisesBridge?.mkdir === 'function';
    if (!hasRm || !fsPromisesBridge) {
        return null;
    }
    const ensureDirectory = async () => {
        if (!hasMkdir) {
            // Fallback: attempt to create directory implicitly by writing a noop file and removing it.
            await fsBridge.writeFile(`${configDirectoryPath}/.keep`, '');
            if (hasRm) {
                try {
                    await fsPromisesBridge.rm(`${configDirectoryPath}/.keep`, { force: true });
                }
                catch (error) {
                    if (!isMissingConfigError(error)) {
                        throw error;
                    }
                }
            }
            return;
        }
        await fsPromisesBridge.mkdir(configDirectoryPath, { recursive: true }).catch((error) => {
            if (!isMissingConfigError(error)) {
                throw error;
            }
        });
    };
    const filePathForKey = (key) => {
        return `${configDirectoryPath}/${encodeURIComponent(key)}.json`;
    };
    const decodeKeyFromFileName = (fileName) => {
        if (!fileName.endsWith('.json')) {
            return null;
        }
        const encoded = fileName.slice(0, -5);
        try {
            return decodeURIComponent(encoded);
        }
        catch (error) {
            console.error('Failed to decode config key from filename', { fileName, error });
            return null;
        }
    };
    const readAll = async () => {
        try {
            const entries = await fsBridge.listDirectory({ directoryPath: configDirectoryPath });
            const results = [];
            for (const entry of entries) {
                if (entry.isDirectory) {
                    continue;
                }
                const key = decodeKeyFromFileName(entry.name);
                if (!key) {
                    continue;
                }
                try {
                    const contents = await fsBridge.readFile(filePathForKey(key));
                    if (typeof contents === 'string') {
                        results.push({ key, value: contents });
                    }
                }
                catch (error) {
                    if (!isMissingConfigError(error)) {
                        console.error('Failed to read config value', { key, error });
                    }
                }
            }
            return results;
        }
        catch (error) {
            if (isMissingConfigError(error)) {
                return [];
            }
            console.error('Failed to enumerate config directory', error);
            return [];
        }
    };
    const read = async (key) => {
        try {
            const contents = await fsBridge.readFile(filePathForKey(key));
            return typeof contents === 'string' ? contents : null;
        }
        catch (error) {
            return handleMissingConfig(error);
        }
    };
    const write = async (key, contents) => {
        await ensureDirectory();
        await fsBridge.writeFile(filePathForKey(key), contents);
    };
    const remove = async (key) => {
        try {
            await fsPromisesBridge.rm(filePathForKey(key), { force: true });
        }
        catch (error) {
            if (!isMissingConfigError(error)) {
                console.error('Failed to remove config value', { key, error });
            }
        }
    };
    return {
        readAll,
        read,
        write,
        remove,
        ensureDirectory
    };
};
const handleMissingConfig = (error) => {
    if (isMissingConfigError(error)) {
        return null;
    }
    if (error instanceof Error) {
        console.error('Failed to access config file', error);
    }
    return null;
};
class FileBackedStorageAdapter {
    constructor(fileBridge) {
        Object.defineProperty(this, "fileBridge", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "data", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: {}
        });
        Object.defineProperty(this, "isLoaded", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "loadPromise", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "persistTimer", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: null
        });
        Object.defineProperty(this, "writePromise", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: Promise.resolve()
        });
        Object.defineProperty(this, "subscribers", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: new Map()
        });
        Object.defineProperty(this, "hasEnsuredDirectory", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "pendingChanges", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: new Map()
        });
        this.fileBridge = fileBridge;
        void this.ensureInitialized();
        if (typeof window !== 'undefined') {
            window.addEventListener('beforeunload', () => {
                if (this.persistTimer) {
                    clearTimeout(this.persistTimer);
                    this.persistTimer = null;
                }
                void this.flushToDisk();
            });
        }
    }
    getItem(key) {
        void this.ensureInitialized();
        return Object.prototype.hasOwnProperty.call(this.data, key) ? this.data[key] : null;
    }
    setItem(key, value) {
        const previous = this.data[key];
        if (previous === value) {
            return;
        }
        this.data[key] = value;
        this.pendingChanges.set(key, value);
        this.schedulePersist();
        this.notify(key, value);
    }
    removeItem(key) {
        if (Object.prototype.hasOwnProperty.call(this.data, key)) {
            delete this.data[key];
            this.pendingChanges.set(key, null);
            this.schedulePersist();
            this.notify(key, null);
        }
    }
    subscribe(key, callback) {
        const listeners = this.subscribers.get(key) ?? new Set();
        listeners.add(callback);
        this.subscribers.set(key, listeners);
        callback(this.getItem(key));
        void this.ensureInitialized().then(() => {
            callback(this.getItem(key));
        });
        return () => {
            const existing = this.subscribers.get(key);
            if (!existing) {
                return;
            }
            existing.delete(callback);
            if (existing.size === 0) {
                this.subscribers.delete(key);
            }
        };
    }
    async ensureInitialized() {
        if (this.isLoaded) {
            return;
        }
        if (!this.loadPromise) {
            this.loadPromise = this.loadFromDisk()
                .catch((error) => {
                if (error instanceof Error) {
                    console.error('Failed to load config file', error);
                }
            })
                .finally(() => {
                this.isLoaded = true;
                this.loadPromise = null;
            });
        }
        await this.loadPromise;
    }
    async loadFromDisk() {
        if (!this.hasEnsuredDirectory) {
            await this.fileBridge.ensureDirectory();
            this.hasEnsuredDirectory = true;
        }
        const entries = await this.fileBridge.readAll();
        const updates = [];
        for (const { key, value } of entries) {
            if (!Object.prototype.hasOwnProperty.call(this.data, key)) {
                this.data[key] = value;
                updates.push(key);
            }
        }
        updates.forEach((key) => this.notify(key, this.data[key] ?? null));
    }
    schedulePersist() {
        if (this.persistTimer) {
            clearTimeout(this.persistTimer);
        }
        this.persistTimer = setTimeout(() => {
            this.persistTimer = null;
            void this.flushToDisk();
        }, PERSIST_DEBOUNCE_MS);
    }
    flushToDisk() {
        this.writePromise = this.writePromise.then(async () => {
            if (this.pendingChanges.size === 0) {
                return;
            }
            const localChanges = Array.from(this.pendingChanges.entries());
            this.pendingChanges.clear();
            try {
                if (!this.hasEnsuredDirectory) {
                    await this.fileBridge.ensureDirectory();
                    this.hasEnsuredDirectory = true;
                }
            }
            catch (error) {
                if (error instanceof Error) {
                    console.error('Failed to ensure config directory exists', error);
                }
                // restore pending changes so we can retry later
                for (const [key, value] of localChanges) {
                    if (!this.pendingChanges.has(key)) {
                        this.pendingChanges.set(key, value);
                    }
                }
                return;
            }
            for (const [key, value] of localChanges) {
                try {
                    if (value === null) {
                        await this.fileBridge.remove(key);
                    }
                    else {
                        await this.fileBridge.write(key, value);
                    }
                }
                catch (error) {
                    if (error instanceof Error) {
                        console.error('Failed to persist config value', { key, error });
                    }
                    if (!this.pendingChanges.has(key)) {
                        this.pendingChanges.set(key, value);
                    }
                }
            }
        });
        return this.writePromise;
    }
    notify(key, value) {
        const listeners = this.subscribers.get(key);
        if (!listeners) {
            return;
        }
        for (const listener of listeners) {
            try {
                listener(value);
            }
            catch (error) {
                if (error instanceof Error) {
                    console.error('StorageAdapter subscriber threw an error', error);
                }
            }
        }
    }
}
const createInMemoryAdapter = () => {
    const store = new Map();
    const subscribers = new Map();
    const notify = (key, value) => {
        const listeners = subscribers.get(key);
        if (!listeners) {
            return;
        }
        for (const listener of listeners) {
            try {
                listener(value);
            }
            catch (error) {
                if (error instanceof Error) {
                    console.error('StorageAdapter subscriber threw an error', error);
                }
            }
        }
    };
    return {
        getItem: (key) => (store.has(key) ? store.get(key) : null),
        setItem: (key, value) => {
            store.set(key, value);
            notify(key, value);
        },
        removeItem: (key) => {
            if (store.delete(key)) {
                notify(key, null);
            }
        },
        subscribe: (key, callback) => {
            const listeners = subscribers.get(key) ?? new Set();
            listeners.add(callback);
            subscribers.set(key, listeners);
            callback(store.has(key) ? store.get(key) : null);
            return () => {
                const existing = subscribers.get(key);
                if (!existing) {
                    return;
                }
                existing.delete(callback);
                if (existing.size === 0) {
                    subscribers.delete(key);
                }
            };
        }
    };
};
export const createElectronStorageAdapter = (options) => {
    const configDirectoryPath = options?.configDirectoryPath ?? DEFAULT_CONFIG_DIRECTORY_PATH;
    const bridge = createFileBridge(configDirectoryPath);
    if (bridge) {
        return new FileBackedStorageAdapter(bridge);
    }
    console.warn('Falling back to in-memory storage adapter');
    return createInMemoryAdapter();
};
