import * as React from "react";

import {
  AbilityLookup,
  Attack,
  CharacterTheme,
  Constants,
  DiceUtils,
  FormatUtils,
  Item,
  ItemUtils,
  RuleData,
  RuleDataUtils,
  WeaponSpellDamageGroup,
} from "@dndbeyond/character-rules-engine/es";
import {
  Dice,
  RollType,
  DiceEvent,
  RollKind,
  IRollContext,
} from "@dndbeyond/dice";
import StarIcon from "@dndbeyond/fontawesome-cache/svgs/solid/star.svg";
import { GameLogContext } from "@dndbeyond/game-log-components";

import { ItemName } from "~/components/ItemName";
import { NumberDisplay } from "~/components/NumberDisplay";
import Tooltip from "~/tools/js/commonComponents/Tooltip";

import Damage from "../../Damage";
import { DigitalDiceWrapper } from "../../Dice";
import { AttackTypeIcon } from "../../Icons";
import NoteComponents from "../../NoteComponents";
import { DiceComponentUtils } from "../../utils";
import CombatAttack from "../CombatAttack";

interface Props {
  attack: Attack;
  item: Item;
  weaponSpellDamageGroups: Array<WeaponSpellDamageGroup>;
  onClick?: (attack: Attack) => void;
  abilityLookup: AbilityLookup;
  ruleData: RuleData;
  showNotes: boolean;
  className: string;
  diceEnabled: boolean;
  theme: CharacterTheme;
  rollContext: IRollContext;
  proficiencyBonus: number;
}

interface State {
  isCriticalHit: boolean;
}
class CombatItemAttack extends React.PureComponent<Props, State> {
  diceEventHandler: (eventData: any) => void;

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

