import React from "react";
import { useContext } from "react";
import { connect, DispatchProp } from "react-redux";
import { AnyAction } from "redux";

import {
  rulesEngineSelectors,
  serviceDataActions,
  Constants,
  Container,
  ContainerUtils,
  InventoryManager,
  InfusionUtils,
  Item,
  ItemUtils,
  RuleData,
  GroupedMenuOption,
  serviceDataSelectors,
  PartyInfo,
  ItemPlan,
  ContainerLookup,
} from "@dndbeyond/character-rules-engine/es";

import { Button } from "~/components/Button";
import { Popover } from "~/components/Popover";
import { PopoverContent } from "~/components/PopoverContent";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { ButtonWithMenu } from "~/subApps/sheet/components/Sidebar/components/ButtonWithMenu";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";

import SimpleQuantity from "../../components/SimpleQuantity";
import { InventoryManagerContext } from "../../managers/InventoryManagerContext";
import * as appEnvSelectors from "../../selectors/appEnv";
import { SharedAppState } from "../../stores/typings";
import { PaneIdentifierUtils } from "../../utils";

// TODO: We are typing with AnyAction here to work around issues with UnknownAction, the type that was introduced in Redux 5.
// If we decide to modernize to Redux 5, we will need to refactor much of the Rules Engine redux usage.
interface Props extends DispatchProp<AnyAction> {
  item: Item;
  hasMaxAttunedItems: boolean;
  ruleData: RuleData;
  isReadonly: boolean;
  containers: Array<Container>;
  onPostRemoveNavigation?: PaneComponentEnum;
  partyInfo: PartyInfo | null;
  inventoryManager: InventoryManager;
  paneHistoryStart: PaneInfo["paneHistoryStart"];
  itemPlans: Array<ItemPlan>;
  containerLookup: ContainerLookup;
  isUserDM: boolean;
  onRemoveItem?: () => void;
}
interface State {
  quantity: number;
  newQuantity: number;
}
class ItemDetailActions extends React.PureComponent<Props, State> {
  static defaultProps = {
    hasMaxAttunedItems: false,
  };

  constructor(props: Props) {
    super(props);

    this.state = {
      quantity: ItemUtils.getQuantity(props.item),
      newQuantity: ItemUtils.getQuantity(props.item),
    };
  }

  componentDidUpdate(
    prevProps: Readonly<Props>,
    prevState: Readonly<State>,
    snapshot?: any
  ): void {
    const { item } = this.props;

    const nextQuantity = ItemUtils.getQuantity(item);
    if (nextQuantity !== prevState.quantity) {
      this.setState({
        quantity: nextQuantity,
        newQuantity: nextQuantity,
      });
    }
  }

  handleQuantityUpdate = (newValue: number): void => {
    const { item, inventoryManager } = this.props;

    inventoryManager.handleQuantitySet({ item, quantity: newValue });
  };

  handleRemove = (): void => {
    const {
      paneHistoryStart,
      item,
      onPostRemoveNavigation,
      inventoryManager,
      onRemoveItem,
    } = this.props;

    if (onRemoveItem) {
      onRemoveItem();
    }

    if (onPostRemoveNavigation) {
      paneHistoryStart(onPostRemoveNavigation);
    }

    inventoryManager.handleRemove({ item });

    if (ItemUtils.isContainer(item)) {
      paneHistoryStart(PaneComponentEnum.EQUIPMENT_MANAGE);
    }
  };

  handleRemoveInfusion = (): void => {
    const {
      dispatch,
      paneHistoryStart,
      item,
      inventoryManager,
      containerLookup,
    } = this.props;

    const infusion = ItemUtils.getInfusion(item);
    if (infusion) {
      const infusionId = InfusionUtils.getId(infusion);
      if (infusionId === null) {
        return;
      }

      if (
        InfusionUtils.getType(infusion) === Constants.InfusionTypeEnum.REPLICATE
      ) {
        const choiceKey = InfusionUtils.getChoiceKey(infusion);
        if (choiceKey !== null) {
          paneHistoryStart(
            PaneComponentEnum.INFUSION_CHOICE,
            PaneIdentifierUtils.generateInfusionChoice(choiceKey)
          );
        }
      }

      dispatch(
        serviceDataActions.infusionMappingDestroy(
          infusionId,
          InfusionUtils.getInventoryMappingId(infusion),
          inventoryManager.handleAcceptOnSuccess(
            ItemUtils.isShared(item, containerLookup)
          )
        )
      );
    }
  };

