/**
 * Central canvas state hook.
 *
 * Architecture:
 * - Current state lives in a useReducer (triggers re-renders on mutation).
 * - History (for undo / redo) lives in a useRef (never triggers re-renders).
 * - Every mutation dispatches through the reducer, which also pushes a snapshot
 *   onto the undo stack.
 */

import { useCallback, useReducer, useRef } from 'react';
import type {
  ArtboardConfig,
  CanvasObject,
  CanvasState,
} from '../types/canvas';
import type { TraceMetadata } from '../types/engine';

// -- Action types -------------------------------------------------------------

type CanvasAction =
  | { type: 'ADD_OBJECT'; object: CanvasObject }
  | { type: 'REMOVE_OBJECT'; id: string }
  | { type: 'UPDATE_OBJECT'; id: string; changes: Partial<CanvasObject> }
  | { type: 'SELECT_OBJECTS'; ids: string[] }
  | { type: 'DESELECT_ALL' }
  | { type: 'REORDER_OBJECT'; id: string; toIndex: number }
  | { type: 'TOGGLE_VISIBILITY'; id: string }
  | { type: 'TOGGLE_LOCK'; id: string }
  | { type: 'SET_ARTBOARD'; config: ArtboardConfig }
  | { type: 'RESTORE'; state: CanvasState };

// -- Reducer ------------------------------------------------------------------

function canvasReducer(state: CanvasState, action: CanvasAction): CanvasState {
  switch (action.type) {
    case 'ADD_OBJECT':
      return { ...state, objects: [...state.objects, action.object] };

    case 'REMOVE_OBJECT':
      return {
        ...state,
        objects: state.objects.filter((o) => o.id !== action.id),
        selectedIds: state.selectedIds.filter((sid) => sid !== action.id),
      };

    case 'UPDATE_OBJECT':
      return {
        ...state,
        objects: state.objects.map((o) =>
          o.id === action.id ? ({ ...o, ...action.changes } as CanvasObject) : o,
        ),
      };

    case 'SELECT_OBJECTS':
      return { ...state, selectedIds: action.ids };

    case 'DESELECT_ALL':
      return { ...state, selectedIds: [] };

    case 'REORDER_OBJECT': {
      const idx = state.objects.findIndex((o) => o.id === action.id);
      if (idx === -1) return state;
      const next = [...state.objects];
      const [item] = next.splice(idx, 1);
      const clampedIndex = Math.max(0, Math.min(action.toIndex, next.length));
      next.splice(clampedIndex, 0, item);
      return { ...state, objects: next };
    }

    case 'TOGGLE_VISIBILITY':
      return {
        ...state,
        objects: state.objects.map((o) =>
          o.id === action.id ? ({ ...o, visible: !o.visible } as CanvasObject) : o,
        ),
      };

    case 'TOGGLE_LOCK':
      return {
        ...state,
        objects: state.objects.map((o) =>
          o.id === action.id ? ({ ...o, locked: !o.locked } as CanvasObject) : o,
        ),
      };

    case 'SET_ARTBOARD':
      return { ...state, artboard: action.config };

    case 'RESTORE':
      return action.state;

    default:
      return state;
  }
}

// -- History ref type ---------------------------------------------------------

interface HistoryRef {
  undoStack: CanvasState[];
  redoStack: CanvasState[];
}

const MAX_HISTORY = 50;

// -- Hook ---------------------------------------------------------------------

export interface UseCanvasStateReturn {
  state: CanvasState;

  addObject: (object: CanvasObject) => void;
  removeObject: (id: string) => void;
  updateObject: (id: string, changes: Partial<CanvasObject>) => void;
  selectObjects: (ids: string[]) => void;
  deselectAll: () => void;
  reorderObject: (id: string, toIndex: number) => void;
  toggleVisibility: (id: string) => void;
  toggleLock: (id: string) => void;
  setArtboard: (config: ArtboardConfig) => void;

  undo: () => void;
  redo: () => void;
  canUndo: boolean;
  canRedo: boolean;
}

export function useCanvasState(
  traceMetadata: TraceMetadata | null = null,
): UseCanvasStateReturn {
  const initialState: CanvasState = {
    objects: [],
    selectedIds: [],
    artboard: null,
    traceMetadata,
  };

  const [state, dispatch] = useReducer(canvasReducer, initialState);
  const historyRef = useRef<HistoryRef>({ undoStack: [], redoStack: [] });

  // Push a snapshot before every mutation
  const pushHistory = useCallback(
    (currentState: CanvasState) => {
      const h = historyRef.current;
      h.undoStack = [...h.undoStack.slice(-(MAX_HISTORY - 1)), currentState];
      h.redoStack = []; // clear redo on new action
    },
    [],
  );

  // -- Mutators (each pushes history then dispatches) -----------------------

  const addObject = useCallback(
    (object: CanvasObject) => {
      pushHistory(state);
      dispatch({ type: 'ADD_OBJECT', object });
    },
    [state, pushHistory],
  );

  const removeObject = useCallback(
    (id: string) => {
      pushHistory(state);
      dispatch({ type: 'REMOVE_OBJECT', id });
    },
    [state, pushHistory],
  );

  const updateObject = useCallback(
    (id: string, changes: Partial<CanvasObject>) => {
      pushHistory(state);
      dispatch({ type: 'UPDATE_OBJECT', id, changes });
    },
    [state, pushHistory],
  );

  const selectObjects = useCallback(
    (ids: string[]) => {
      dispatch({ type: 'SELECT_OBJECTS', ids });
    },
    [],
  );

  const deselectAll = useCallback(() => {
    dispatch({ type: 'DESELECT_ALL' });
  }, []);

  const reorderObject = useCallback(
    (id: string, toIndex: number) => {
      pushHistory(state);
      dispatch({ type: 'REORDER_OBJECT', id, toIndex });
    },
    [state, pushHistory],
  );

  const toggleVisibility = useCallback(
    (id: string) => {
      pushHistory(state);
      dispatch({ type: 'TOGGLE_VISIBILITY', id });
    },
    [state, pushHistory],
  );

  const toggleLock = useCallback(
    (id: string) => {
      pushHistory(state);
      dispatch({ type: 'TOGGLE_LOCK', id });
    },
    [state, pushHistory],
  );

  const setArtboard = useCallback(
    (config: ArtboardConfig) => {
      pushHistory(state);
      dispatch({ type: 'SET_ARTBOARD', config });
    },
    [state, pushHistory],
  );

  // -- Undo / Redo ----------------------------------------------------------

  const undo = useCallback(() => {
    const h = historyRef.current;
    if (h.undoStack.length === 0) return;
    const prev = h.undoStack[h.undoStack.length - 1];
    h.undoStack = h.undoStack.slice(0, -1);
    h.redoStack = [...h.redoStack, state];
    dispatch({ type: 'RESTORE', state: prev });
  }, [state]);

  const redo = useCallback(() => {
    const h = historyRef.current;
    if (h.redoStack.length === 0) return;
    const next = h.redoStack[h.redoStack.length - 1];
    h.redoStack = h.redoStack.slice(0, -1);
    h.undoStack = [...h.undoStack, state];
    dispatch({ type: 'RESTORE', state: next });
  }, [state]);

  return {
    state,
    addObject,
    removeObject,
    updateObject,
    selectObjects,
    deselectAll,
    reorderObject,
    toggleVisibility,
    toggleLock,
    setArtboard,
    undo,
    redo,
    canUndo: historyRef.current.undoStack.length > 0,
    canRedo: historyRef.current.redoStack.length > 0,
  };
}
