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

import { Collapsible } from "@dndbeyond/character-components/es";
import {
  CharacterTheme,
  Constants,
  Container,
  ContainerLookup,
  ContainerUtils,
  DataOrigin,
  EntityValueLookup,
  InventoryManager,
  Infusion,
  InfusionChoiceLookup,
  InfusionUtils,
  Item,
  ItemUtils,
  Modifier,
  PartyInfo,
  RuleData,
  rulesEngineSelectors,
  serviceDataSelectors,
  SnippetData,
  Spell,
  TypeValueLookup,
  WeaponSpellDamageGroup,
  ItemManager,
  ContainerManager,
  CharacterCurrencyContract,
  FormatUtils,
  CoinManager,
  ItemPlanUtils,
  EntityUtils,
} from "@dndbeyond/character-rules-engine/es";

import { EditableName } from "~/components/EditableName";
import { ItemName } from "~/components/ItemName";
import { Link } from "~/components/Link";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { Preview } from "~/subApps/sheet/components/Sidebar/components/Preview";
import {
  getDataOriginComponentInfo,
  getSpellComponentInfo,
} from "~/subApps/sheet/components/Sidebar/helpers/paneUtils";
import {
  PaneComponentEnum,
  PaneIdentifiersContainer,
} from "~/subApps/sheet/components/Sidebar/types";

import { PaneInitFailureContent } from "../../../../../../subApps/sheet/components/Sidebar/components/PaneInitFailureContent";
import { CURRENCY_VALUE } from "../../../../Shared/constants/App";
import { CoinManagerContext } from "../../../../Shared/managers/CoinManagerContext";
import * as toastActions from "../../../actions/toastMessage/actions";
import ContainerActions from "../../../components/ContainerActions";
import ItemDetail from "../../../components/ItemDetail";
import { InventoryManagerContext } from "../../../managers/InventoryManagerContext";
import { appEnvSelectors } from "../../../selectors";
import { SharedAppState } from "../../../stores/typings";
import { AppNotificationUtils, PaneIdentifierUtils } from "../../../utils";
import ItemDetailActions from "../../ItemDetailActions";
import { CurrencyErrorTypeEnum } from "../CurrencyPane/CurrencyPaneConstants";

// 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> {
  weaponSpellDamageGroups: Array<WeaponSpellDamageGroup>;
  ruleData: RuleData;
  snippetData: SnippetData;
  entityValueLookup: EntityValueLookup;
  infusionChoiceLookup: InfusionChoiceLookup;
  identifiers: PaneIdentifiersContainer | null;
  isReadonly: boolean;
  theme: CharacterTheme;
  containers: Array<Container>;
  globalModifiers: Array<Modifier>;
  valueLookupByType: TypeValueLookup;
  inventory: Array<Item>;
  containerLookup: ContainerLookup;
  partyInfo: PartyInfo | null;
  inventoryManager: InventoryManager;
  coinManager: CoinManager;
  paneHistoryPush: PaneInfo["paneHistoryPush"];
}
interface State {
  item: ItemManager | null;
  currentContainer: ContainerManager | null;
  isCustomizeClosed: boolean;
}
class ContainerPane extends React.PureComponent<Props, State> {
  constructor(props) {
    super(props);

    this.state = this.generateStateData(props, true);
  }

  componentDidUpdate(
    prevProps: Readonly<Props>,
    prevState: Readonly<State>
  ): void {
    const { inventory, identifiers, containerLookup } = this.props;

    if (
      inventory !== prevProps.inventory ||
      containerLookup !== prevProps.containerLookup ||
      identifiers !== prevProps.identifiers
    ) {
      this.setState(
        this.generateStateData(this.props, prevState.isCustomizeClosed)
      );
    }
  }

  generateStateData = (props: Props, isCustomizeClosed: boolean): State => {
    const { identifiers, inventoryManager } = props;

    let foundItem: ItemManager | null = null;
    let foundContainer: ContainerManager | null = null;
    if (identifiers?.containerDefinitionKey) {
      foundContainer = inventoryManager.getContainer(
        identifiers.containerDefinitionKey
      );
      foundItem = foundContainer ? foundContainer.getContainerItem() : null;
    }

    return {
      item: foundItem ?? null,
      currentContainer: foundContainer ?? null,
      isCustomizeClosed,
    };
  };

