"use client";

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";

import type { FunnelAnswers } from "./types";
import {
  saveFunnelState,
  loadFunnelState,
  clearExpiredFunnelStates,
  clearFunnelState,
} from "./storage";

interface FunnelRuntimeState {
  answers: FunnelAnswers;
  history: string[];
  version: number;
}

interface FunnelContextValue {
  state: Record<string, FunnelRuntimeState>;
  registerScreenVisit: (funnelId: string, screenId: string) => void;
  updateScreenAnswers: (
    funnelId: string,
    screenId: string,
    answers: string[]
  ) => void;
  resetFunnel: (funnelId: string) => void;
}

const DEFAULT_RUNTIME_STATE: FunnelRuntimeState = {
  answers: {},
  history: [],
  version: 0,
};

function createInitialState(): FunnelRuntimeState {
  return {
    answers: {},
    history: [],
    version: 0,
  };
}

function arraysEqual(left: string[] | undefined, right: string[]): boolean {
  if (!left && right.length === 0) {
    return true;
  }
  if (!left || left.length !== right.length) {
    return false;
  }

  return left.every((value, index) => value === right[index]);
}

const FunnelContext = createContext<FunnelContextValue | undefined>(undefined);

interface FunnelProviderProps {
  children: ReactNode;
}

export function FunnelProvider({ children }: FunnelProviderProps) {
  const [state, setState] = useState<Record<string, FunnelRuntimeState>>({});
  const [isHydrated, setIsHydrated] = useState(false);

  // Восстанавливаем состояние из localStorage при монтировании
  useEffect(() => {
    // Очищаем устаревшие состояния
    clearExpiredFunnelStates();
    setIsHydrated(true);
  }, []);

  // Автоматически сохраняем состояние при изменениях
  useEffect(() => {
    if (!isHydrated) {
      return;
    }

    // Сохраняем состояние каждой воронки
    for (const [funnelId, funnelState] of Object.entries(state)) {
      saveFunnelState(funnelId, funnelState);
    }
  }, [state, isHydrated]);

  const registerScreenVisit = useCallback((funnelId: string, screenId: string) => {
    setState((prev) => {
      // Пытаемся загрузить сохраненное состояние, если его еще нет в памяти
      let previousState = prev[funnelId];
      if (!previousState) {
        const loaded = loadFunnelState(funnelId);
        previousState = loaded ?? createInitialState();
      }
      const history = previousState.history ?? [];

      let nextHistory = history;

      if (history.length === 0 || history[history.length - 1] !== screenId) {
        const existingIndex = history.indexOf(screenId);

        if (existingIndex === -1) {
          nextHistory = [...history, screenId];
        } else if (existingIndex !== history.length - 1) {
          nextHistory = history.slice(0, existingIndex + 1);
        }
      }

      if (nextHistory === history) {
        return prev;
      }

      return {
        ...prev,
        [funnelId]: {
          ...previousState,
          history: nextHistory,
        },
      };
    });
  }, []);

  const updateScreenAnswers = useCallback(
    (funnelId: string, screenId: string, answers: string[]) => {
      setState((prev) => {
        // Пытаемся загрузить сохраненное состояние, если его еще нет в памяти
        let previousState = prev[funnelId];
        if (!previousState) {
          const loaded = loadFunnelState(funnelId);
          previousState = loaded ?? createInitialState();
        }
        const previousAnswers = previousState.answers ?? {};
        const existingAnswers = previousAnswers[screenId];

        if (answers.length === 0) {
          if (!existingAnswers) {
            return prev;
          }

          const rest = { ...previousAnswers };
          delete rest[screenId];

          return {
            ...prev,
            [funnelId]: {
              ...previousState,
              answers: rest,
            },
          };
        }

        if (arraysEqual(existingAnswers, answers)) {
          return prev;
        }

        return {
          ...prev,
          [funnelId]: {
            ...previousState,
            answers: {
              ...previousAnswers,
              [screenId]: answers,
            },
          },
        };
      });
    },
    []
  );

  const resetFunnel = useCallback((funnelId: string) => {
    setState((prev) => {
      const previousState = prev[funnelId];
      if (!previousState) {
        return prev;
      }

      if (
        previousState.history.length === 0 &&
        Object.keys(previousState.answers).length === 0
      ) {
        return prev;
      }

      // Очищаем состояние из localStorage
      clearFunnelState(funnelId);

      return {
        ...prev,
        [funnelId]: {
          ...createInitialState(),
          version: (previousState.version ?? 0) + 1,
        },
      };
    });
  }, []);

  const value = useMemo<FunnelContextValue>(
    () => ({ state, registerScreenVisit, updateScreenAnswers, resetFunnel }),
    [state, registerScreenVisit, updateScreenAnswers, resetFunnel]
  );

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

function useFunnelContext() {
  const context = useContext(FunnelContext);
  if (!context) {
    throw new Error("useFunnelContext must be used within a FunnelProvider");
  }
  return context;
}

export function useFunnelRuntime(funnelId: string) {
  const { state, registerScreenVisit, updateScreenAnswers, resetFunnel } =
    useFunnelContext();

  const runtime = state[funnelId] ?? DEFAULT_RUNTIME_STATE;

  const setAnswers = useCallback(
    (screenId: string, answers: string[]) => {
      updateScreenAnswers(funnelId, screenId, answers);
    },
    [funnelId, updateScreenAnswers]
  );

  const register = useCallback(
    (screenId: string) => {
      registerScreenVisit(funnelId, screenId);
    },
    [funnelId, registerScreenVisit]
  );

  const reset = useCallback(() => {
    resetFunnel(funnelId);
  }, [funnelId, resetFunnel]);

  return {
    answers: runtime.answers,
    history: runtime.history,
    version: runtime.version,
    setAnswers,
    registerScreen: register,
    reset,
  };
}
