import React from "react";
import { useContext } from "react";
import { DispatchProp } from "react-redux";
import { AnyAction } from "redux";
import { v4 as uuidv4 } from "uuid";

import {
  Collapsible,
  CollapsibleHeaderContent,
  CollapsibleHeading,
} from "@dndbeyond/character-components/es";
import {
  characterActions,
  CharacterConfiguration,
  CharacterCurrencyContract,
  CharacterNotes,
  CharacterTheme,
  Constants,
  ContainerLookup,
  ContainerUtils,
  InventoryManager,
  FormatUtils,
  Item,
  ItemUtils,
  Modifier,
  RuleData,
  rulesEngineSelectors,
  TypeValueLookup,
  CoinManager,
} from "@dndbeyond/character-rules-engine/es";

import { EditorWithDialog } from "~/subApps/builder/components/EditorWithDialog";
import { RouteKey } from "~/subApps/builder/constants";
import {
  ModalData,
  useModalManager,
} from "~/subApps/builder/contexts/ModalManager";

import * as toastActions from "../../../../Shared/actions/toastMessage/actions";
import { ArmorListItem } from "../../../../Shared/components/legacy/ArmorList";
import { CurrencyList } from "../../../../Shared/components/legacy/CurrencyList";
import { EquipmentManagerShop } from "../../../../Shared/components/legacy/EquipmentManagerShop";
import { GearListItem } from "../../../../Shared/components/legacy/GearList";
import { WeaponListItem } from "../../../../Shared/components/legacy/WeaponList";
import { CURRENCY_VALUE } from "../../../../Shared/constants/App";
import StartingEquipment from "../../../../Shared/containers/StartingEquipment";
import { CurrencyErrorTypeEnum } from "../../../../Shared/containers/panes/CurrencyPane/CurrencyPaneConstants";
import { CoinManagerContext } from "../../../../Shared/managers/CoinManagerContext";
import { InventoryManagerContext } from "../../../../Shared/managers/InventoryManagerContext";
import { AppNotificationUtils } from "../../../../Shared/utils";
import Page from "../../../components/Page";
import { PageHeader } from "../../../components/PageHeader";
import { BuilderAppState } from "../../../typings";
import ConnectedBuilderPage from "../ConnectedBuilderPage";

