import React, { createContext, useContext, useReducer, useCallback, useRef, useEffect } from 'react';
import * as api from '../utils/api';

let _toastId = 0;

const DrawerContext = createContext(null);

const initialState = {
    theme: localStorage.getItem('qa_assistant_git_drawer_theme') || 'dark',
    isOpen: false,
    repositories: [],
    selectedRepository: null,
    branches: [],
    branchMeta: [],
    currentBranch: '',
    hasChanges: false,
    lastPulled: null,
    loading: {
        repos: false,
        branches: false,
        pull: false,
        fetch: false,
        pullAll: false,
        fetchAll: false,
        switching: null, // branch name being switched to
    },
    searchQuery: '',
    error: null,
    toasts: [],
    uncommittedModal: null, // { type: 'pull' | 'switch', pluginDir, branch? }
};

function reducer(state, action) {
    switch (action.type) {
        case 'OPEN_DRAWER':
            return { ...state, isOpen: true };
        case 'CLOSE_DRAWER':
            return { ...initialState, theme: state.theme };
        case 'TOGGLE_THEME': {
            const newTheme = state.theme === 'dark' ? 'light' : 'dark';
            localStorage.setItem('qa_assistant_git_drawer_theme', newTheme);
            return { ...state, theme: newTheme };
        }
        case 'SET_REPOSITORIES':
            return { ...state, repositories: action.payload };
        case 'SELECT_REPOSITORY': {
            const repo = action.payload;
            return {
                ...state,
                selectedRepository: repo,
                branches: [],
                currentBranch: repo?.currentBranch || '',
                searchQuery: '',
            };
        }
        case 'SET_BRANCHES': {
            const meta = action.payload.branchMeta ?? [];
            return {
                ...state,
                branchMeta: meta.length > 0 ? meta : state.branchMeta,
                branches: meta.length > 0
                    ? meta.map(b => b.name)
                    : (action.payload.branches ?? state.branches),
                currentBranch: action.payload.currentBranch,
                hasChanges: action.payload.hasChanges ?? state.hasChanges,
                lastPulled: action.payload.lastPulled ?? state.lastPulled,
            };
        }
        case 'SET_CURRENT_BRANCH':
            return {
                ...state,
                currentBranch: action.payload,
                repositories: state.repositories.map((r) =>
                    r.slug === state.selectedRepository?.slug ? { ...r, currentBranch: action.payload } : r
                ),
            };
        case 'SET_LAST_PULLED':
            return {
                ...state,
                lastPulled: action.payload,
                repositories: state.repositories.map((r) =>
                    r.slug === state.selectedRepository?.slug ? { ...r, lastPulled: action.payload } : r
                ),
            };
        case 'SET_HAS_CHANGES':
            return { ...state, hasChanges: action.payload };
        case 'SET_LOADING':
            return { ...state, loading: { ...state.loading, ...action.payload } };
        case 'SET_SEARCH_QUERY':
            return { ...state, searchQuery: action.payload };
        case 'SET_ERROR':
            return { ...state, error: action.payload };
        case 'ADD_TOAST':
            return { ...state, toasts: [...state.toasts, action.payload] };
        case 'REMOVE_TOAST':
            return { ...state, toasts: state.toasts.filter((t) => t.id !== action.payload) };
        case 'SET_UNCOMMITTED_MODAL':
            return { ...state, uncommittedModal: action.payload };
        default:
            return state;
    }
}

