import axios, { Canceler } from "axios";
import { ComponentProps, ComponentType, JSX, PureComponent } from "react";

import {
  CharacterTheme,
  ContainerManager,
  InfusionChoice,
  InventoryManager,
  Item,
  ItemUtils,
  RuleData,
} from "@dndbeyond/character-rules-engine/es";

import DataLoadingStatusEnum from "../../../../constants/DataLoadingStatusEnum";
import { AppLoggerUtils } from "../../../../utils";

function getDisplayName(WrappedComponent: ComponentType) {
  return WrappedComponent.displayName || WrappedComponent.name || "Component";
}

export interface RequiredWithAvailableItemsProps {
  inventoryManager: InventoryManager;
  theme?: CharacterTheme;
  infusionChoice: InfusionChoice;
  onItemSelected: (item: Item) => void;
  ruleData: RuleData;
}
interface State {
  loadingStatus: DataLoadingStatusEnum;
  availableItems: Array<Item>;
}
export function withAvailableItems<
  C extends ComponentType<ComponentProps<C>>,
  ResolvedProps = JSX.LibraryManagedAttributes<C, ComponentProps<C>>
>(WrappedComponent) {
  return class withAvailableItems extends PureComponent<
    ResolvedProps & RequiredWithAvailableItemsProps,
    State
  > {
    static displayName = `withAvailableItems(${getDisplayName(
      WrappedComponent
    )})`;

    loadAvailableItemsCanceler: null | Canceler = null;

    constructor(props) {
      super(props);

      this.state = {
        loadingStatus: DataLoadingStatusEnum.NOT_LOADED,
        availableItems: [],
      };
    }

    componentDidMount() {
      const { loadingStatus } = this.state;
      const { inventoryManager } = this.props;

      if (loadingStatus === DataLoadingStatusEnum.NOT_LOADED) {
        this.setState({
          loadingStatus: DataLoadingStatusEnum.LOADING,
        });

        inventoryManager
          .getInventoryShoppe({
            onSuccess: (shoppeContainer: ContainerManager) => {
              const storeItems = shoppeContainer
                .getInventoryItems()
                .items.map((item) => item.getItem());

              this.setState({
                availableItems: storeItems,
                loadingStatus: DataLoadingStatusEnum.LOADED,
              });
            },
            additionalApiConfig: {
              cancelToken: new axios.CancelToken((c) => {
                this.loadAvailableItemsCanceler = c;
              }),
            },
          })
          .then((res) => {
            this.loadAvailableItemsCanceler = null;
            return res;
          })
          .catch(AppLoggerUtils.handleAdhocApiError);
      } else {
        this.setState({
          loadingStatus: DataLoadingStatusEnum.LOADED,
        });
      }
    }

    componentWillUnmount(): void {
      if (this.loadAvailableItemsCanceler !== null) {
        this.loadAvailableItemsCanceler();
      }
    }

    render() {
      const { loadingStatus, availableItems } = this.state;

      let itemCanBeAddedLookup: Record<number, boolean> = availableItems.reduce(
        (acc, item) => {
          acc[ItemUtils.getId(item)] = ItemUtils.canBeAddedToInventory(item);
          return acc;
        },
        {}
      );

      return (
        <WrappedComponent
          items={availableItems}
          itemCanBeAddedLookup={itemCanBeAddedLookup}
          itemLoadingStatus={loadingStatus}
          {...(this.props as JSX.LibraryManagedAttributes<
            C,
            ComponentProps<C>
          >)}
        />
      );
    }
  };
}

export default withAvailableItems;
