import React from 'react';
import { useLocalStorageState } from '../hooks/useLocalStorage';
const EMPTY_STATE = {
    order: [],
    byId: {},
    selectedId: null
    // Note: initialServersApplied is intentionally omitted so we can detect
    // when storage has actually loaded vs just returning the default empty state
};
const McpServersContext = React.createContext(undefined);
const generateServerId = () => {
    if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
        return crypto.randomUUID();
    }
    return `mcp-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
};
const normalizeToolPermission = (value) => {
    if (typeof value === 'string') {
        const normalized = value.trim().toLowerCase();
        if (normalized === 'enabled') {
            return 'enabled';
        }
        if (normalized === 'disabled') {
            return 'disabled';
        }
        if (normalized === 'ask_first' || normalized === 'ask-first' || normalized === 'ask') {
            return 'ask_first';
        }
    }
    if (typeof value === 'boolean') {
        return value ? 'enabled' : 'disabled';
    }
    return undefined;
};
const normalizeToolPreferences = (preferences) => {
    if (!preferences) {
        return undefined;
    }
    const normalized = {};
    for (const [toolName, enabled] of Object.entries(preferences)) {
        const permission = normalizeToolPermission(enabled);
        if (permission) {
            normalized[toolName] = permission;
        }
    }
    return Object.keys(normalized).length > 0 ? normalized : undefined;
};
const normalizeServerOptions = (options) => {
    const { toolPreferences, serverEnabled, cachedPrompts, cachedPromptsUpdatedAt, ...rest } = options;
    const { method: _legacyMethod, ...restWithoutMethod } = rest;
    void _legacyMethod;
    const normalizedUrl = typeof restWithoutMethod.url === 'string'
        ? restWithoutMethod.url.trim()
        : restWithoutMethod.url;
    const normalizedOptions = {
        ...restWithoutMethod,
        url: normalizedUrl,
        serverEnabled: serverEnabled ?? true,
        toolPreferences: normalizeToolPreferences(toolPreferences),
        cachedPrompts: cachedPrompts?.length ? cachedPrompts : undefined,
        cachedPromptsUpdatedAt: cachedPromptsUpdatedAt ?? undefined
    };
    return normalizedOptions;
};
const trimmedUrl = (url) => {
    if (typeof url !== 'string')
        return undefined;
    const value = url.trim();
    return value.length > 0 ? value : undefined;
};
const findServerIdByUrl = (collection, url) => {
    const target = trimmedUrl(url);
    if (!target)
        return undefined;
    for (const [id, server] of Object.entries(collection)) {
        if (trimmedUrl(server.url) === target) {
            return id;
        }
    }
    return undefined;
};
const storedServersEqual = (a, b) => {
    return JSON.stringify(a) === JSON.stringify(b);
};
export const McpServersProvider = ({ children, initialServers, storageKey = 'mcp-server-options' }) => {
    const { value: state, update: updateState, setValue } = useLocalStorageState(storageKey, EMPTY_STATE);
    // Track if we've received the first storage load (to handle async Electron storage)
    const [storageLoaded, setStorageLoaded] = React.useState(false);
    const initialStateRef = React.useRef(state);
    // Detect when storage has loaded by checking if state changed from initial empty state
    React.useEffect(() => {
        console.log('[McpContext] Storage load detection:', {
            initialServersApplied: state.initialServersApplied,
            hasInitialServersAppliedProp: 'initialServersApplied' in state,
            serverCount: Object.keys(state.byId).length,
            isInitialState: state === initialStateRef.current,
            storageLoaded,
            rawState: JSON.stringify(state)
        });
        // If state has the initialServersApplied property (true or false), storage has loaded
        // The EMPTY_STATE doesn't have this property, so its presence means real data loaded
        if ('initialServersApplied' in state) {
            console.log('[McpContext] Storage loaded - detected initialServersApplied property');
            setStorageLoaded(true);
            return;
        }
        // If state has any servers, storage has loaded with real data
        if (Object.keys(state.byId).length > 0) {
            console.log('[McpContext] Storage loaded - detected servers in state');
            setStorageLoaded(true);
            return;
        }
        // After a short delay, assume storage has finished loading empty/new storage
        console.log('[McpContext] Setting storage loaded timeout');
        const timer = setTimeout(() => {
            console.log('[McpContext] Storage loaded - timeout fallback');
            setStorageLoaded(true);
        }, 100);
        return () => clearTimeout(timer);
    }, [state, storageLoaded]);
    React.useEffect(() => {
        console.log('[McpContext] Prop servers effect check:', {
            hasInitialServers: !!initialServers?.length,
            storageLoaded
        });
        if (!initialServers?.length)
            return;
        if (!storageLoaded) {
            console.log('[McpContext] Waiting for storage to load before applying prop servers');
            return; // Wait for storage to load before applying prop servers
        }
        updateState((prev) => {
            console.log('[McpContext] Initial servers effect running', {
                hasInitialServers: !!initialServers?.length,
                initialServerIds: initialServers?.map(s => s.id),
                currentServerCount: Object.keys(prev.byId).length,
                currentServerIds: Object.keys(prev.byId),
                currentServers: prev.byId
            });
            let nextById = prev.byId;
            let nextOrder = prev.order;
            let changed = false;
            for (const entry of initialServers) {
                const normalized = normalizeServerOptions(entry.options);
                const targetId = entry.id ?? generateServerId();
                console.log('[McpContext] Processing initial server:', {
                    targetId,
                    normalized,
                    alreadyExists: !!nextById[targetId]
                });
                // Only add if not already present
                if (!nextById[targetId]) {
                    console.log('[McpContext] Adding initial server:', targetId);
                    nextById = {
                        ...nextById,
                        [targetId]: normalized
                    };
                    if (!nextOrder.includes(targetId)) {
                        nextOrder = [...nextOrder, targetId];
                    }
                    changed = true;
                }
                else {
                    console.log('[McpContext] Skipping initial server (already exists):', targetId, nextById[targetId]);
                }
            }
            if (!changed) {
                console.log('[McpContext] No changes needed - all prop servers already exist');
                return prev;
            }
            console.log('[McpContext] Applied prop servers, new state:', {
                order: nextOrder,
                serverIds: Object.keys(nextById),
                servers: nextById
            });
            return {
                order: nextOrder,
                byId: nextById,
                selectedId: prev.selectedId || (nextOrder.length > 0 ? nextOrder[nextOrder.length - 1] : null),
                initialServersApplied: true
            };
        });
    }, [initialServers, storageLoaded, updateState]);
    const addServer = React.useCallback((options, providedId) => {
        let resolvedId = providedId ?? generateServerId();
        const normalizedOptions = normalizeServerOptions({
            ...options,
            toolPreferences: options.toolPreferences
        });
        const candidateUrl = trimmedUrl(normalizedOptions.url);
        updateState((prev) => {
            const duplicateEntry = candidateUrl
                ? Object.entries(prev.byId).find(([, server]) => trimmedUrl(server.url) === candidateUrl)
                : undefined;
            if (duplicateEntry) {
                const [existingId, existingOptions] = duplicateEntry;
                resolvedId = existingId;
                const mergedOptions = {
                    ...existingOptions,
                    ...normalizedOptions
                };
                return {
                    ...prev,
                    order: prev.order,
                    byId: {
                        ...prev.byId,
                        [existingId]: mergedOptions
                    },
                    selectedId: prev.selectedId ?? existingId
                };
            }
            const alreadyExists = prev.byId[resolvedId] !== undefined;
            const order = alreadyExists ? prev.order : [...prev.order, resolvedId];
            return {
                ...prev,
                order,
                byId: {
                    ...prev.byId,
                    [resolvedId]: normalizedOptions
                },
                selectedId: prev.selectedId ?? resolvedId
            };
        });
        return resolvedId;
    }, [updateState]);
    const updateServer = React.useCallback((id, options) => {
        console.log('[McpContext] updateServer called:', {
            id,
            options,
            currentServerIds: Object.keys(state.byId),
            currentServers: state.byId
        });
        updateState((prev) => {
            if (!prev.byId[id]) {
                console.log('[McpContext] updateServer: server not found:', id);
                return prev;
            }
            const existing = prev.byId[id];
            console.log('[McpContext] updateServer existing server:', { id, existing });
            const merged = {
                ...existing,
                ...options,
                toolPreferences: options.toolPreferences ?? existing.toolPreferences
            };
            console.log('[McpContext] updateServer merged:', { id, merged });
            const normalized = normalizeServerOptions(merged);
            console.log('[McpContext] updateServer normalized:', { id, normalized });
            // Only check for URL duplicates if this is an SSE server with a URL
            // STDIO servers should never be considered duplicates based on URL
            const isSSEServer = normalized.transportMode === 'sse' || !normalized.stdioConfig;
            const candidateUrl = isSSEServer ? trimmedUrl(normalized.url) : undefined;
            console.log('[McpContext] updateServer duplicate check:', {
                id,
                isSSEServer,
                candidateUrl,
                transportMode: normalized.transportMode,
                hasStdioConfig: !!normalized.stdioConfig
            });
            if (candidateUrl) {
                const duplicateEntry = Object.entries(prev.byId).find(([otherId, server]) => {
                    // Only match against other SSE servers
                    const otherIsSSE = server.transportMode === 'sse' || !server.stdioConfig;
                    const matches = otherId !== id && otherIsSSE && trimmedUrl(server.url) === candidateUrl;
                    console.log('[McpContext] updateServer checking duplicate:', {
                        otherId,
                        otherIsSSE,
                        otherTransportMode: server.transportMode,
                        otherHasStdioConfig: !!server.stdioConfig,
                        otherUrl: trimmedUrl(server.url),
                        matches
                    });
                    return matches;
                });
                if (duplicateEntry) {
                    const [duplicateId, duplicateOptions] = duplicateEntry;
                    console.log('[McpContext] updateServer DUPLICATE FOUND - MERGING:', {
                        fromId: id,
                        toId: duplicateId,
                        duplicateOptions,
                        normalized
                    });
                    const mergedDuplicate = {
                        ...duplicateOptions,
                        ...normalized
                    };
                    const nextById = {
                        ...prev.byId,
                        [duplicateId]: mergedDuplicate
                    };
                    delete nextById[id];
                    const nextOrder = prev.order.filter((entryId) => entryId !== id);
                    const nextSelectedId = prev.selectedId === id ? duplicateId : prev.selectedId;
                    console.log('[McpContext] updateServer AFTER MERGE:', {
                        deletedId: id,
                        mergedIntoId: duplicateId,
                        newServerIds: Object.keys(nextById),
                        nextOrder
                    });
                    return {
                        ...prev,
                        order: nextOrder,
                        byId: nextById,
                        selectedId: nextSelectedId
                    };
                }
            }
            console.log('[McpContext] updateServer: no duplicate, updating in place:', id);
            return {
                ...prev,
                byId: {
                    ...prev.byId,
                    [id]: normalized
                }
            };
        });
    }, [updateState, state.byId]);
    const removeServer = React.useCallback((id) => {
        updateState((prev) => {
            if (!prev.byId[id]) {
                return prev;
            }
            const nextById = { ...prev.byId };
            delete nextById[id];
            const nextOrder = prev.order.filter((entryId) => entryId !== id);
            const nextSelectedId = prev.selectedId === id ? (nextOrder.length > 0 ? nextOrder[nextOrder.length - 1] : null) : prev.selectedId;
            return {
                ...prev,
                order: nextOrder,
                byId: nextById,
                selectedId: nextSelectedId
            };
        });
    }, [updateState]);
    const selectServer = React.useCallback((id) => {
        updateState((prev) => {
            if (id === null) {
                if (prev.selectedId === null)
                    return prev;
                return {
                    ...prev,
                    selectedId: null
                };
            }
            if (!prev.byId[id]) {
                return prev;
            }
            if (prev.selectedId === id) {
                return prev;
            }
            return {
                ...prev,
                selectedId: id
            };
        });
    }, [updateState]);
    const clearServers = React.useCallback(() => {
        setValue({
            order: [],
            byId: {},
            selectedId: null
            // initialServersApplied intentionally omitted - will be set when prop servers are applied
        });
    }, [setValue]);
    const getServer = React.useCallback((id) => {
        const server = state.byId[id];
        return server ? normalizeServerOptions(server) : undefined;
    }, [state.byId]);
    const setServerEnabled = React.useCallback((id, enabled) => {
        updateState((prev) => {
            const server = prev.byId[id];
            if (!server) {
                return prev;
            }
            if ((server.serverEnabled ?? true) === enabled) {
                return prev;
            }
            return {
                ...prev,
                byId: {
                    ...prev.byId,
                    [id]: {
                        ...server,
                        serverEnabled: enabled
                    }
                }
            };
        });
    }, [updateState]);
    const setToolPermission = React.useCallback((id, toolName, permission) => {
        updateState((prev) => {
            const server = prev.byId[id];
            if (!server) {
                return prev;
            }
            const normalizedPermission = normalizeToolPermission(permission) ?? 'enabled';
            const currentPreferences = server.toolPreferences ?? {};
            const currentPermission = normalizeToolPermission(currentPreferences[toolName]) ?? 'enabled';
            if (currentPermission === normalizedPermission) {
                return prev;
            }
            const nextPreferences = {
                ...currentPreferences,
                [toolName]: normalizedPermission
            };
            return {
                ...prev,
                byId: {
                    ...prev.byId,
                    [id]: {
                        ...server,
                        toolPreferences: nextPreferences,
                        serverEnabled: server.serverEnabled ?? true
                    }
                }
            };
        });
    }, [updateState]);
    const getToolPermission = React.useCallback((id, toolName) => {
        const server = state.byId[id];
        if (!server) {
            return 'enabled';
        }
        const preferences = server.toolPreferences ?? {};
        return normalizeToolPermission(preferences[toolName]) ?? 'enabled';
    }, [state.byId]);
    const setServerPrompts = React.useCallback((id, prompts) => {
        updateState((prev) => {
            const server = prev.byId[id];
            if (!server) {
                return prev;
            }
            const normalizedPrompts = prompts && prompts.length > 0 ? [...prompts] : undefined;
            const existingPrompts = server.cachedPrompts ?? undefined;
            const existingTimestamp = server.cachedPromptsUpdatedAt ?? undefined;
            const promptsChanged = JSON.stringify(existingPrompts ?? []) !== JSON.stringify(normalizedPrompts ?? []);
            const timestamp = normalizedPrompts ? Date.now() : undefined;
            if (!promptsChanged && existingTimestamp === timestamp) {
                return prev;
            }
            return {
                ...prev,
                byId: {
                    ...prev.byId,
                    [id]: {
                        ...server,
                        cachedPrompts: normalizedPrompts,
                        cachedPromptsUpdatedAt: timestamp
                    }
                }
            };
        });
    }, [updateState]);
    const isToolEnabled = React.useCallback((id, toolName) => {
        return getToolPermission(id, toolName) === 'enabled';
    }, [getToolPermission]);
    const servers = React.useMemo(() => state.order
        .map((id) => ({ id, options: state.byId[id] }))
        .filter((entry) => entry.options !== undefined)
        .map((entry) => ({
        id: entry.id,
        options: normalizeServerOptions(entry.options)
    })), [state.byId, state.order]);
    const contextValue = React.useMemo(() => ({
        state,
        servers,
        addServer,
        updateServer,
        removeServer,
        selectServer,
        clearServers,
        getServer,
        setServerEnabled,
        setToolPermission,
        getToolPermission,
        isToolEnabled,
        setServerPrompts
    }), [state, servers, addServer, updateServer, removeServer, selectServer, clearServers, getServer, setServerEnabled, setToolPermission, getToolPermission, isToolEnabled, setServerPrompts]);
    return React.createElement(McpServersContext.Provider, { value: contextValue }, children);
};
export const useMcpServers = () => {
    const context = React.useContext(McpServersContext);
    if (!context) {
        throw new Error('useMcpServers must be used within an McpServersProvider');
    }
    return context;
};
export default McpServersContext;
