import clsx from "clsx";
import { FC, HTMLAttributes, useContext, useEffect, useState } from "react";
import { useSelector } from "react-redux";

import {
  ApiAdapterUtils,
  apiCreatorSelectors,
  CharClass,
  Choice,
  ChoiceUtils,
  ClassDefinitionContract,
  ClassFeature,
  ClassFeatureUtils,
  ClassUtils,
  Constants,
  ContainerManager,
  Feat,
  FeatUtils,
} from "@dndbeyond/character-rules-engine";

import { Heading } from "~/subApps/sheet/components/Sidebar/components/Heading";
import { InventoryManagerContext } from "~/tools/js/Shared/managers/InventoryManagerContext";
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
import { AppLoggerUtils } from "~/tools/js/Shared/utils";
import LoadingPlaceholder from "~/tools/js/smartComponents/LoadingPlaceholder";
import { DataLoadingStatusEnum } from "~/tools/js/smartComponents/componentConstants";

import { FeatureChoice } from "./FeatureChoice";
import styles from "./styles.module.css";

export interface FeatureChoicesProps extends HTMLAttributes<HTMLDivElement> {
  choices: Array<Choice>;
  showHeading?: boolean;
  onChoiceChange: (
    choiceId: string,
    type: number,
    value: any,
    parentChoiceId: string | null
  ) => void;
  collapseDescription?: boolean;
  charClass?: CharClass;
  classFeature?: ClassFeature;
  shouldFetch?: boolean; // Optional prop to control fetching behavior
  itemShoppe?: ContainerManager | null; // Optional prop for cases where the itemShoppe is fetched from the parent component (only used if the Feature Choices component is rendered multiple times in the same parent - e.g. in the Item Plans Manage Pane)
}

