import clsx from "clsx";
import { useContext, ReactNode, PureComponent } from "react";
import React from "react";
import { connect } from "react-redux";

import { DarkBuilderSvg } from "@dndbeyond/character-components/es";
import {
  CharacterNotes,
  CharacterTheme,
  Constants,
  ContainerManager,
  Creature,
  CreatureUtils,
  InfusionChoice,
  InfusionChoiceUtils,
  Item,
  ItemUtils,
  SnippetData,
  RuleData,
  rulesEngineSelectors,
  ContainerUtils,
  InventoryManager,
  FormatUtils,
  serviceDataSelectors,
  PartyInfo,
  CoinManager,
  ClassUtils,
  CharClass,
  ItemPlan,
  ItemPlanUtils,
} from "@dndbeyond/character-rules-engine/es";

import { Button } from "~/components/Button";
import { ItemName } from "~/components/ItemName";
import { Link } from "~/components/Link";
import { NumberDisplay } from "~/components/NumberDisplay";
import { TabFilter } from "~/components/TabFilter";
import { TabList } from "~/components/TabList";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import a11yStyles from "~/styles/accessibility.module.css";
import { EquipmentOverview } from "~/subApps/sheet/components/Equipment/EquipmentOverview";
import { MagicItemPlans } from "~/subApps/sheet/components/MagicItemPlans";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";

import CurrencyButton from "../../../CharacterSheet/components/CurrencyButton";
import { ItemSlotManager } from "../../../Shared/components/ItemSlotManager";
import { ThemeButton } from "../../../Shared/components/common/Button";
import { CoinManagerContext } from "../../../Shared/managers/CoinManagerContext";
import { InventoryManagerContext } from "../../../Shared/managers/InventoryManagerContext";
import { appEnvSelectors } from "../../../Shared/selectors";
import { PaneIdentifierUtils } from "../../../Shared/utils";
import ContentGroup from "../../components/ContentGroup";
import Infusions from "../../components/Infusions";
import InventoryFilter from "../../components/InventoryFilter";
import InventoryTableHeader from "../../components/InventoryTableHeader";
import OtherPossessions from "../../components/OtherPossessions";
import { sheetAppSelectors } from "../../selectors";
import { SheetAppState } from "../../typings";
import Attunement from "../Attunement";
import Inventory from "../Inventory";
import styles from "./styles.module.css";

interface Props {
  showNotes: boolean;

  builderUrl: string;
  inventory: Array<Item>;
  partyInventory: Array<Item>;
  creatures: Array<Creature>;
  notes: CharacterNotes;
  infusionChoices: Array<InfusionChoice>;
  ruleData: RuleData;
  snippetData: SnippetData;
  isReadonly: boolean;
  isMobile: boolean;
  theme: CharacterTheme;
  proficiencyBonus: number;
  classes: Array<CharClass>;
  inventoryManager: InventoryManager;
  campaignInfo: PartyInfo | null;
  coinManager: CoinManager;
  paneHistoryStart: PaneInfo["paneHistoryStart"];
  itemPlans: Array<ItemPlan>;
  maxReplicatedItemsCount: number | null;
}
interface StateFilterData {
  filteredInventory: Array<Item>;
  filteredPartyInventory: Array<Item>;
  showAdvancedFilters: boolean;
  isFiltering: boolean;
}
interface State {
  infusableChoices: Array<InfusionChoice>;
  filterData: StateFilterData;
  filteredEquippedInventory: Array<Item>;
  shouldShowPartyInventory: number;
}

class Equipment extends PureComponent<Props, State> {
  static defaultProps = {
    showNotes: true,
  };

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