  handleRemoveInfusions = (itemIds: Array<number>): void => {
    const { dispatch } = this.props;

    itemIds.forEach((id) => {
      dispatch(serviceDataActions.infusionMappingsDestroy(itemIds));
    });
  };

  handleUnequip = (): void => {
    const { inventoryManager, item } = this.props;
    inventoryManager.handleUnequip({ item });
  };

  handleEquip = (): void => {
    const { inventoryManager, item } = this.props;
    inventoryManager.handleEquip({ item });
  };

  handleAttune = (): void => {
    const { inventoryManager, item } = this.props;

    inventoryManager.handleEquip({ item });
    inventoryManager.handleAttune({ item });
  };

  handleUnattune = (): void => {
    const { inventoryManager, item } = this.props;

    inventoryManager.handleUnattune({ item });
  };

  handleMove = (containerDefinitionKey: string): void => {
    const { inventoryManager, item } = this.props;

    inventoryManager.handleMove({ item, containerDefinitionKey });
  };

  getContainerOptions = (): Array<GroupedMenuOption> => {
    const { containers, item } = this.props;

    return ContainerUtils.getGroupedOptions(
      ItemUtils.getContainerDefinitionKey(item),
      containers,
      "Move To:"
    );
  };

  renderRemove = (): React.ReactNode => {
    const {
      item,
      containers,
      isReadonly,
      inventoryManager,
      itemPlans,
      isUserDM,
    } = this.props;

    const canRemoveItem = isUserDM
      ? true
      : inventoryManager.canRemoveItem(item);

    if (isReadonly || !canRemoveItem) {
      return null;
    }

    const infusion = ItemUtils.getInfusion(item);

    const name = ItemUtils.getName(item);
    const isContainer = ItemUtils.isContainer(item);
    const itemPlan = ItemUtils.getItemPlan(item, itemPlans);

    if (isContainer) {
      const container = containers.find((container) => {
        return (
          ContainerUtils.getContainerType(container) ===
            Constants.ContainerTypeEnum.ITEM &&
          ContainerUtils.getMappingId(container) ===
            ItemUtils.getMappingId(item)
        );
      });

      if (container) {
        if (infusion) {
          return (
            <Button
              onClick={this.handleRemove}
              variant="outline"
              size="xx-small"
              themed
              data-testid="remove-infusion-button"
            >
              Remove Infusion
            </Button>
          );
        }
        const hasInfusions = ContainerUtils.hasInfusions(container);

        return (
          <Popover
            trigger={
              <Button size="xx-small" variant="outline" themed>
                Delete{itemPlan ? " Replicated Item" : ""}
              </Button>
            }
            position="bottom"
            data-testid="delete-item-button"
            maxWidth={250}
          >
            <PopoverContent
              title={`Remove ${name}?`}
              content={`Removing the ${name} will also remove all of its ${
                hasInfusions ? "infusions and " : " "
              } contents.`}
              confirmText="Delete"
              onConfirm={() => this.handleRemove()}
              withCancel
            />
          </Popover>
        );
      }
    }

    if (infusion) {
      return (
        <Button
          onClick={this.handleRemoveInfusion}
          variant="outline"
          size="xx-small"
          themed
          data-testid="remove-infusion-button"
        >
          Remove Infusion
        </Button>
      );
    }

    return (
      <Button
        onClick={this.handleRemove}
        variant="outline"
        size="xx-small"
        themed
        data-testid="delete-item-button"
      >
        Delete{itemPlan ? " Replicated Item" : ""}
      </Button>
    );
  };