export const FeatureChoices: FC<FeatureChoicesProps> = ({
  className,
  choices,
  showHeading,
  onChoiceChange,
  collapseDescription,
  charClass,
  classFeature,
  shouldFetch = true, // Default to true if not provided
  itemShoppe = null,
  ...props
}) => {
  const isReadonly = useSelector(appEnvSelectors.getIsReadonly);
  const loadAvailableFeats = useSelector(
    apiCreatorSelectors.makeLoadAvailableFeats
  );
  const loadAvailableSubclasses = useSelector(
    apiCreatorSelectors.makeLoadAvailableSubclasses
  );

  const { inventoryManager } = useContext(InventoryManagerContext);

  const [subclassData, setSubclassData] = useState<
    Array<ClassDefinitionContract>
  >([]);
  const [featsData, setFeatsData] = useState<Array<Feat>>([]);
  const [localItemShoppe, setLocalItemShoppe] =
    useState<ContainerManager | null>(itemShoppe);
  const [featLoadingStatus, setFeatLoadingStatus] =
    useState<DataLoadingStatusEnum>(DataLoadingStatusEnum.NOT_INITIALIZED);
  const [subclassLoadingStatus, setSubclassLoadingStatus] =
    useState<DataLoadingStatusEnum>(DataLoadingStatusEnum.NOT_INITIALIZED);
  const [itemsLoadingStatus, setItemsLoadingStatus] =
    useState<DataLoadingStatusEnum>(DataLoadingStatusEnum.NOT_INITIALIZED);

  const hasFeatChoice = choices.some(
    (choice) =>
      ChoiceUtils.getType(choice) ===
      Constants.BuilderChoiceTypeEnum.FEAT_CHOICE_OPTION
  );

  const hasSubclassChoice = choices.some(
    (choice) =>
      ChoiceUtils.getType(choice) ===
      Constants.BuilderChoiceTypeEnum.SUB_CLASS_OPTION
  );

  const hasItemMappings = classFeature
    ? ClassFeatureUtils.getHasItemMappings(classFeature)
    : false;

  useEffect(() => {
    const abortController = new AbortController();

    if (
      hasFeatChoice &&
      shouldFetch &&
      featLoadingStatus !== DataLoadingStatusEnum.LOADED
    ) {
      setFeatLoadingStatus(DataLoadingStatusEnum.LOADING);

      loadAvailableFeats({
        signal: abortController.signal,
      })
        .then((response) => {
          let featsData: Array<Feat> = [];

          const data = ApiAdapterUtils.getResponseData(response);
          if (data !== null) {
            featsData = data.map((definition) =>
              FeatUtils.simulateFeat(definition)
            );
          }

          setFeatsData(featsData);
          setFeatLoadingStatus(DataLoadingStatusEnum.LOADED);
        })
        .catch((e) => {
          if (abortController.signal.aborted) return;
          AppLoggerUtils.handleAdhocApiError(e);
        });
    }

    if (
      charClass &&
      hasSubclassChoice &&
      shouldFetch &&
      subclassLoadingStatus !== DataLoadingStatusEnum.LOADED
    ) {
      setSubclassLoadingStatus(DataLoadingStatusEnum.LOADING);
      loadAvailableSubclasses(ClassUtils.getId(charClass), {
        signal: abortController.signal,
      })
        .then((response) => {
          let subclassData: Array<ClassDefinitionContract> = [];

          const data = ApiAdapterUtils.getResponseData(response);
          if (data !== null) {
            subclassData = data;
          }

          setSubclassData(subclassData);
          setSubclassLoadingStatus(DataLoadingStatusEnum.LOADED);
        })
        .catch((e) => {
          if (abortController.signal.aborted) return;
          AppLoggerUtils.handleAdhocApiError(e);
        });
    }

    if (
      localItemShoppe === null &&
      hasItemMappings &&
      shouldFetch &&
      itemsLoadingStatus !== DataLoadingStatusEnum.LOADED
    ) {
      if (inventoryManager) {
        setItemsLoadingStatus(DataLoadingStatusEnum.LOADING);

        inventoryManager
          .getInventoryShoppe({
            onSuccess: (shoppeContainer: ContainerManager) => {
              setLocalItemShoppe(shoppeContainer);
              setItemsLoadingStatus(DataLoadingStatusEnum.LOADED);
            },
            additionalApiConfig: {
              signal: abortController.signal,
            },
          })
          .catch((e) => {
            if (abortController.signal.aborted) return;
            AppLoggerUtils.handleAdhocApiError(e);
          });
      } else {
        setItemsLoadingStatus(DataLoadingStatusEnum.LOADED);
      }
    }

    return () => {
      abortController.abort();
    };
  }, [
    hasFeatChoice,
    hasSubclassChoice,
    hasItemMappings,
    subclassLoadingStatus,
    featLoadingStatus,
    itemsLoadingStatus,
    charClass,
    loadAvailableFeats,
    loadAvailableSubclasses,
    shouldFetch,
    inventoryManager,
  ]);

  const isDataLoaded = (): boolean => {
    if (hasSubclassChoice) {
      if (subclassLoadingStatus !== DataLoadingStatusEnum.LOADED) {
        return false;
      }
    }
    if (hasFeatChoice) {
      if (featLoadingStatus !== DataLoadingStatusEnum.LOADED) {
        return false;
      }
    }
    if (itemShoppe !== null) {
      return true;
    } else if (hasItemMappings) {
      if (itemsLoadingStatus !== DataLoadingStatusEnum.LOADED) {
        return false;
      }
    }

    return true;
  };

  return !isReadonly && choices.length > 0 ? (
    <div className={clsx([styles.featureChoices, className])} {...props}>
      <>
        {showHeading && (
          <Heading className={styles.heading}>{`Option${
            choices.length > 1 ? "s" : ""
          }`}</Heading>
        )}
        {isDataLoaded() ? (
          choices.map((choice) => (
            <FeatureChoice
              charClass={charClass}
              choice={choice}
              featsData={featsData}
              itemShoppe={localItemShoppe}
              subclassData={subclassData}
              className={styles.choice}
              key={ChoiceUtils.getId(choice)}
              onChoiceChange={onChoiceChange}
              collapseDescription={collapseDescription}
            />
          ))
        ) : (
          <LoadingPlaceholder />
        )}
      </>
    </div>
  ) : null;
};
