import React, { createContext, useContext, useReducer, useMemo } from 'react';
import { produce } from 'immer';
import type { DesignData, Widget, WidgetType, EventBinding } from './types';

export interface AppState {
    vscode: { postMessage: (msg: any) => void };
    design: DesignData;
    selectedWidgetId: string | null;
    history: DesignData[];
    historyIndex: number;
}

type Action =
    | { type: 'init'; payload: DesignData }
    | { type: 'addWidget'; payload: { type: WidgetType; x: number; y: number } }
    | { type: 'selectWidget'; payload: { id: string | null } }
    | { type: 'updateWidget'; payload: { id: string; patch: Partial<Widget> } }
    | {
          type: 'updateProps';
          payload: { id: string; properties: Record<string, any> };
      }
    | { type: 'deleteWidget'; payload: { id: string } }
    | { type: 'addEvent'; payload: EventBinding }
    | {
          type: 'removeEvent';
          payload: { widget: string; type: string; name?: string };
      }
    | { type: 'clear' }
    | { type: 'undo' }
    | { type: 'redo' }
    | {
          type: 'setForm';
          payload: {
              name?: string;
              title?: string;
              size?: { width: number; height: number };
              className?: string;
          };
      }
    | { type: 'pushHistory' };

const AppStateContext = createContext<AppState | undefined>(undefined);
const AppDispatchContext = createContext<React.Dispatch<Action> | undefined>(
    undefined
);

// Helper to check if two design states are effectively different to warrant a history entry
// This helps prevent spamming history with tiny drag updates if we were to push every frame (though we usually push on mouse up)
function hasDesignChanged(prev: DesignData, next: DesignData): boolean {
    return JSON.stringify(prev) !== JSON.stringify(next);
}

