import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { EventHandler, PagesData, ComponentNode } from '../../common/types/component.types';
import type { ComponentEventType } from '../../common/registry/types';
import { interpolateText } from '../../common/utils/stateBinding';
import { updateStateAtPath } from '../../common/state/statePathUtils';
import { parseScopedPath } from '../../common/state/scopedPath';
import { EventExecutor } from '../../common/events';
import { RuntimeContext } from '../../common/runtime';
import type { RuntimeContextValue } from '../../common/runtime';

const isPathValidForNested = (path: string): boolean => {
  if (!path || typeof path !== 'string') return false;
  const trimmed = path.trim();
  if (!trimmed || trimmed.startsWith('.') || trimmed.endsWith('.') || trimmed.includes('..')) return false;
  const pathPattern = /^[a-zA-Z0-9_. -]+$/;
  return pathPattern.test(trimmed);
};

const safeObject = (value: unknown): Record<string, any> =>
  value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, any>) : {};

const deserializePageStateSnapshot = (pagesData: PagesData): Record<string, Record<string, any>> => {
  const raw = pagesData.pageStateData;
  if (!raw || typeof raw !== 'object') return {};

  const byPageId: Record<string, Record<string, any>> = {};
  for (const [pageId, stateData] of Object.entries(raw)) {
    const state = (stateData as any)?.state;
    byPageId[pageId] = safeObject(state);
  }
  return byPageId;
};

export interface RendererRuntimeProviderProps {
  children: React.ReactNode;
  pagesData: PagesData;
  workflow?: { executeUrl: string };
  currentRoute: string;
  onNavigate: (route: string) => void;
}

export const RendererRuntimeProvider: React.FC<RendererRuntimeProviderProps> = ({
  children,
  pagesData,
  workflow,
  currentRoute,
  onNavigate,
}) => {
  const pages = pagesData.pages ?? [];

  const [globalState, setGlobalState] = useState<Record<string, any>>(() => safeObject(pagesData.stateData?.state));
  const [pageStateById, setPageStateById] = useState<Record<string, Record<string, any>>>(() =>
    deserializePageStateSnapshot(pagesData)
  );

  const currentPageId = useMemo(() => {
    const matched = pages.find((p) => p.route === currentRoute);
    if (matched) return matched.id;
    const fallback = pages.find((p) => p.isDefault) || pages[0];
    return fallback?.id ?? null;
  }, [currentRoute, pages]);

  const currentPageState = useMemo(() => {
    if (!currentPageId) return {};
    return pageStateById[currentPageId] ?? {};
  }, [currentPageId, pageStateById]);

  const scopedState = useMemo(() => {
    return {
      page: currentPageState,
      global: globalState,
    };
  }, [globalState, currentPageState]);

  const setScopedStateValueDirect = useCallback(
    (path: string, value: any): boolean => {
      let parsedPath;
      try {
        parsedPath = parseScopedPath(path);
      } catch (error) {
        console.error(error);
        return false;
      }

      if (!isPathValidForNested(parsedPath.relativePath)) return false;
      const parts = parsedPath.relativePath.split('.');

      if (parsedPath.scope === 'page') {
        if (!currentPageId) return false;
        setPageStateById((prev) => {
          const current = prev[currentPageId] ?? {};
          const next = updateStateAtPath(current, parts, value);
          return { ...prev, [currentPageId]: next };
        });
        return true;
      }

      setGlobalState((prev) => updateStateAtPath(prev, parts, value));
      return true;
    },
    [currentPageId]
  );

  const setScopedStateValue = useCallback(
    (path: string, value: any): boolean => {
      let parsedPath;
      try {
        parsedPath = parseScopedPath(path);
      } catch (error) {
        console.error(error);
        return false;
      }

      if (!isPathValidForNested(parsedPath.relativePath)) return false;

      let processedValue: any = value;
      if (typeof value === 'string') {
        const trimmedValue = value.trim();
        if (trimmedValue.toLowerCase() === 'true') {
          processedValue = true;
        } else if (trimmedValue.toLowerCase() === 'false') {
          processedValue = false;
        } else if (trimmedValue === 'null') {
          processedValue = null;
        } else {
          const numberMatch = trimmedValue.match(/^-?\d+\.?\d*$/);
          if (numberMatch) {
            processedValue = Number(trimmedValue);
            if (isNaN(processedValue)) {
              processedValue = value;
            }
          } else {
            processedValue = value;
          }
        }
      }

      return setScopedStateValueDirect(parsedPath.fullPath, processedValue);
    },
    [setScopedStateValueDirect]
  );

  const setScopedStateValueRaw = useCallback(
    (key: string, value: any): boolean => {
      let parsedPath;
      try {
        parsedPath = parseScopedPath(key);
      } catch (error) {
        console.error(error);
        return false;
      }

      if (!parsedPath.relativePath) return false;

      if (parsedPath.scope === 'page') {
        if (!currentPageId) return false;
        setPageStateById((prev) => {
          const current = prev[currentPageId] ?? {};
          return { ...prev, [currentPageId]: { ...current, [parsedPath.relativePath]: value } };
        });
        return true;
      }

      setGlobalState((prev) => ({ ...prev, [parsedPath.relativePath]: value }));
      return true;
    },
    [currentPageId]
  );

  const navigateToPage = useCallback(
    (pageId: string) => {
      const page = pages.find((p) => p.id === pageId);
      if (page) {
        onNavigate(page.route);
      }
    },
    [onNavigate, pages]
  );

  const eventExecutor = useMemo(() => new EventExecutor({ workflow }), [workflow?.executeUrl]);

  useEffect(() => {
    eventExecutor.setContext({
      setStateValue: setScopedStateValue,
      setStateValueRaw: setScopedStateValueRaw,
      setStateValueDirect: setScopedStateValueDirect,
      navigateToPage,
      pages,
      state: scopedState,
    });
  }, [
    eventExecutor,
    navigateToPage,
    pages,
    scopedState,
    setScopedStateValue,
    setScopedStateValueDirect,
    setScopedStateValueRaw,
  ]);

  const resolveText = useCallback(
    (text: string): string => {
      return interpolateText(text, scopedState);
    },
    [scopedState]
  );

  const executeHandlers = useCallback(
    async (handlers: EventHandler[]) => {
      if (handlers.length === 0) return;
      await eventExecutor.executeWithErrorHandling(handlers);
    },
    [eventExecutor]
  );

  const dispatchEvent = useCallback(
    async (_eventType: ComponentEventType, component: ComponentNode, handlersKey = 'eventHandlers') => {
      const handlers = (component.props?.[handlersKey] as EventHandler[]) || [];
      if (handlers.length === 0) return;
      await eventExecutor.executeWithErrorHandling(handlers);
    },
    [eventExecutor]
  );

  const setStateValue = useCallback(
    (path: string, value: any) => {
      setScopedStateValueDirect(path, value);
    },
    [setScopedStateValueDirect]
  );

  const contextValue = useMemo<RuntimeContextValue>(
    () => ({
      state: scopedState,
      resolveText,
      executeHandlers,
      dispatchEvent,
      pages,
      navigateToPage,
      setStateValue,
      previewMode: true,
    }),
    [dispatchEvent, executeHandlers, navigateToPage, pages, resolveText, scopedState, setStateValue]
  );

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