// 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> {
  configuration: CharacterConfiguration;
  inventory: Array<Item>;
  containerLookup: ContainerLookup;
  totalWeight: number;
  notes: CharacterNotes;
  ruleData: RuleData;
  hasMaxAttunedItems: boolean;
  characterId: number;
  theme: CharacterTheme;
  globalModifiers: Array<Modifier>;
  valueLookupByType: TypeValueLookup;
  inventoryManager: InventoryManager;
  coinManager: CoinManager;
  activeSourceCategories: Array<number>;
  createModal: (modalData: ModalData) => void;
}
interface State {
  showStartingEquipment: boolean;
}
class EquipmentManage extends React.PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);

    this.state = {
      showStartingEquipment: props.configuration.startingEquipmentType === null,
    };
  }

  textareaInput = React.createRef<HTMLDivElement>();

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

    if (configuration !== prevProps.configuration) {
      this.setState({
        showStartingEquipment: configuration.startingEquipmentType === null,
      });
    }
  }

  handleItemAdd = (
    item: Item,
    amount: number,
    containerDefinitionKey: string
  ): void => {
    const { inventoryManager } = this.props;

    inventoryManager.handleAdd(
      { item, amount, containerDefinitionKey },
      AppNotificationUtils.handleItemAddAccepted.bind(this, item, amount)
    );
  };

  handleCurrencyChange = (coin: CharacterCurrencyContract): void => {
    const { coinManager } = this.props;

    coinManager.handleCoinSet({
      coin,
      containerDefinitionKey: coinManager.getCharacterContainerDefinitionKey(),
    });
  };

  handleCurrencyError = (
    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
        )
      );
    }
  };

  renderStartingEquipment = (): React.ReactNode => {
    const { showStartingEquipment } = this.state;

    let headingNode: React.ReactNode = (
      <CollapsibleHeading>Starting Equipment</CollapsibleHeading>
    );

    let headerNode: React.ReactNode = (
      <CollapsibleHeaderContent heading={headingNode} />
    );

    return (
      <Collapsible
        header={headerNode}
        className="equipment-manage__starting"
        useBuilderStyles={true}
        initiallyCollapsed={!showStartingEquipment}
        collapsed={!showStartingEquipment}
        onChangeHandler={(isCollapsed) => {
          this.setState({
            showStartingEquipment: !isCollapsed,
          });
        }}
      >
        <StartingEquipment
          onStartingEquipmentChoose={() => {
            this.setState({
              showStartingEquipment: false,
            });
          }}
          isInitialView={true}
        />
      </Collapsible>
    );
  };

  renderItem = (
    item: Item,
    itemParams,
    weaponLabels,
    armorLabels,
    gearLabels
  ): React.ReactNode => {
    if (ItemUtils.isWeaponContract(item)) {
      let finalWeaponLabels = weaponLabels;
      if (ItemUtils.isAmmunition(item)) {
        //TODO fix this to be cleaner
        finalWeaponLabels = {
          ...weaponLabels,
          equipLabel: "Use",
          unequipLabel:
            weaponLabels.unequipLabel === "Stow"
              ? weaponLabels.unequipLabel
              : "In Use",
        };
      }

      return (
        <WeaponListItem
          key={uuidv4()}
          item={item}
          {...finalWeaponLabels}
          {...itemParams}
        />
      );
    } else if (ItemUtils.isArmorContract(item)) {
      return (
        <ArmorListItem
          key={uuidv4()}
          item={item}
          {...armorLabels}
          {...itemParams}
        />
      );
    } else if (ItemUtils.isGearContract(item)) {
      return (
        <GearListItem
          key={uuidv4()}
          item={item}
          {...gearLabels}
          {...itemParams}
        />
      );
    }

    return null;
  };

  renderItemList = (): React.ReactNode => {
    const { inventory } = this.props;

    const weaponLabels = {
      equipLabel: "Wield",
      unequipLabel: "Wielding",
    };
    const armorLabels = {
      equipLabel: "Wear",
      unequipLabel: "Wearing",
    };
    const gearLabels = {
      equipLabel: "Use",
      unequipLabel: "In Use",
    };
    const itemParams = {
      showRemove: true,
      showEquip: true,
      showUnequip: true,
      showHeaderAction: true,
    };

    return (
      <>
        {ItemUtils.sortInventoryItems(inventory).map((item) =>
          this.renderItem(
            item,
            itemParams,
            weaponLabels,
            armorLabels,
            gearLabels
          )
        )}
      </>
    );
  };

  renderInventory = (): React.ReactNode => {
    const { inventory, totalWeight } = this.props;

    const itemTotal: number = inventory.length;

    let headerNode: React.ReactNode = (
      <CollapsibleHeaderContent
        heading={
          <CollapsibleHeading>
            Current Inventory ({itemTotal})
          </CollapsibleHeading>
        }
        callout={
          <div className="equipment-manage__callout">
            Total Weight: {FormatUtils.renderWeight(totalWeight)}
          </div>
        }
      />
    );

    return (
      <Collapsible
        header={headerNode}
        className="equipment-manage__inventory"
        useBuilderStyles={true}
        initiallyCollapsed={itemTotal === 0}
        collapsed={itemTotal === 0}
      >
        {itemTotal === 0 ? (
          <div className="equipment-manager__inventory-empty">
            You currently have no items in your inventory. Add Starting
            Equipment above or Add Items from the list of available items below.
          </div>
        ) : (
          this.renderItemList()
        )}
      </Collapsible>
    );
  };

  renderOtherPossessions = (): React.ReactNode => {
    const { notes, dispatch } = this.props;

    return (
      <Collapsible
        header={"Other Possessions"}
        className="equipment-manage__possessions"
        useBuilderStyles={true}
      >
        <EditorWithDialog
          heading={<h3>Personal Possessions</h3>}
          editButtonLabel="Edit Possessions"
          addButtonLabel="Add Possessions"
          placeholder="Add personal possessions here..."
          content={notes[Constants.NoteKeyEnum.PERSONAL_POSSESSIONS] ?? ""}
          onSave={(content) => {
            dispatch(
              characterActions.noteSet(
                Constants.NoteKeyEnum.PERSONAL_POSSESSIONS,
                content
              )
            );
          }}
        />
      </Collapsible>
    );
  };

  renderAddItems = (): React.ReactNode => {
    const {
      ruleData,
      characterId,
      theme,
      globalModifiers,
      valueLookupByType,
      activeSourceCategories,
      inventoryManager,
    } = this.props;

    const characterContainerKey =
      ContainerUtils.getCharacterContainerDefinitionKey(characterId);

    return (
      <Collapsible
        header={"Add Items"}
        className="equipment-manage__add-items"
        useBuilderStyles={true}
      >
        <EquipmentManagerShop
          theme={theme}
          globalModifiers={globalModifiers}
          valueLookupByType={valueLookupByType}
          onItemAdd={this.handleItemAdd}
          ruleData={ruleData}
          containerDefinitionKey={characterContainerKey}
          activeSourceCategories={activeSourceCategories}
          inventoryManager={inventoryManager}
        />
      </Collapsible>
    );
  };

  renderCurrency = (): React.ReactNode => {
    const { coinManager } = this.props;

    const coin = coinManager.getContainerCoin(
      coinManager.getCharacterContainerDefinitionKey()
    );

    if (!coin) {
      return null;
    }

    let headerNode: React.ReactNode = (
      <CollapsibleHeaderContent
        heading={<CollapsibleHeading>Currency</CollapsibleHeading>}
        callout={
          <div className="equipment-manage__callout">
            Total in GP:{" "}
            {FormatUtils.renderLocaleNumber(
              coinManager.getTotalContainerCoinInGold(
                coinManager.getCharacterContainerDefinitionKey()
              )
            )}
          </div>
        }
      />
    );

    return (
      <Collapsible
        header={headerNode}
        className="equipment-manage__currency"
        useBuilderStyles={true}
      >
        <CurrencyList
          {...coin}
          onChange={this.handleCurrencyChange}
          onError={this.handleCurrencyError}
          totalGp={coinManager.getTotalContainerCoinInGold(
            coinManager.getCharacterContainerDefinitionKey()
          )}
        />
      </Collapsible>
    );
  };

  render() {
    return (
      <Page>
        <PageHeader>Choose Equipment</PageHeader>
        <div className="equipment-manage">
          {this.renderStartingEquipment()}
          {this.renderInventory()}
          {this.renderOtherPossessions()}
          {this.renderAddItems()}
          {this.renderCurrency()}
        </div>
      </Page>
    );
  }
}

