import * as React from 'react';
import {
  Component,
  Dispatch,
  ErrorInfo,
  useState,
  useEffect,
  useRef,
  useCallback,
  ReactNode,
  SetStateAction,
  createContext,
  useContext,
  FC,
} from 'react';
import { Config, IData, IMedia, IWidgetEvents } from '../types';

import { Debug } from '../debug';

export const DEFAULT_CONFIG: Config = {
  classPrefix: 'adl-wdgt',
  shopThisLookText: 'SHOP THE LOOK',
  moreLikeThisText: 'More like this',
};

export interface StoreState {
  exportState: (getState: () => StoreState, setter: StateSetter) => void;
  post: IMedia | null;
  data: IData;
  forceMobile: boolean;
  isMobile: boolean;
  sliderPosition: number;
  root: Element | null;
  config: Config;
  triggerEvent: IWidgetEvents['triggerEvent'];
  hasSubscribed: IWidgetEvents['hasSubscribed'];
  setStoreState: Dispatch<SetStateAction<StoreState>>;
  canSlideLeft: boolean;
  canSlideRight: boolean;
  currentPosition: number;
  widgetWidth: number;
  productImageWidths: Record<string, number>;
  setProductImageWidth: (productId: string, width: number) => void;
  getProductImageWidth: (productId: string) => number;
  unlockedShouldersForCarouselPostId: string | null;
  productListUniformMode: boolean; // In product list, when nbItems < content size, all items should be the same size
}

export interface StoreProps {
  state: Partial<StoreState>;
  children: ReactNode;
}

export const initial: StoreState = {
  exportState: () => {},
  post: null,
  data: {} as IData,
  forceMobile: false,
  isMobile: false,
  root: null,
  sliderPosition: 0,
  config: DEFAULT_CONFIG,
  triggerEvent: () => {},
  hasSubscribed: () => false,
  setStoreState: () => {},
  canSlideLeft: false,
  canSlideRight: true,
  currentPosition: 0,
  widgetWidth: 0, // will be set by the Widget component
  productImageWidths: {},
  setProductImageWidth: () => {},
  getProductImageWidth: () => 0,
  unlockedShouldersForCarouselPostId: null,
  productListUniformMode: false,
};

export const StoreContext = createContext<StoreState>(initial);

export type StateSetter = Dispatch<SetStateAction<StoreState>>;

export const useStore = () => useContext(StoreContext);

interface ErrorBoundaryProps {
  children: ReactNode;
}

interface ErrorBoundaryState {
  hasError: boolean;
}

/**
 * React error boundary scoped to the widget component tree.
 */
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(): ErrorBoundaryState {
    return { hasError: true };
  }

  componentDidCatch(error: Error, info: ErrorInfo): void {
    Debug.error(error, info.componentStack);
  }

  render(): ReactNode {
    if (this.state.hasError) {
      return null;
    }
    return this.props.children;
  }
}

export const StoreProvider: FC<StoreProps> = ({ state, children }) => {
  const [storeState, setStoreState] = useState<StoreState>(() => {
    // Create initial state first
    const initialState = {
      ...initial,
      ...state,
    };

    // Then add setter function
    return {
      ...initialState,
      setStoreState: (newState: SetStateAction<StoreState>) => {
        setStoreState(
          typeof newState === 'function' ? newState : (current) => ({ ...current, ...newState }),
        );
      },
    };
  });

  const productImageWidthsRef = useRef<Map<string, number>>(new Map());

  // Create product image width management functions that reference current state
    const setProductImageWidth = useCallback((productId: string, width: number) => {
    productImageWidthsRef.current.set(productId, width);
  }, []);

  const getProductImageWidth = (productId: string) => {
    return productImageWidthsRef.current.get(productId) || 0;
  };

  // Update store state with the functions
  const contextValue = {
    ...storeState,
    setProductImageWidth,
    getProductImageWidth,
  };

  // Move effects after state initialization
  useEffect(() => {
    if (state.exportState) {
      state.exportState(() => contextValue, setStoreState);
    }
  }, [state.exportState, contextValue]);

  useEffect(() => {
    setStoreState((current) => ({
      ...current,
      ...state,
    }));
  }, [state]);

  return (
    <ErrorBoundary>
      <StoreContext.Provider value={contextValue}>{children}</StoreContext.Provider>
    </ErrorBoundary>
  );
};

// Hook for components to consume store
export const useStoreComponent = <P extends object>(props: P): [P, StoreState, StateSetter] => {
  const store = useStore();
  return [props, store, store.setStoreState];
};
