/// <reference types="@applicaster/applicaster-types" />
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
import React, { createContext, useEffect, useMemo } from "react";
import {
  actionExecutor as _actionExecutor,
  ActionResult,
} from "./ActionExecutor";
import {
  batchRemoveAllFromNamespaceForStorage,
  batchSave,
} from "../zappFrameworkUtils/localStorageHelper";
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";

import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
import { QUICK_BRICK_EVENTS } from "@applicaster/zapp-react-native-bridge/QuickBrick";
import { showConfirmationDialog } from "../alertUtils";
import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
import { createLogger } from "../logger";
import { ACTIVE_LAYOUT_ID_STORAGE_KEY } from "@applicaster/quick-brick-core/App/remoteContextReloader/consts";
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
import {
  EntryResolver,
  resolveObjectValues,
} from "../appUtils/contextKeysManager/contextResolver";
import { useNavigation } from "../reactHooks";

import {
  useContentTypes,
  usePickFromState,
} from "@applicaster/zapp-react-native-redux/hooks";
import { useSubscriberFor } from "../reactHooks/useSubscriberFor";
import { APP_EVENTS } from "../appUtils/events";
import {
  localStorageToggleFlag,
  sessionStorageToggleFlag,
} from "./StorageActions";

import { screenSetVariable, screenToggleFlag } from "./ScreenActions";

export const { log_error, log_info, log_debug } = createLogger({
  subsystem: "ActionExecutorContext",
  category: "General",
});

type ActionExecutorContextType = {
  registerAction: (
    type: string,
    handler: (
      action: ActionType,
      context?: Record<string, any>
    ) => Promise<ActionResult>
  ) => void;
  unregisterAction: (type: string) => void;
  handleAction: (
    action: ActionType,
    context?: Record<string, any>
  ) => Promise<ActionResult>;
  handleActions: (
    actions: ActionType[],
    context: Record<string, any>
  ) => Promise<ActionResult>;
  handleEntryActions: (
    entry: ZappEntry,
    context?: Record<string, any>
  ) => Promise<ActionResult>;
};
type Props = {
  children: React.ReactNode;
};

function findParentComponent(
  childId: string,
  parent: ZappRiver | ZappUIComponent
): ZappRiver | ZappUIComponent | null {
  for (const child of parent.ui_components) {
    if (child.id === childId) return parent;

    if (child.ui_components) {
      const found = findParentComponent(childId, child);
      if (found) return found;
    }
  }

  return null;
}

const prepareDefaultActions = (actionExecutor) => {
  actionExecutor.registerAction("localStorageSet", async (action) => {
    const namespaces = action.options.content;
    await batchSave(namespaces, localStorage);
    // TODO: Add support for ownershipKey and ownershipNamespace

    return ActionResult.Success;
  });

  actionExecutor.registerAction("sessionStorageSet", async (action) => {
    const namespaces = action.options.content;
    await batchSave(namespaces, sessionStorage);
    // TODO: Add support for ownershipKey and ownershipNamespace

    return ActionResult.Success;
  });

  actionExecutor.registerAction(
    "refreshComponent",
    async (_action, context) => {
      const dispatch = appStore.getDispatch();

      const dataSource =
        context?.component?.data?.source ||
        findParentComponent(context?.component.id, context?.screenData)?.data
          ?.source;

      log_info(`handleAction: refreshComponent for dataSource:${dataSource}`);

      // TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
      // TODO: handle focused item removal
      dispatch(
        loadPipesData(dataSource, {
          silentRefresh: false,
          clearCache: true,
          riverId: context?.screenData?.id,
        })
      );

      return ActionResult.Success;
    }
  );

  actionExecutor.registerAction("switchLayout", async (action) => {
    log_info("handleAction: switchLayout event");

    await localStorage.setItem(
      ACTIVE_LAYOUT_ID_STORAGE_KEY,
      action.options.layoutId
    );

    QuickBrickManager.sendQuickBrickEvent(QUICK_BRICK_EVENTS.FORCE_APP_RELOAD);

    return ActionResult.Success;
  });

  actionExecutor.registerAction("appRestart", async () => {
    log_info(`handleAction: ${QUICK_BRICK_EVENTS.FORCE_APP_RELOAD} event`);

    QuickBrickManager.sendQuickBrickEvent(QUICK_BRICK_EVENTS.FORCE_APP_RELOAD);

    return ActionResult.Success;
  });

  actionExecutor.registerAction("confirmDialog", async (action) => {
    log_info("handleAction: confirmDialog event");

    return new Promise<ActionResult>((resolve) => {
      showConfirmationDialog({
        ...action.options,
        confirmCompletion: () => resolve(ActionResult.Success),
        cancelCompletion: () => resolve(ActionResult.Cancel),
      });
    });
  });

  actionExecutor.registerAction("sendCloudEvent", async (action, context) => {
    try {
      const options = action.options;

      const entry = context?.entry || {};
      const entryResolver = new EntryResolver(entry);
      const screenData = context?.screenStateStore?.getState().data || {};
      const screenResolver = new EntryResolver(screenData || {});

      const data =
        options?.data && options.inflateData
          ? await resolveObjectValues(options.data, {
              entry: entryResolver,
              screen: screenResolver,
            })
          : options?.data || entry;

      const cloudEvent = await createCloudEvent({
        type: options.type || "com.applicaster.selector.action.v1",
        data,
        subject: options.subject || entry?.id,
      });

      log_info("handleAction: sendCloudEvent event", { cloudEvent });

      const { error, code } = await sendCloudEvent(cloudEvent, options.url);

      if (error) {
        log_error("sendCloudEvent: error sending cloud event", { error });

        return ActionResult.Error;
      }

      if (code && code >= 200 && code < 300 && !error) {
        log_info("sendCloudEvent: cloud event sent successfully");

        return ActionResult.Success;
      }
    } catch (error) {
      log_error("sendCloudEvent: error sending cloud event", {
        action,
        error,
      });
    }

    return ActionResult.Error;
  });

  actionExecutor.registerAction(
    "sessionStorageToggleFlag",
    async (
      action: ActionType,
      context?: Record<string, any>
    ): Promise<ActionResult> => {
      return await sessionStorageToggleFlag(context, action);
    }
  );

  actionExecutor.registerAction(
    "localStorageToggleFlag",
    async (
      action: ActionType,
      context?: Record<string, any>
    ): Promise<ActionResult> => {
      return await localStorageToggleFlag(context, action);
    }
  );

  actionExecutor.registerAction(
    "screenSetVariable",
    async (
      action: ActionType,
      context?: Record<string, any>
    ): Promise<ActionResult> => {
      const route = context?.screenRoute;
      const screenStateStore = context?.screenStateStore;

      await screenSetVariable(
        route,
        screenStateStore,
        { entry: context?.entry, options: action.options },
        action
      );

      return Promise.resolve(ActionResult.Success);
    }
  );

  actionExecutor.registerAction(
    "screenToggleFlag",
    async (
      action: ActionType,
      context?: Record<string, any>
    ): Promise<ActionResult> => {
      const screenRoute = context?.screenRoute;
      const screenStateStore = context?.screenStateStore;

      await screenToggleFlag(
        screenRoute,
        screenStateStore,
        { entry: context?.entry, options: action.options },
        action
      );

      return Promise.resolve(ActionResult.Success);
    }
  );
};