function EquipmentManageContainer(props) {
  const { inventoryManager } = useContext(InventoryManagerContext);
  const { coinManager } = useContext(CoinManagerContext);
  const { createModal } = useModalManager();

  return (
    <EquipmentManage
      coinManager={coinManager}
      inventoryManager={inventoryManager}
      createModal={createModal}
      {...props}
    />
  );
}

export default ConnectedBuilderPage(
  EquipmentManageContainer,
  RouteKey.EQUIPMENT_MANAGE,
  (state: BuilderAppState) => {
    return {
      configuration: rulesEngineSelectors.getCharacterConfiguration(state),
      inventory: rulesEngineSelectors.getInventory(state),
      containerLookup: rulesEngineSelectors.getContainerLookup(state),
      totalWeight: rulesEngineSelectors.getTotalCarriedWeight(state),
      notes: rulesEngineSelectors.getCharacterNotes(state),
      currencies: rulesEngineSelectors.getCurrencies(state),
      ruleData: rulesEngineSelectors.getRuleData(state),
      hasMaxAttunedItems: rulesEngineSelectors.hasMaxAttunedItems(state),
      characterId: rulesEngineSelectors.getId(state),
      globalModifiers: rulesEngineSelectors.getValidGlobalModifiers(state),
      valueLookupByType:
        rulesEngineSelectors.getCharacterValueLookupByType(state),
      theme: rulesEngineSelectors.getCharacterTheme(state),
      activeSourceCategories:
        rulesEngineSelectors.getActiveSourceCategories(state),
    };
  }
);