    this.state = {
      isCriticalHit: false,
    };
  }

  static defaultProps = {
    showNotes: true,
    className: "",
    diceEnabled: false,
  };

  componentDidMount = () => {
    this.diceEventHandler = DiceComponentUtils.setupResetCritStateOnRoll(
      ItemUtils.getName(this.props.attack.data as Item),
      this
    );
  };

  componentWillUnmount = () => {
    Dice.removeEventListener(DiceEvent.ROLL, this.diceEventHandler);
  };

  handleClick = (): void => {
    const { onClick, attack } = this.props;

    if (onClick) {
      onClick(attack);
    }
  };

  renderNotes = (): React.ReactNode => {
    const {
      item,
      weaponSpellDamageGroups,
      ruleData,
      abilityLookup,
      showNotes,
      proficiencyBonus,
      theme,
    } = this.props;

    if (!showNotes) {
      return null;
    }

    return (
      <div className="ddbc-combat-item-attack__notes">
        <NoteComponents
          notes={ItemUtils.getNoteComponents(
            item,
            weaponSpellDamageGroups,
            ruleData,
            abilityLookup,
            proficiencyBonus
          )}
          theme={theme}
        />
      </div>
    );
  };

  handleRoll = (wasCrit: boolean) => {
    this.setState({ isCriticalHit: wasCrit });
  };

  render() {
    const {
      attack,
      item,
      weaponSpellDamageGroups,
      showNotes,
      diceEnabled,
      theme,
      rollContext,
      className,
      ruleData,
    } = this.props;

    const [{ messageTargetOptions, defaultMessageTargetOption, userId }] =
      this.context;

    const { isCriticalHit } = this.state;

    const toHit = ItemUtils.getToHit(item);
    const proficiency = ItemUtils.hasProficiency(item);
    const metaItems = ItemUtils.getMetaItems(item);
    const type = ItemUtils.getType(item);
    const damage = ItemUtils.getDamage(item);
    const damageType = ItemUtils.getDamageType(item);
    const attackType = ItemUtils.getAttackType(item);
    let attackTypeName: string = "";
    if (attackType) {
      attackTypeName = RuleDataUtils.getAttackTypeRangeName(attackType);
    }
    const versatileDamage = ItemUtils.getVersatileDamage(item);
    const reach = ItemUtils.getReach(item);
    const range = ItemUtils.getRange(item);
    const longRange = ItemUtils.getLongRange(item);
    const isHexWeapon = ItemUtils.isHexWeapon(item);
    const isPactWeapon = ItemUtils.isPactWeapon(item);
    const isDedicatedWeapon = ItemUtils.isDedicatedWeapon(item);
    const isLegacy = ItemUtils.isLegacy(item);
    const appliedWeaponReplacementStats =
      ItemUtils.getAppliedWeaponReplacementStats(item);

    let combinedMetaItems: Array<string> = [];

    if (isLegacy) {
      combinedMetaItems.push("Legacy");
    }
    if (type === Constants.WeaponTypeEnum.AMMUNITION) {
      combinedMetaItems.push("Ammunition");
    } else {
      if (
        isHexWeapon ||
        isPactWeapon ||
        isDedicatedWeapon ||
        appliedWeaponReplacementStats.length > 0
      ) {
        if (isHexWeapon) {
          combinedMetaItems.push("Hex Weapon");
        }
        if (isPactWeapon) {
          combinedMetaItems.push("Pact Weapon");
        }
        if (isDedicatedWeapon) {
          combinedMetaItems.push("Dedicated Weapon");
        }
        if (appliedWeaponReplacementStats.length > 0) {
          appliedWeaponReplacementStats.forEach((statId) => {
            combinedMetaItems.push(
              `Using ${RuleDataUtils.getStatNameById(statId, ruleData, true)}`
            );
          });
        }
      } else {
        combinedMetaItems.push(`${attackTypeName} Weapon`);
      }
    }
    if (ItemUtils.isOffhand(item)) {
      combinedMetaItems.push("Dual Wield");
    }
    if (ItemUtils.isAdamantine(item)) {
      combinedMetaItems.push("Adamantine");
    }
    if (ItemUtils.isSilvered(item)) {
      combinedMetaItems.push("Silvered");
    }
    if (ItemUtils.isCustomized(item)) {
      combinedMetaItems.push("Customized");
    }
    if (ItemUtils.getMasteryName(item)) {
      combinedMetaItems.push("Mastery");
    }
    combinedMetaItems = [...combinedMetaItems, ...metaItems];

    let versatileDamageNode: React.ReactNode;
    if (versatileDamage) {
      versatileDamageNode = (
        <DigitalDiceWrapper
          diceNotation={DiceUtils.renderDice(versatileDamage)}
          rollType={RollType.Damage}
          rollAction={ItemUtils.getName(attack.data as Item)}
          rollKind={isCriticalHit ? RollKind.CriticalHit : RollKind.None}
          diceEnabled={diceEnabled}
          themeColor={theme.themeColor}
          rollContext={rollContext}
          rollTargetOptions={
            messageTargetOptions
              ? Object.values(messageTargetOptions?.entities)
              : undefined
          }
          rollTargetDefault={defaultMessageTargetOption}
          userId={userId}
        >
          <Damage
            damage={DiceComponentUtils.getDamageDiceNotation(
              versatileDamage,
              isCriticalHit
            )}
            type={damageType}
            theme={theme}
            isVersatile={true}
          />
        </DigitalDiceWrapper>
      );
    }
    let classNames: Array<string> = ["ddbc-combat-item-attack__damage"];
    if (versatileDamage) {
      classNames.push("ddb-combat-item-attack__damage--is-versatile");
    }

    let damageNode: React.ReactNode;
    if (damage === null) {
      damageNode = null;
    } else {
      damageNode = (
        <DigitalDiceWrapper
          diceNotation={
            typeof damage === "number"
              ? damage.toString()
              : DiceUtils.renderDice(damage)
          }
          rollType={RollType.Damage}
          rollAction={ItemUtils.getName(attack.data as Item)}
          rollKind={isCriticalHit ? RollKind.CriticalHit : RollKind.None}
          diceEnabled={diceEnabled}
          themeColor={theme.themeColor}
          rollContext={rollContext}
          rollTargetOptions={
            messageTargetOptions
              ? Object.values(messageTargetOptions?.entities)
              : undefined
          }
          rollTargetDefault={defaultMessageTargetOption}
          userId={userId}
        >
          <Damage
            damage={DiceComponentUtils.getDamageDiceNotation(
              damage,
              isCriticalHit
            )}
            type={damageType}
            theme={theme}
          />
        </DigitalDiceWrapper>
      );
    }

    let damageDisplayNode: React.ReactNode = (
      <div className={classNames.join(" ")}>
        {damageNode}
        {versatileDamageNode}
      </div>
    );

    const showRange: boolean =
      attackType === Constants.AttackTypeRangeEnum.RANGED ||
      ItemUtils.hasWeaponProperty(item, Constants.WeaponPropertyEnum.THROWN) ||
      ItemUtils.hasWeaponProperty(item, Constants.WeaponPropertyEnum.RANGE);

    let rangeValue: React.ReactNode;
    let rangeLabel: React.ReactNode;
    if (showRange) {
      rangeValue = (
        <React.Fragment>
          <span
            className={`ddbc-combat-attack__range-value-close ${
              theme.isDarkMode
                ? "ddbc-combat-attack__range-value-close--dark-mode"
                : ""
            }`}
          >
            {range}
          </span>
          {longRange !== null && (
            <span
              className={`ddbc-combat-attack__range-value-long ${
                theme.isDarkMode
                  ? "ddbc-combat-attack__range-value-long--dark-mode"
                  : ""
              }`}
            >
              ({longRange})
            </span>
          )}
        </React.Fragment>
      );
      rangeLabel = "";
    } else {
      rangeValue = <NumberDisplay type="distanceInFt" number={reach} />;
    }

    let filteredWeaponSpellDamageGroups =
      ItemUtils.getApplicableWeaponSpellDamageGroups(
        item,
        weaponSpellDamageGroups
      );

    let iconKey: string = `weapon-${FormatUtils.slugify(attackTypeName)}`;
    if (filteredWeaponSpellDamageGroups.length) {
      iconKey = "weapon-spell-damage";
    }
    let attackClassNames: Array<string> = [
      "ddbc-combat-attack--item",
      `ddbc-combat-item-attack--${FormatUtils.slugify(attackTypeName)}`,
      className,
    ];
    if (isCriticalHit) {
      attackClassNames.push("ddbc-combat-attack--crit");
    }
    return (
      <CombatAttack
        attack={attack}
        className={attackClassNames.join(" ")}
        icon={
          <>
            {ItemUtils.getMasteryName(item) && (
              <Tooltip title="Weapon Mastery" isDarkMode={theme.isDarkMode}>
                <StarIcon
                  style={{ fill: theme.themeColor, marginLeft: "1px" }}
                />
              </Tooltip>
            )}
            <AttackTypeIcon
              actionType={Constants.ActionTypeEnum.WEAPON}
              rangeType={attackType ?? Constants.AttackTypeRangeEnum.MELEE}
              themeMode={theme.isDarkMode ? "gray" : "dark"}
              className={`ddbc-combat-attack__icon-img--${iconKey}`}
              overrideType={
                filteredWeaponSpellDamageGroups.length ? "weapon-spell" : null
              }
            />
          </>
        }
        name={<ItemName item={item} />}
        metaItems={combinedMetaItems}
        rangeValue={rangeValue}
        rangeLabel={rangeLabel}
        isProficient={proficiency}
        toHit={toHit}
        damage={damageDisplayNode}
        notes={this.renderNotes()}
        showNotes={showNotes}
        onClick={this.handleClick}
        diceEnabled={diceEnabled}
        onRoll={this.handleRoll}
        rollContext={rollContext}
        theme={theme}
      />
    );
  }
}

CombatItemAttack.contextType = GameLogContext;

export default CombatItemAttack;