    this.state = {
      infusableChoices: props.infusionChoices.filter((infusionChoice) =>
        InfusionChoiceUtils.canInfuse(infusionChoice)
      ),
      filterData: {
        filteredInventory: [],
        filteredPartyInventory: [],
        showAdvancedFilters: false,
        isFiltering: false,
      },
      filteredEquippedInventory: [],
      shouldShowPartyInventory: 0, // mui used 0,1,2 for tabs so 0 is off 1 is on...
    };
  }

  componentDidUpdate(prevProps: Props, prevState: State) {
    if (prevProps.infusionChoices !== this.props.infusionChoices) {
      this.setState({
        infusableChoices: this.props.infusionChoices.filter((infusionChoice) =>
          InfusionChoiceUtils.canInfuse(infusionChoice)
        ),
      });
    }
  }

  handleCurrencyClick = (
    evt: React.MouseEvent | React.KeyboardEvent,
    containerDefinitionKey: string
  ): void => {
    const { paneHistoryStart } = this.props;

    evt.stopPropagation();
    evt.nativeEvent.stopImmediatePropagation();

    paneHistoryStart(
      PaneComponentEnum.CURRENCY,
      PaneIdentifierUtils.generateCurrencyContext(containerDefinitionKey)
    );
  };

  handlePossessionsManage = (): void => {
    const { paneHistoryStart, isReadonly } = this.props;

    if (!isReadonly) {
      paneHistoryStart(
        PaneComponentEnum.NOTE_MANAGE,
        PaneIdentifierUtils.generateNote(
          Constants.NoteKeyEnum.PERSONAL_POSSESSIONS
        )
      );
    }
  };

  handleManageClick = (): void => {
    const { paneHistoryStart } = this.props;

    paneHistoryStart(PaneComponentEnum.EQUIPMENT_MANAGE);
  };

  handleFilterUpdate = (filterData: StateFilterData): void => {
    this.setState({
      filterData,
      filteredEquippedInventory: [
        ...filterData.filteredInventory.filter(ItemUtils.isEquipped),
        ...filterData.filteredPartyInventory.filter(ItemUtils.isEquipped),
      ],
    });
  };

  handleInfusionChoiceShow = (infusionChoice: InfusionChoice): void => {
    const { paneHistoryStart } = this.props;

    const choiceKey = InfusionChoiceUtils.getKey(infusionChoice);
    if (choiceKey !== null) {
      paneHistoryStart(
        PaneComponentEnum.INFUSION_CHOICE,
        PaneIdentifierUtils.generateInfusionChoice(choiceKey)
      );
    }
  };

  handleItemShow = (item: Item): void => {
    const { paneHistoryStart } = this.props;
    paneHistoryStart(
      PaneComponentEnum.ITEM_DETAIL,
      PaneIdentifierUtils.generateItem(ItemUtils.getMappingId(item))
    );
  };

  handleContainerShow = (container: ContainerManager): void => {
    const { paneHistoryStart } = this.props;

    paneHistoryStart(
      PaneComponentEnum.CONTAINER,
      PaneIdentifierUtils.generateContainer(container.getDefinitionKey())
    );
  };

  handleCreatureShow = (creature: Creature): void => {
    const { paneHistoryStart } = this.props;
    paneHistoryStart(
      PaneComponentEnum.CREATURE,
      PaneIdentifierUtils.generateCreature(CreatureUtils.getMappingId(creature))
    );
  };

  handleItemPlansManagePaneShow = (): void => {
    const { paneHistoryStart } = this.props;

    paneHistoryStart(PaneComponentEnum.ITEM_PLANS_MANAGE);
  };

  shouldRenderInfusions = (): boolean => {
    const { infusionChoices } = this.props;

    return infusionChoices.length > 0;
  };

  shouldRenderMagicItemPlans = (): boolean => {
    const { classes } = this.props;

    return classes.some((charClass) =>
      ClassUtils.getEnablesReplicateMagicItem(charClass)
    );
  };

  renderContainer = (
    container: ContainerManager,
    inventory: Array<Item>
  ): ReactNode => {
    const { shouldShowPartyInventory } = this.state;
    const { isReadonly, showNotes, theme, coinManager } = this.props;
    const containerMappingId = container.getMappingId();

    const containerName = container.getName();
    const weightInfo = container.getWeightInfo();

    const containerInventory = container.getInventoryItems({
      filteredInventory: inventory,
    }).items;
    const containerItem = container.getContainerItem();

    const quantityNode = (
      <span className="ct-equipment__container-quantity">{`(${containerInventory.length})`}</span>
    );
    const nameNode: ReactNode = containerItem ? (
      <>
        <ItemName item={containerItem.item} /> {quantityNode}
      </>
    ) : (
      <>
        {containerName} {quantityNode}
      </>
    );

    const headerNode: ReactNode = (
      <div
        className="ct-equipment__container ct-inventory-item"
        onClick={(evt) => {
          evt.nativeEvent.stopImmediatePropagation();
          this.handleContainerShow(container);
        }}
        role="button"
        data-testid={`${containerItem?.getName() || containerName}-section`
          .toLowerCase()
          .replace(/\s/g, "-")}
      >
        <div className="ct-equipment__container-action ct-inventory-item__action">
          {containerItem && (
            <ItemSlotManager
              isUsed={!!containerItem.isEquipped()}
              isReadonly={isReadonly}
              canUse={
                !(
                  containerItem.isEquipped() &&
                  !containerItem.isEquippedToCurrentCharacter()
                )
              }
              onSet={(uses) => {
                if (uses === 0) {
                  containerItem.handleUnequip();
                }

                if (uses === 1) {
                  containerItem.handleEquip();
                }
              }}
              theme={theme}
              useTooltip={false}
            />
          )}
        </div>
        <div className="ct-equipment__container-name ct-inventory-item__name">
          {nameNode}
        </div>
        <div className="ct-equipment__container-weight ct-inventory-item__weight">
          <NumberDisplay type="weightInLb" number={weightInfo.applied} />
          {weightInfo.capacity > 0 && (
            <span className="ct-equipment__container-weight-capacity">
              ({Math.ceil(weightInfo.total)}/
              {FormatUtils.renderWeight(weightInfo.capacity)})
            </span>
          )}
        </div>
        {coinManager.canUseCointainers() ? (
          <div className="ct-equipment__container-coin">
            <CurrencyButton
              coin={ContainerUtils.getCoin(container.container)}
              isDarkMode={theme.isDarkMode}
              shouldShowPartyInventory={shouldShowPartyInventory === 1}
              handleCurrencyClick={(evt) =>
                this.handleCurrencyClick(
                  evt,
                  ContainerUtils.getDefinitionKey(container.container)
                )
              }
            />
          </div>
        ) : (
          containerItem &&
          container.isEquipped() &&
          !containerItem.isEquippedToCurrentCharacter() && (
            <div className="ct-equipment__container-equipped">
              Equipped by {containerItem.getEquippedCharacterName()}
            </div>
          )
        )}
      </div>
    );

    return (
      <ContentGroup key={containerMappingId} header={headerNode}>
        <Inventory
          container={container}
          inventory={containerInventory}
          showNotes={showNotes}
          showTableHeader={false}
          isReadonly={isReadonly}
          theme={theme}
        />
      </ContentGroup>
    );
  };

  renderCharacterContainers = (): ReactNode => {
    const { filterData } = this.state;
    const { inventoryManager } = this.props;

    return inventoryManager
      .getCharacterContainers()
      .map((container) =>
        this.renderContainer(container, filterData.filteredInventory)
      );
  };

  renderPartyContainers = (): ReactNode => {
    const { filterData } = this.state;
    const { inventoryManager } = this.props;

    return inventoryManager
      .getPartyContainers()
      .map((container) =>
        this.renderContainer(container, filterData.filteredPartyInventory)
      );
  };

  renderAttunement = (): ReactNode => {
    const { isMobile } = this.props;

    return (
      <ContentGroup header="Attunement">
        <Attunement isMobile={isMobile} />
      </ContentGroup>
    );
  };

  renderMagicItemPlans = (): ReactNode => {
    const { isReadonly, itemPlans, maxReplicatedItemsCount } = this.props;

    if (!this.shouldRenderMagicItemPlans()) {
      return null;
    }

    const itemPlanWithMappingsCount = itemPlans.filter((plan) =>
      ItemPlanUtils.getItem(plan)
    ).length;

    const headerNode: ReactNode = (
      <>
        Magic Item Plans{" "}
        {maxReplicatedItemsCount !== null && (
          <span className={styles.replicatedCount}>
            ({itemPlanWithMappingsCount}/{maxReplicatedItemsCount} Replicated)
          </span>
        )}
      </>
    );

    let extraNode: ReactNode = null;
    if (!isReadonly) {
      extraNode = (
        <Button
          variant="text"
          themed
          size="x-small"
          className="ct-equipment__builder-link-text"
          onClick={(e) => {
            e.preventDefault();
            e.stopPropagation();
            this.handleItemPlansManagePaneShow();
          }}
        >
          Manage Plans
        </Button>
      );
    }

    return (
      <ContentGroup header={headerNode} extra={extraNode}>
        <MagicItemPlans />
      </ContentGroup>
    );
  };

  renderInfusions = (): ReactNode => {
    const { infusableChoices } = this.state;
    const {
      builderUrl,
      infusionChoices,
      isReadonly,
      snippetData,
      inventory,
      partyInventory,
      creatures,
      ruleData,
      proficiencyBonus,
      theme,
    } = this.props;

    const headerNode: ReactNode = (
      <>
        Infusions{" "}
        {infusableChoices.length !== infusionChoices.length && (
          <>
            ({infusableChoices.length}/{infusionChoices.length} Known Infusions)
          </>
        )}
      </>
    );

    let extraNode: ReactNode = null;
    if (!isReadonly) {
      extraNode = (
        <Link
          href={builderUrl}
          className="ct-equipment__builder-link"
          userouter
        >
          <DarkBuilderSvg />
          <span className="ct-equipment__builder-link-text">
            Manage Infusions
          </span>
        </Link>
      );
    }

    return (
      <ContentGroup header={headerNode} extra={extraNode}>
        <Infusions
          theme={theme}
          infusionChoices={infusableChoices}
          inventory={[...inventory, ...partyInventory]}
          creatures={creatures}
          snippetData={snippetData}
          onCreatureShow={this.handleCreatureShow}
          onItemShow={this.handleItemShow}
          onInfusionChoiceShow={this.handleInfusionChoiceShow}
          ruleData={ruleData}
          isReadonly={isReadonly}
          proficiencyBonus={proficiencyBonus}
        />
      </ContentGroup>
    );
  };

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

    if (isReadonly) {
      return null;
    }

    return (
      <ContentGroup header="Other Possessions">
        <OtherPossessions
          notes={notes}
          onClick={this.handlePossessionsManage}
        />
      </ContentGroup>
    );
  };

  renderContent = (): ReactNode => {
    const {
      isReadonly,
      inventoryManager,
      inventory,
      partyInventory,
      showNotes,
    } = this.props;
    const { shouldShowPartyInventory } = this.state;

    const headerNode: ReactNode = (
      <div
        className="ct-equipment__container"
        onClick={(evt) => {
          evt.nativeEvent.stopImmediatePropagation();
          this.handleManageClick();
        }}
      >
        Party Inventory
      </div>
    );

    return (
      <div className={styles.tabFilter}>
        <TabFilter
          sharedChildren={<InventoryTableHeader showNotes={showNotes} />}
          filters={[
            {
              label: "All",
              content: (
                <>
                  {shouldShowPartyInventory === 0 ? (
                    <>
                      {this.renderCharacterContainers()}
                      {this.renderAttunement()}
                      {this.shouldRenderInfusions() && this.renderInfusions()}
                      {this.renderMagicItemPlans()}
                      {this.renderOtherPossessions()}
                    </>
                  ) : null}
                  {shouldShowPartyInventory === 1 &&
                  inventoryManager.getPartyContainers().length > 0 ? (
                    <ContentGroup header={headerNode}>
                      {this.renderPartyContainers()}
                    </ContentGroup>
                  ) : null}
                </>
              ),
            },
            ...(shouldShowPartyInventory === 0
              ? inventoryManager
                  .getCharacterContainers()
                  .map((characterContainer) => ({
                    label: characterContainer.getName(),
                    content: this.renderContainer(
                      characterContainer,
                      inventory
                    ),
                  }))
              : []),
            ...(shouldShowPartyInventory === 1
              ? inventoryManager.getPartyContainers().map((partyContainer) => ({
                  label: partyContainer.getName(),
                  content: this.renderContainer(partyContainer, partyInventory),
                }))
              : []),
            ...(shouldShowPartyInventory === 0
              ? [
                  {
                    label: "Attunement",
                    content: this.renderAttunement(),
                  },
                  ...(this.shouldRenderInfusions()
                    ? [
                        {
                          label: "Infusions",
                          content: this.renderInfusions(),
                        },
                      ]
                    : []),
                  ...(this.shouldRenderMagicItemPlans()
                    ? [
                        {
                          label: "Magic Item Plans",
                          content: this.renderMagicItemPlans(),
                        },
                      ]
                    : []),
                  ...(!isReadonly
                    ? [
                        {
                          label: "Other Possessions",
                          content: this.renderOtherPossessions(),
                        },
                      ]
                    : []),
                ]
              : []),
          ]}
          showAllTab={false}
        />
      </div>
    );
  };

  renderManageButton = (): ReactNode => {
    const { isReadonly } = this.props;

    if (isReadonly) {
      return null;
    }

    return (
      <ThemeButton
        style="outline"
        size="medium"
        onClick={this.handleManageClick}
        data-testid="open-manage-equipment"
      >
        Manage Inventory
      </ThemeButton>
    );
  };

  toggleShouldShowPartyInventory = (event, newValue): void => {
    this.setState({
      shouldShowPartyInventory: newValue,
    });
  };

  renderOverviewFiltersAndContent = (): ReactNode => {
    const { filterData, shouldShowPartyInventory } = this.state;
    const { inventory, ruleData, theme, partyInventory } = this.props;

    return (
      <>
        <div className="ct-equipment__overview">
          <EquipmentOverview
            activeEquipmentTab={
              shouldShowPartyInventory === 1 ? "party" : "character"
            }
          />
        </div>
        <div className="ct-equipment__filter">
          <InventoryFilter
            partyInventory={partyInventory}
            inventory={inventory}
            ruleData={ruleData}
            onDataUpdate={this.handleFilterUpdate}
            callout={this.renderManageButton()}
            theme={theme}
          />
        </div>
        {!filterData.showAdvancedFilters && this.renderContent()}
      </>
    );
  };

  handleChangeTab = (id) =>
    this.setState({
      shouldShowPartyInventory: id === "party-inventory" ? 1 : 0,
    });

  render() {
    const { campaignInfo } = this.props;

    return (
      <section className={clsx(["ct-equipment", styles.section])}>
        <h2 className={a11yStyles.screenreaderOnly}>Inventory</h2>
        {campaignInfo !== null ? (
          <TabList
            className={styles.tabList}
            variant="fullwidth"
            themed={true}
            tabs={[
              {
                label: "My Inventory",
                content: this.renderOverviewFiltersAndContent(),
                id: "my-inventory",
              },
              {
                label: "Party Inventory",
                content: this.renderOverviewFiltersAndContent(),
                id: "party-inventory",
              },
            ]}
            onChangeTab={this.handleChangeTab}
          />
        ) : (
          this.renderOverviewFiltersAndContent()
        )}
      </section>
    );
  }
}