  render() {
    const {
      item,
      hasMaxAttunedItems,
      isReadonly,
      containers,
      inventoryManager,
    } = this.props;
    const containerMoveOptions = this.getContainerOptions();
    const isEquipped = ItemUtils.isEquipped(item);
    const isAttuned = ItemUtils.isAttuned(item);
    const isStackable = ItemUtils.isStackable(item);
    const quantity = ItemUtils.getQuantity(item);
    const parentContainer = ContainerUtils.getItemParentContainer(
      containers,
      ItemUtils.getContainerDefinitionKey(item)
    );
    if (!parentContainer) {
      return null;
    }
    const canEquip = inventoryManager.canEquipUnequipItem(item) && !isReadonly;
    const canAttune =
      inventoryManager.canAttuneUnattuneItem(item) && !isReadonly;
    const canModifyQuantity =
      inventoryManager.canModifyQuantity(item) && !isReadonly;
    const canMoveItem = inventoryManager.canMoveItem(item) && !isReadonly;
    const canShareContainer =
      inventoryManager.canShareContainer(item) && !isReadonly;
    const canClaimContainer =
      inventoryManager.canClaimContainer(item) && !isReadonly;
    const partyInventoryDefinitionKey =
      inventoryManager.getPartyEquipmentContainerDefinitionKey();
    const characterInventoryDefinitionKey =
      inventoryManager.getCharacterContainerDefinitionKey();

    return (
      <div className="ct-item-detail__actions">
        {isStackable && (
          <SimpleQuantity
            quantity={quantity}
            onUpdate={this.handleQuantityUpdate}
            isReadonly={!canModifyQuantity}
          />
        )}

        {canEquip && (
          <Button
            onClick={isEquipped ? this.handleUnequip : this.handleEquip}
            variant={isEquipped ? "solid" : "outline"}
            size="xx-small"
            themed
            data-testid="equip-item-button"
          >
            {isEquipped ? "Unequip" : "Equip"}
          </Button>
        )}
        {canAttune && (
          <Button
            onClick={isAttuned ? this.handleUnattune : this.handleAttune}
            variant={isAttuned ? "solid" : "outline"}
            size="xx-small"
            themed
            disabled={hasMaxAttunedItems && !isAttuned}
            data-testid="attune-item-button"
          >
            {isAttuned ? "Unattune" : "Attune"}
          </Button>
        )}
        {canShareContainer && partyInventoryDefinitionKey && (
          <ButtonWithMenu
            showSingleOption={true}
            placement="bottom-end"
            variant="outline"
            groupedOptions={[
              {
                label: "Move To:",
                options: [
                  {
                    label: "Party Inventory",
                    value: partyInventoryDefinitionKey,
                  },
                ],
              },
            ]}
            onSelect={() => this.handleMove(partyInventoryDefinitionKey)}
            data-testid="move-item-button"
          >
            Move
          </ButtonWithMenu>
        )}
        {canClaimContainer && characterInventoryDefinitionKey && (
          <ButtonWithMenu
            showSingleOption={true}
            placement="bottom-end"
            variant="outline"
            groupedOptions={[
              {
                label: "Move To:",
                options: [
                  {
                    label: "My Inventory",
                    value: characterInventoryDefinitionKey,
                  },
                ],
              },
            ]}
            onSelect={() => this.handleMove(characterInventoryDefinitionKey)}
            data-testid="move-item-button"
          >
            Move
          </ButtonWithMenu>
        )}
        {canMoveItem && (
          <ButtonWithMenu
            variant="outline"
            groupedOptions={containerMoveOptions}
            showSingleOption={true}
            onSelect={this.handleMove}
          >
            Move
          </ButtonWithMenu>
        )}
        {this.renderRemove()}
      </div>
    );
  }
}

function mapStateToProps(state: SharedAppState) {
  return {
    ruleData: rulesEngineSelectors.getRuleData(state),
    isReadonly: appEnvSelectors.getIsReadonly(state),
    hasMaxAttunedItems: rulesEngineSelectors.hasMaxAttunedItems(state),
    containers: rulesEngineSelectors.getInventoryContainers(state),
    partyInfo: serviceDataSelectors.getPartyInfo(state),
    itemPlans: rulesEngineSelectors.getAvailableItemPlans(state),
    containerLookup: rulesEngineSelectors.getContainerLookup(state),
    isUserDM: appEnvSelectors.getIsUserDM(state),
  };
}

function ItemDetailActionsContainer(props) {
  const { inventoryManager } = useContext(InventoryManagerContext);
  const {
    pane: { paneHistoryStart },
  } = useSidebar();

  return (
    <ItemDetailActions
      inventoryManager={inventoryManager}
      paneHistoryStart={paneHistoryStart}
      {...props}
    />
  );
}

export default connect(mapStateToProps)(ItemDetailActionsContainer);