function reducer(state: AppState, action: Action): AppState {
    if (action.type === 'undo') {
        if (state.historyIndex <= 0) return state;
        const idx = state.historyIndex - 1;
        return {
            ...state,
            historyIndex: idx,
            design: JSON.parse(JSON.stringify(state.history[idx])),
        };
    }

    if (action.type === 'redo') {
        if (state.historyIndex >= state.history.length - 1) return state;
        const idx = state.historyIndex + 1;
        return {
            ...state,
            historyIndex: idx,
            design: JSON.parse(JSON.stringify(state.history[idx])),
        };
    }

    if (action.type === 'pushHistory') {
         // This action is explicitly called when an operation is "finished" (e.g. onMouseUp after drag)
         if (state.historyIndex >= 0 && !hasDesignChanged(state.history[state.historyIndex], state.design)) {
             return state;
         }
         return produce(state, (draft) => {
             pushHistoryDraft(draft);
         });
    }

    // For other actions, we update the state but DO NOT push to history automatically
    // unless it's a discrete action like 'addWidget' or 'deleteWidget'.
    // Continuous actions like 'updateWidget' (drag) should only update state, 
    // and rely on a subsequent 'pushHistory' or explicit logic to save state.
    
    // However, to keep backward compatibility with existing components that might not call pushHistory,
    // we will still push history for "one-off" actions, but we should be careful with high-frequency updates.
    
    // In the previous implementation, every update pushed history.
    // We will optimize this by only pushing history for significant actions,
    // or letting the UI trigger history pushes for drag operations.

    return produce(state, (draft) => {
        let shouldPushHistory = false;

        switch (action.type) {
            case 'init': {
                draft.design = action.payload;
                draft.selectedWidgetId = null;
                if (!draft.design.events) draft.design.events = [];
                // Init resets history usually
                draft.history = [JSON.parse(JSON.stringify(draft.design))];
                draft.historyIndex = 0;
                break;
            }
            case 'addWidget': {
                const id = `widget_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
                const w: Widget = {
                    id,
                    type: action.payload.type,
                    x: action.payload.x,
                    y: action.payload.y,
                    width: 120,
                    height: 40,
                    properties: { text: action.payload.type },
                };
                draft.design.widgets.push(w);
                draft.selectedWidgetId = id;
                shouldPushHistory = true;
                break;
            }
            case 'selectWidget': {
                draft.selectedWidgetId = action.payload.id;
                // Selection change doesn't need history
                break;
            }
            case 'updateWidget': {
                const idx = draft.design.widgets.findIndex(
                    (w) => w.id === action.payload.id
                );
                if (idx >= 0) {
                    Object.assign(
                        draft.design.widgets[idx],
                        action.payload.patch
                    );
                }
                // Don't push history here for every drag frame. 
                // Components should dispatch 'pushHistory' on drag end.
                break;
            }
            case 'updateProps': {
                const idx = draft.design.widgets.findIndex(
                    (w) => w.id === action.payload.id
                );
                if (idx >= 0) {
                    draft.design.widgets[idx].properties = {
                        ...draft.design.widgets[idx].properties,
                        ...action.payload.properties,
                    };
                }
                shouldPushHistory = true;
                break;
            }
            case 'deleteWidget': {
                draft.design.widgets = draft.design.widgets.filter(
                    (w) => w.id !== action.payload.id
                );
                if (!draft.design.events) draft.design.events = [];
                draft.design.events = draft.design.events.filter(
                    (e) => e.widget !== action.payload.id
                );
                draft.selectedWidgetId = null;
                shouldPushHistory = true;
                break;
            }
            case 'addEvent': {
                if (!draft.design.events) draft.design.events = [];
                const existsIndex = draft.design.events.findIndex(
                    (e) =>
                        e.widget === action.payload.widget &&
                        e.type === action.payload.type &&
                        e.name === action.payload.name
                );
                if (existsIndex >= 0) {
                    draft.design.events[existsIndex].code = action.payload.code;
                } else {
                    draft.design.events.push(action.payload);
                }
                shouldPushHistory = true;
                break;
            }
            case 'removeEvent': {
                if (!draft.design.events) draft.design.events = [];
                draft.design.events = draft.design.events.filter((e) => {
                    const matchWidget = e.widget === action.payload.widget;
                    const matchType = e.type === action.payload.type;
                    const matchName = action.payload.name
                        ? e.name === action.payload.name
                        : true;
                    return !(matchWidget && matchType && matchName);
                });
                shouldPushHistory = true;
                break;
            }
            case 'clear': {
                draft.design.widgets = [];
                draft.design.events = [];
                draft.selectedWidgetId = null;
                shouldPushHistory = true;
                break;
            }
            case 'setForm': {
                if (action.payload.name)
                    draft.design.form.name = action.payload.name;
                if (action.payload.title)
                    draft.design.form.title = action.payload.title;
                if (action.payload.size)
                    draft.design.form.size = action.payload.size;
                if (action.payload.className)
                    draft.design.form.className = action.payload.className;
                shouldPushHistory = true;
                break;
            }
        }

        if (shouldPushHistory) {
             pushHistoryDraft(draft);
        }
    });
}

function pushHistoryDraft(draft: AppState) {
    // Limit history stack size to prevent memory issues
    const MAX_HISTORY = 50;
    
    // Remove future history if we were in the middle of the stack
    if (draft.historyIndex < draft.history.length - 1) {
        draft.history = draft.history.slice(0, draft.historyIndex + 1);
    }
    
    draft.history.push(JSON.parse(JSON.stringify(draft.design)));
    
    if (draft.history.length > MAX_HISTORY) {
        draft.history.shift(); // Remove oldest
    }
    
    draft.historyIndex = draft.history.length - 1;
}

export function useAppState() {
    const ctx = useContext(AppStateContext);
    if (!ctx) throw new Error('useAppState must be used within AppProvider');
    return ctx;
}

export function useAppDispatch() {
    const ctx = useContext(AppDispatchContext);
    if (!ctx) throw new Error('useAppDispatch must be used within AppProvider');
    return ctx;
}

export function AppProvider({
    children,
    vscode,
}: {
    children: React.ReactNode;
    vscode: { postMessage: (msg: any) => void };
}) {
    const initialDesign: DesignData = useMemo(
        () => ({
            form: {
                name: 'Form1',
                title: 'My App',
                size: { width: 800, height: 600 },
            },
            widgets: [],
            events: [],
        }),
        []
    );
    const initial: AppState = {
        vscode,
        design: initialDesign,
        selectedWidgetId: null,
        history: [initialDesign],
        historyIndex: 0,
    };
    const [state, dispatch] = useReducer(reducer, initial);

    return (
        <AppStateContext.Provider value={state}>
            <AppDispatchContext.Provider value={dispatch}>
                {children}
            </AppDispatchContext.Provider>
        </AppStateContext.Provider>
    );
}