function mapStateToProps(state: SheetAppState) {
  return {
    builderUrl: sheetAppSelectors.getBuilderUrl(state),
    inventory: rulesEngineSelectors.getInventory(state),
    partyInventory: rulesEngineSelectors.getPartyInventory(state),
    creatures: rulesEngineSelectors.getCreatures(state),
    notes: rulesEngineSelectors.getCharacterNotes(state),
    infusionChoices: rulesEngineSelectors.getAvailableInfusionChoices(state),
    ruleData: rulesEngineSelectors.getRuleData(state),
    snippetData: rulesEngineSelectors.getSnippetData(state),
    theme: rulesEngineSelectors.getCharacterTheme(state),
    isReadonly: appEnvSelectors.getIsReadonly(state),
    isMobile: appEnvSelectors.getIsMobile(state),
    proficiencyBonus: rulesEngineSelectors.getProficiencyBonus(state),
    classes: rulesEngineSelectors.getClasses(state),
    campaignInfo: serviceDataSelectors.getPartyInfo(state),
    itemPlans: rulesEngineSelectors.getAvailableItemPlans(state),
    maxReplicatedItemsCount:
      rulesEngineSelectors.getMaxReplicatedItemsCount(state),
  };
}

const EquipmentContainer = (props) => {
  const { inventoryManager } = useContext(InventoryManagerContext);
  const { coinManager } = useContext(CoinManagerContext);
  const {
    pane: { paneHistoryStart },
  } = useSidebar();
  return (
    <Equipment
      inventoryManager={inventoryManager}
      coinManager={coinManager}
      paneHistoryStart={paneHistoryStart}
      {...props}
    />
  );
};

export default connect(mapStateToProps)(EquipmentContainer);