  handleCustomDataUpdate = (
    adjustmentType: Constants.AdjustmentTypeEnum,
    value: any
  ): void => {
    const { item } = this.state;
    const { inventoryManager } = this.props;

    if (item) {
      inventoryManager.handleCustomizationSet({
        item: item.item,
        adjustmentType,
        value,
      });
    }
  };

  handleRemoveCustomizations = (): void => {
    const { item } = this.state;
    const { inventoryManager } = this.props;

    if (item) {
      inventoryManager.handleCustomizationsRemove({ item: item.item });
    }
  };

  handleAdd = (
    item: Item,
    amount: number,
    containerDefinitionKey: string
  ): void => {
    const { inventoryManager } = this.props;
    inventoryManager.handleAdd(
      { item, amount, containerDefinitionKey },
      AppNotificationUtils.handleItemAddAccepted.bind(this, item, amount),
      AppNotificationUtils.handleItemAddRejected.bind(this, item, amount)
    );
  };

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

  handleItemSetEquip = (item: Item, uses: number): void => {
    const { inventoryManager } = this.props;
    //TODO need different component than SlotManager for item equipped/unequipped
    if (uses === 0) {
      inventoryManager.handleUnequip({ item });
    }

    if (uses === 1) {
      inventoryManager.handleEquip({ item });
    }
  };

  handleDataOriginClick = (dataOrigin: DataOrigin) => {
    const { paneHistoryPush } = this.props;

    let component = getDataOriginComponentInfo(dataOrigin);
    if (component.type !== PaneComponentEnum.ERROR_404) {
      paneHistoryPush(component.type, component.identifiers);
    }
  };

  handleSpellClick = (spell: Spell): void => {
    const { paneHistoryPush } = this.props;

    let component = getSpellComponentInfo(spell);
    if (component.type) {
      paneHistoryPush(component.type, component.identifiers);
    }
  };

  handleInfusionClick = (infusion: Infusion): void => {
    const { paneHistoryPush } = this.props;

    const choiceKey = InfusionUtils.getChoiceKey(infusion);
    if (choiceKey !== null) {
      paneHistoryPush(
        PaneComponentEnum.INFUSION_CHOICE,
        PaneIdentifierUtils.generateInfusionChoice(choiceKey)
      );
    }
  };

  handleManageShow = (): void => {
    const { paneHistoryPush } = this.props;

    paneHistoryPush(PaneComponentEnum.EQUIPMENT_MANAGE);
  };

  handleStartingEquipmentShow = (): void => {
    const { paneHistoryPush } = this.props;
    paneHistoryPush(PaneComponentEnum.STARTING_EQUIPMENT);
  };

  handleCurrencyShow = (): void => {
    const { paneHistoryPush } = this.props;
    paneHistoryPush(PaneComponentEnum.CURRENCY);
  };

  handleEncumbranceShow = (): void => {
    const { paneHistoryPush } = this.props;
    paneHistoryPush(PaneComponentEnum.ENCUMBRANCE);
  };

  sortInventoryItems = (inventory: Array<Item>): Array<Item> => {
    return orderBy(
      inventory,
      [
        (item) => ItemUtils.getRarityLevel(item),
        (item) => ItemUtils.getName(item),
        (item) => ItemUtils.getMappingId(item),
      ],
      ["desc", "asc", "asc"]
    );
  };

  handleOpenCustomize = () => {
    this.setState({ isCustomizeClosed: !this.state.isCustomizeClosed });
  };

  hasCurrencyValueChanged = (
    value: number,
    currencyKey: keyof CharacterCurrencyContract,
    coin: CharacterCurrencyContract
  ): boolean => {
    return value !== coin[currencyKey];
  };