export function DrawerProvider({ children }) {
    const [state, dispatch] = useReducer(reducer, initialState);

    const stateRef = useRef(state);
    useEffect(() => { stateRef.current = state; });

    const addToast = useCallback((message, type = 'info') => {
        const id = ++_toastId;
        dispatch({ type: 'ADD_TOAST', payload: { id, message, type } });
        setTimeout(() => dispatch({ type: 'REMOVE_TOAST', payload: id }), 4000);
    }, []);

    const loadRepositories = useCallback(async () => {
        dispatch({ type: 'SET_LOADING', payload: { repos: true } });
        try {
            const res = await api.fetchRepositories();
            if (res.success) {
                dispatch({ type: 'SET_REPOSITORIES', payload: res.data.repositories });
                // Auto-select first repo if available and nothing is selected
                if (res.data.repositories.length > 0) {
                    const currentSelection = stateRef.current.selectedRepository;
                    if (!currentSelection) {
                        dispatch({ type: 'SELECT_REPOSITORY', payload: res.data.repositories[0] });
                    } else {
                        // Keep current selection if it still exists in the refreshed list
                        const stillExists = res.data.repositories.find(r => r.slug === currentSelection.slug);
                        if (!stillExists) {
                            dispatch({ type: 'SELECT_REPOSITORY', payload: res.data.repositories[0] });
                        }
                    }
                }
            } else {
                dispatch({ type: 'SET_ERROR', payload: res.data?.message || 'Failed to load repositories' });
            }
        } catch (err) {
            dispatch({ type: 'SET_ERROR', payload: 'Network error loading repositories' });
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { repos: false } });
        }
    }, []);

    const loadBranches = useCallback(async (pluginDir) => {
        dispatch({ type: 'SET_LOADING', payload: { branches: true } });
        try {
            const res = await api.fetchBranches(pluginDir);
            if (res.success) {
                dispatch({
                    type: 'SET_BRANCHES',
                    payload: {
                        branchMeta: res.data.branches,   // [{name, age}] from PHP
                        currentBranch: res.data.currentBranch,
                        hasChanges: res.data.hasChanges,
                        lastPulled: res.data.lastPulled,
                    },
                });
            } else {
                addToast(res.data?.message || 'Failed to load branches', 'error');
            }
        } catch (err) {
            addToast('Network error loading branches', 'error');
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { branches: false } });
        }
    }, [addToast]);

    const doSwitchBranch = useCallback(async (pluginDir, branch, force = false) => {
        dispatch({ type: 'SET_LOADING', payload: { switching: branch } });
        try {
            const res = await api.switchBranch(pluginDir, branch, force);
            if (res.success) {
                dispatch({ type: 'SET_CURRENT_BRANCH', payload: res.data.current_branch });
                addToast(`Switched to ${res.data.current_branch}`, 'success');
            } else {
                if (res.data?.has_changes) {
                    dispatch({
                        type: 'SET_UNCOMMITTED_MODAL',
                        payload: { type: 'switch', pluginDir, branch },
                    });
                } else {
                    addToast(res.data?.message || 'Failed to switch branch', 'error');
                }
            }
        } catch (err) {
            addToast('Network error switching branch', 'error');
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { switching: null } });
        }
    }, [addToast]);

    const doPull = useCallback(async (pluginDir) => {
        dispatch({ type: 'SET_LOADING', payload: { pull: true } });
        try {
            const res = await api.pullRepo(pluginDir);
            if (res.success) {
                addToast(`Pulled latest for ${res.data.branch}`, 'success');
                if (res.data.lastPulled) {
                    dispatch({ type: 'SET_LAST_PULLED', payload: res.data.lastPulled });
                }
                // Refresh branches after pull
                await loadBranches(pluginDir);
            } else {
                if (res.data?.has_changes) {
                    dispatch({
                        type: 'SET_UNCOMMITTED_MODAL',
                        payload: { type: 'pull', pluginDir },
                    });
                } else {
                    addToast(res.data?.message || 'Pull failed', 'error');
                }
            }
        } catch (err) {
            addToast('Network error during pull', 'error');
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { pull: false } });
        }
    }, [addToast, loadBranches]);

    const doFetch = useCallback(async (pluginDir) => {
        dispatch({ type: 'SET_LOADING', payload: { fetch: true } });
        try {
            const res = await api.fetchRepo(pluginDir);
            if (res.success) {
                addToast(`Fetched ${res.data.branches.length} branches`, 'success');
                // refresh_branches returns plain strings; normalize to {name, age} shape
                const normalized = res.data.branches.map(b =>
                    typeof b === 'string' ? { name: b, age: 0 } : b
                );
                dispatch({
                    type: 'SET_BRANCHES',
                    payload: { branchMeta: normalized, currentBranch: res.data.current_branch },
                });
            } else {
                addToast(res.data?.message || 'Fetch failed', 'error');
            }
        } catch (err) {
            addToast('Network error during fetch', 'error');
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { fetch: false } });
        }
    }, [addToast]);

    const doPullAll = useCallback(async () => {
        dispatch({ type: 'SET_LOADING', payload: { pullAll: true } });
        try {
            const res = await api.pullAllRepos();
            if (res.success) {
                const succeeded = res.data.results.filter(r => r.success).length;
                const failed    = res.data.results.filter(r => !r.success).length;
                addToast(
                    failed === 0
                        ? `Pulled all ${succeeded} repos`
                        : `Pulled ${succeeded} repos, ${failed} failed`,
                    failed === 0 ? 'success' : 'warning'
                );
                await loadRepositories();
            } else {
                addToast(res.data?.message || 'Pull all failed', 'error');
            }
        } catch (err) {
            addToast('Network error during pull all', 'error');
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { pullAll: false } });
        }
    }, [addToast, loadRepositories]);

    const doFetchAll = useCallback(async () => {
        dispatch({ type: 'SET_LOADING', payload: { fetchAll: true } });
        try {
            const res = await api.fetchAllRepos();
            if (res.success) {
                const succeeded = res.data.results.filter(r => r.success).length;
                const failed    = res.data.results.filter(r => !r.success).length;
                addToast(
                    failed === 0
                        ? `Fetched ${succeeded} repos`
                        : `Fetched ${succeeded} repos, ${failed} failed`,
                    failed === 0 ? 'success' : 'warning'
                );
                await loadRepositories();
            } else {
                addToast(res.data?.message || 'Fetch all failed', 'error');
            }
        } catch (err) {
            addToast('Network error during fetch all', 'error');
        } finally {
            dispatch({ type: 'SET_LOADING', payload: { fetchAll: false } });
        }
    }, [addToast, loadRepositories]);

    const doStash = useCallback(async (pluginDir) => {
        try {
            const res = await api.stashChanges(pluginDir);
            if (res.success) {
                addToast('Changes stashed', 'success');
                return true;
            } else {
                addToast(res.data?.message || 'Stash failed', 'error');
                return false;
            }
        } catch {
            addToast('Network error during stash', 'error');
            return false;
        }
    }, [addToast]);

    const doCommit = useCallback(async (pluginDir, message) => {
        try {
            const res = await api.commitChanges(pluginDir, message);
            if (res.success) {
                addToast('Changes committed', 'success');
                return true;
            } else {
                addToast(res.data?.message || 'Commit failed', 'error');
                return false;
            }
        } catch {
            addToast('Network error during commit', 'error');
            return false;
        }
    }, [addToast]);

    const value = {
        state,
        dispatch,
        addToast,
        loadRepositories,
        loadBranches,
        doSwitchBranch,
        doPull,
        doFetch,
        doPullAll,
        doFetchAll,
        doStash,
        doCommit,
    };

    return <DrawerContext.Provider value={value}>{children}</DrawerContext.Provider>;
}

export function useDrawer() {
    const ctx = useContext(DrawerContext);
    if (!ctx) throw new Error('useDrawer must be used within DrawerProvider');
    return ctx;
}