export const ActionExecutorContext =
  createContext<ActionExecutorContextType>(null);

export function withActionExecutor(Component) {
  prepareDefaultActions(_actionExecutor);

  return function ActionExecutorComponent(props: Props) {
    const navigator = useNavigation();
    const { rivers } = usePickFromState(["rivers"]);
    const contentTypes = useContentTypes();

    const handlers = useMemo(() => {
      return {
        unregisterAction: _actionExecutor.unregisterAction,
        registerAction: _actionExecutor.registerAction,
        handleAction: _actionExecutor.handleAction,
        handleActions: _actionExecutor.handleActions,
        handleEntryActions: _actionExecutor.handleEntryActions,
      };
    }, []);

    useSubscriberFor(APP_EVENTS.onLogout, () => {
      log_debug(
        "User profile: onLogout event received, clearing user profile data"
      );

      const userAccountKey = "user_account";
      void batchRemoveAllFromNamespaceForStorage(userAccountKey, localStorage);

      void batchRemoveAllFromNamespaceForStorage(
        userAccountKey,
        sessionStorage
      );
    });

    useEffect(() => {
      return _actionExecutor.registerAction(
        "navigateToScreen",

        async (action: ActionType, context?: Record<string, any>) => {
          const screenType = action.options?.typeMapping;

          if (!screenType) {
            log_error("navigateToScreen: typeMapping option is missing");

            return ActionResult.Error;
          }

          const navigationAction = action.options?.navigationAction;
          const entrySource = action.options?.entry;

          const entry = entrySource
            ? entrySource === "@{entry/}"
              ? context?.entry
              : entrySource
            : null;

          if (entry) {
            if (typeof entry !== "object") {
              log_error(
                `navigateToScreen: entry option is not an object, entry: ${entry}`
              );

              return ActionResult.Error;
            }

            log_info(
              `navigateToScreen: navigating to screen type: ${screenType} with entry id: ${entry.id}`
            );

            const overriddenEntry = {
              ...entry,
              type: {
                value: screenType,
              },
            };

            if (navigationAction === "push") {
              navigator.push(overriddenEntry);
            } else {
              navigator.replace(overriddenEntry);
            }

            return ActionResult.Success;
          }

          const screenId = contentTypes?.[screenType]?.screen_id || null;

          if (!screenId) {
            log_error(
              `navigateToScreen: can not resolve screen type mapping: ${screenType}`
            );

            return ActionResult.Error;
          }

          const river = rivers[screenId];

          if (!river) {
            log_error("navigateToScreen: can not resolve river");

            return ActionResult.Error;
          }

          context?.callback?.({ success: false, error: null, abort: true });

          if (navigationAction === "push") {
            navigator.push(river);
          } else {
            navigator.replace(river);
          }

          return ActionResult.Success;
        }
      );
    }, [navigator, rivers, contentTypes]);

    return (
      <ActionExecutorContext.Provider value={handlers}>
        <Component {...props} />
      </ActionExecutorContext.Provider>
    );
  };
}

// TODO: Please use direct import from the ActionExecutor.ts
export const actionExecutor = _actionExecutor;