  handleCurrencyChangeError = (
    currencyName: string,
    errorType: CurrencyErrorTypeEnum
  ): void => {
    const { dispatch } = this.props;

    let message: string = "";
    if (errorType === CurrencyErrorTypeEnum.MIN) {
      message =
        "Cannot set currency to a negative value, the previous amount has been set instead.";
    }

    if (errorType === CurrencyErrorTypeEnum.MAX) {
      message = `The max amount allowed for each currency type is ${FormatUtils.renderLocaleNumber(
        CURRENCY_VALUE.MAX
      )}, the previous value has been set instead.`;
    }

    if (errorType !== null) {
      dispatch(
        toastActions.toastError(
          `Unable to Set Currency: ${currencyName}`,
          message
        )
      );
    }
  };

  handleCurrencyAdjust = (
    coin: Partial<CharacterCurrencyContract>,
    multiplier: 1 | -1,
    containerDefinitionKey: string
  ): void => {
    const { coinManager, dispatch } = this.props;

    let actionLabel: string = "";
    if (multiplier === 1) {
      actionLabel = "Add";
    } else if (multiplier === -1) {
      actionLabel = "Delete";
    }

    coinManager.handleTransaction(
      { coin, containerDefinitionKey, multiplier },
      () => {
        // dispatch(toastActions.toastSuccess(
        //     `A ${containerDefinitionKey === coinManager.getPartyEquipmentContainerDefinitionKey() ? 'Party' : ''}Coin transaction was completed!`,
        //     'could list the updates?'
        // ));
      },
      () => {
        dispatch(
          toastActions.toastError(
            `Unable to make transaction: ${actionLabel} Coin`,
            "Cannot set currency to a negative value, the previous amount has been set instead."
          )
        );
      }
    );
  };

  handleAmountSet = (
    containerDefinitionKey: string,
    key: keyof CharacterCurrencyContract,
    amount: number
  ): void => {
    const { coinManager } = this.props;
    const coin = coinManager.getContainerCoin(containerDefinitionKey);

    if (coin && this.hasCurrencyValueChanged(amount, key, coin)) {
      coinManager.handleAmountSet({
        key,
        amount,
        containerDefinitionKey,
      });
    }
  };

  handleParentClick = (): void => {
    const { item } = this.state;
    const { paneHistoryPush } = this.props;
    if (item) {
      const itemPlan = item.getItemPlan();
      if (itemPlan) {
        const dataOrigin = ItemPlanUtils.getDataOrigin(itemPlan);
        let component = getDataOriginComponentInfo(dataOrigin);
        if (component.type !== PaneComponentEnum.ERROR_404) {
          paneHistoryPush(component.type, component.identifiers);
        }
      }
    }
  };

  render() {
    const { item, currentContainer, isCustomizeClosed } = this.state;
    const {
      weaponSpellDamageGroups,
      ruleData,
      snippetData,
      entityValueLookup,
      infusionChoiceLookup,
      isReadonly,
      theme,
      containers,
      globalModifiers,
      valueLookupByType,
      inventory,
      identifiers,
      partyInfo,
    } = this.props;

    if (currentContainer === null) {
      return <PaneInitFailureContent />;
    }

    const shopOpenInitially = identifiers?.showAddItems ?? false;

    let parentName: string | null = null;
    const itemPlan = item ? item.getItemPlan() : null;
    if (itemPlan) {
      const dataOrigin = ItemPlanUtils.getDataOrigin(itemPlan);
      parentName = EntityUtils.getDataOriginName(dataOrigin);
    }

    return (
      <div className="ct-container-pane">
        <Header
          preview={
            <Preview imageUrl={item ? item.getAvatarUrl() : undefined} />
          }
          parent={parentName}
          onClick={this.handleParentClick}
        >
          {item ? (
            <EditableName onClick={this.handleOpenCustomize}>
              <ItemName item={item.item} />
            </EditableName>
          ) : (
            currentContainer.getName()
          )}
        </Header>
        {item && (
          <Collapsible
            header="Item Description"
            initiallyCollapsed={shopOpenInitially}
            overrideCollapsed={shopOpenInitially}
            className="ct-container-pane__content"
          >
            <ItemDetail
              className="ct-container-pane__item-detail"
              theme={theme}
              key={item.getUniqueKey()}
              item={item.item}
              weaponSpellDamageGroups={weaponSpellDamageGroups}
              ruleData={ruleData}
              snippetData={snippetData}
              onCustomDataUpdate={this.handleCustomDataUpdate}
              onCustomizationsRemove={this.handleRemoveCustomizations}
              onDataOriginClick={this.handleDataOriginClick}
              onSpellClick={this.handleSpellClick}
              onInfusionClick={this.handleInfusionClick}
              entityValueLookup={entityValueLookup}
              infusionChoiceLookup={infusionChoiceLookup}
              isReadonly={isReadonly}
              showActions={false}
              showCustomize={!isReadonly}
              container={currentContainer.container}
              isCustomizeClosed={isCustomizeClosed}
              onCustomizeClick={this.handleOpenCustomize}
              partyInfo={partyInfo}
            />
          </Collapsible>
        )}
        <ContainerActions
          currentContainer={currentContainer.container}
          containers={containers}
          ruleData={ruleData}
          theme={theme}
          globalModifiers={globalModifiers}
          valueLookupByType={valueLookupByType}
          onItemAdd={this.handleAdd}
          onItemMove={this.handleItemMove}
          onItemEquip={this.handleItemSetEquip}
          inventory={this.sortInventoryItems(
            ContainerUtils.getInventoryItems(
              currentContainer.container,
              inventory
            )
          )}
          shopOpenInitially={shopOpenInitially}
          isReadonly={isReadonly}
          partyInfo={partyInfo}
          handleCurrencyChangeError={this.handleCurrencyChangeError.bind(this)}
          handleCurrencyAdjust={this.handleCurrencyAdjust.bind(this)}
          handleAmountSet={this.handleAmountSet.bind(this)}
        />
        {!isReadonly && item && <ItemDetailActions item={item.item} />}
        {!isReadonly && currentContainer.isCharacterContainer() && (
          <div className="ct-container-pane__links">
            <div className="ct-container-pane__link">
              <Link useTheme={true} onClick={this.handleManageShow}>
                Manage Inventory
              </Link>
            </div>
            <div className="ct-container-pane__link">
              <Link useTheme={true} onClick={this.handleStartingEquipmentShow}>
                Starting Equipment
              </Link>
            </div>
            <div className="ct-container-pane__link">
              <Link useTheme={true} onClick={this.handleCurrencyShow}>
                Currency
              </Link>
            </div>
            <div className="ct-container-pane__link">
              <Link useTheme={true} onClick={this.handleEncumbranceShow}>
                Encumbrance
              </Link>
            </div>
          </div>
        )}
      </div>
    );
  }
}

function mapStateToProps(state: SharedAppState) {
  return {
    ruleData: rulesEngineSelectors.getRuleData(state),
    weaponSpellDamageGroups:
      rulesEngineSelectors.getWeaponSpellDamageGroups(state),
    entityValueLookup:
      rulesEngineSelectors.getCharacterValueLookupByEntity(state),
    snippetData: rulesEngineSelectors.getSnippetData(state),
    infusionChoiceLookup: rulesEngineSelectors.getInfusionChoiceLookup(state),
    isReadonly: appEnvSelectors.getIsReadonly(state),
    theme: rulesEngineSelectors.getCharacterTheme(state),
    containers: rulesEngineSelectors.getInventoryContainers(state),
    globalModifiers: rulesEngineSelectors.getValidGlobalModifiers(state),
    valueLookupByType:
      rulesEngineSelectors.getCharacterValueLookupByType(state),
    inventory: rulesEngineSelectors.getAllInventoryItems(state),
    containerLookup: rulesEngineSelectors.getContainerLookup(state),
    partyInfo: serviceDataSelectors.getPartyInfo(state),
  };
}
function ContainerPaneContainer(props) {
  const { inventoryManager } = useContext(InventoryManagerContext);
  const { coinManager } = useContext(CoinManagerContext);
  const {
    pane: { paneHistoryPush },
  } = useSidebar();
  return (
    <ContainerPane
      inventoryManager={inventoryManager}
      coinManager={coinManager}
      paneHistoryPush={paneHistoryPush}
      {...props}
    />
  );
}
export default connect(mapStateToProps)(ContainerPaneContainer);
