import * as React from "react";

import {
  AbilityLookup,
  Action,
  ActionUtils,
  Attack,
  CharacterTheme,
  Constants,
  DiceUtils,
  EntityUtils,
  FormatUtils,
  RuleData,
  RuleDataUtils,
} from "@dndbeyond/character-rules-engine/es";
import { GameLogContext } from "@dndbeyond/game-log-components";
import {
  RollKinds,
  RollTypes,
} from "@dndbeyond/pocket-dimension-dice/constants";
import { RollContext } from "@dndbeyond/pocket-dimension-dice/types";

import { NumberDisplay } from "~/components/NumberDisplay";

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

interface Props {
  attack: Attack;
  action: Action;
  onClick?: (attack: Attack) => void;
  ruleData: RuleData;
  abilityLookup: AbilityLookup;
  showNotes: boolean;
  className: string;
  diceEnabled: boolean;
  theme: CharacterTheme;
  rollContext: RollContext;
  proficiencyBonus: number;
}

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

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

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

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

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

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

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

  getRangeInfo = (): CombatActionAttackComponentRangeInfo => {
    const { action, ruleData, theme } = this.props;

    const attackRange = ActionUtils.getRange(action);
    const attackReach = ActionUtils.getReach(action);
    const attackTypeId = ActionUtils.getAttackRangeId(action);

    let rangeValueNode: React.ReactNode;
    let rangeLabel: string = "Range";

    switch (ActionUtils.getActionTypeId(action)) {
      case Constants.ActionTypeEnum.WEAPON:
        if (
          attackRange !== null &&
          attackTypeId === Constants.AttackTypeRangeEnum.RANGED
        ) {
          let { longRange, range } = attackRange;

          if (longRange) {
            rangeValueNode = (
              <>
                <span
                  className={`ddbc-combat-attack__range-value-close ${
                    theme.isDarkMode
                      ? "ddbc-combat-attack__range-value-close--dark-mode"
                      : ""
                  }`}
                >
                  {range}
                </span>
                <span
                  className={`ddbc-combat-attack__range-value-long ${
                    theme.isDarkMode
                      ? "ddbc-combat-attack__range-value-long--dark-mode"
                      : ""
                  }`}
                >
                  ({longRange})
                </span>
              </>
            );
            rangeLabel = "";
          } else {
            rangeValueNode = (
              <NumberDisplay type="distanceInFt" number={range} />
            );
            rangeLabel = "Range";
          }
        } else if (attackReach !== null) {
          rangeValueNode = (
            <NumberDisplay type="distanceInFt" number={attackReach} />
          );
          rangeLabel = "Reach";
        }
        break;

      case Constants.ActionTypeEnum.SPELL:
        if (attackRange !== null) {
          if (
            attackRange.origin &&
            attackRange.origin !== Constants.SpellRangeTypeEnum.RANGED
          ) {
            let spellRangeType = RuleDataUtils.getSpellRangeType(
              attackRange.origin,
              ruleData
            );
            if (spellRangeType) {
              rangeValueNode = spellRangeType.name;
            }
          }
          if (attackRange.range) {
            rangeValueNode = (
              <NumberDisplay type="distanceInFt" number={attackRange.range} />
            );
          }
        }

        rangeLabel = "";
        if (attackTypeId === Constants.AttackTypeRangeEnum.MELEE) {
          rangeLabel = "Reach";
        }
        break;

      case Constants.ActionTypeEnum.GENERAL:
        if (attackRange !== null && attackRange.range) {
          rangeValueNode = (
            <NumberDisplay type="distanceInFt" number={attackRange.range} />
          );
        }
        if (
          attackReach !== null &&
          attackTypeId === Constants.AttackTypeRangeEnum.MELEE
        ) {
          rangeValueNode = (
            <NumberDisplay type="distanceInFt" number={attackReach} />
          );
          rangeLabel = "Reach";
        }
        break;

      default:
      // not implemented
    }

    return {
      rangeValueNode,
      rangeLabel,
    };
  };

  getClassName = (): string => {
    const { action } = this.props;

    let type: string = "";
    switch (ActionUtils.getActionTypeId(action)) {
      case Constants.ActionTypeEnum.WEAPON:
        type = "weapon";
        break;

      case Constants.ActionTypeEnum.SPELL:
        type = "spell";
        break;

      case Constants.ActionTypeEnum.GENERAL:
        type = "general";
        break;
    }

    return FormatUtils.slugify(`ddbc-combat-action-attack--${type}`);
  };

  getIconKey = (): string => {
    const { action } = this.props;

    const attackTypeId = ActionUtils.getAttackRangeId(action);
    let attackType: string = "";
    if (attackTypeId) {
      attackType = RuleDataUtils.getAttackTypeRangeName(attackTypeId);
    }

    let type: string = "";
    let iconType: string = RuleDataUtils.getAttackTypeRangeName(
      Constants.AttackTypeRangeEnum.MELEE
    );

    switch (ActionUtils.getActionTypeId(action)) {
      case Constants.ActionTypeEnum.WEAPON:
        type = "weapon";
        iconType = attackType;
        break;

      case Constants.ActionTypeEnum.SPELL:
        type = "spell";
        if (attackType) {
          iconType = attackType;
        }
        break;

      case Constants.ActionTypeEnum.GENERAL:
        type = "general";
        if (attackType) {
          iconType = attackType;
        }
        break;

      default:
      //not implemented
    }

    return FormatUtils.slugify(`action-attack-${type}-${iconType}`);
  };

  getIconNode = (): React.ReactNode => {
    const { action, theme } = this.props;

    let rangeType: Constants.AttackTypeRangeEnum =
      ActionUtils.getAttackRangeId(action) ??
      Constants.AttackTypeRangeEnum.MELEE;

    //this sucks, but have to switch these around because of how unarmed strike is currently a weapon type
    //and most others that come thru here are custom actions
    let actionTypeId = ActionUtils.getActionTypeId(action);
    let actionType: Constants.ActionTypeEnum | null = actionTypeId;
    switch (actionTypeId) {
      case Constants.ActionTypeEnum.WEAPON:
        if (rangeType === Constants.AttackTypeRangeEnum.MELEE) {
          actionType = Constants.ActionTypeEnum.GENERAL;
        }
        break;

      case Constants.ActionTypeEnum.GENERAL:
        if (rangeType === Constants.AttackTypeRangeEnum.MELEE) {
          actionType = Constants.ActionTypeEnum.WEAPON;
        }
        break;
      case Constants.ActionTypeEnum.SPELL:
        break;

      default:
      //not implemented
    }

    return (
      <AttackTypeIcon
        actionType={actionType}
        rangeType={rangeType}
        themeMode={theme.isDarkMode ? "gray" : "dark"}
        className={`ddbc-combat-attack__icon-img--${this.getIconKey()}`}
      />
    );
  };

  getMetaItems = (): Array<string> => {
    const { action } = this.props;

    const damage = ActionUtils.getDamage(action);
    const attackTypeId = ActionUtils.getAttackRangeId(action);
    let attackType: string | null = null;
    if (attackTypeId) {
      attackType = RuleDataUtils.getAttackTypeRangeName(attackTypeId);
    }
    const isOffhand = ActionUtils.isOffhand(action);

    let combinedMetaItems: Array<string> = [];
    if (attackType) {
      combinedMetaItems.push(`${attackType} Attack`);
    }
    if (damage.isMartialArts) {
      combinedMetaItems.push("Martial Arts");
    }
    if (damage.value && damage.dataOrigin) {
      combinedMetaItems.push(EntityUtils.getDataOriginName(damage.dataOrigin));
    }

    switch (ActionUtils.getActionTypeId(action)) {
      case Constants.ActionTypeEnum.WEAPON:
        if (isOffhand) {
          combinedMetaItems.push("Dual Wield");
        }
        break;

      default:
      // not implemented
    }

    if (ActionUtils.isCustomized(action)) {
      combinedMetaItems.push("Customized");
    }

    return combinedMetaItems;
  };

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

    if (!showNotes) {
      return null;
    }

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

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

    const { isCriticalHit } = this.state;

    const proficiency = ActionUtils.isProficient(action);
    const damage = ActionUtils.getDamage(action);
    let rangeInfo = this.getRangeInfo();

    let toHit: number | null = null;
    let attackSaveValue: number | null = null;
    let attackSaveLabel: React.ReactNode = "";
    if (ActionUtils.requiresAttackRoll(action)) {
      toHit = ActionUtils.getToHit(action);
    } else if (ActionUtils.requiresSavingThrow(action)) {
      let saveStatId = ActionUtils.getSaveStatId(action);
      if (saveStatId) {
        attackSaveLabel = RuleDataUtils.getStatNameById(saveStatId, ruleData);
      }
      attackSaveValue = ActionUtils.getAttackSaveValue(action);
    }

    let damageNode: React.ReactNode;
    if (damage.value !== null) {
      damageNode = (
        <DigitalDiceWrapper
          diceNotation={
            typeof damage.value === "number"
              ? damage.value.toString()
              : DiceUtils.renderDice(damage.value)
          }
          rollType={RollTypes.Damage}
          action={ActionUtils.getName(attack.data as Action)}
          rollKind={isCriticalHit ? RollKinds.CriticalHit : undefined}
          isDiceEnabled={diceEnabled}
          entityId={String(rollContext.entityId)}
          entityType={String(rollContext.entityType)}
          name={String(rollContext.name)}
          rollCallback={() => {
            if (isCriticalHit) {
              this.setState({ isCriticalHit: false });
            }
          }}
        >
          <Damage
            type={damage.type ? damage.type.name : null}
            damage={DiceComponentUtils.getDamageDiceNotation(
              damage.value,
              isCriticalHit
            )}
            theme={theme}
          />
        </DigitalDiceWrapper>
      );
    }

    let classNames: Array<string> = [className, this.getClassName()];

    if (isCriticalHit) {
      classNames.push("ddbc-combat-attack--crit");
    }
    return (
      <CombatAttack
        attack={attack}
        className={classNames.join(" ")}
        icon={this.getIconNode()}
        name={<ActionName theme={theme} action={action} />}
        metaItems={this.getMetaItems()}
        rangeValue={rangeInfo.rangeValueNode}
        rangeLabel={rangeInfo.rangeLabel}
        isProficient={proficiency}
        toHit={toHit}
        attackSaveLabel={attackSaveLabel}
        attackSaveValue={attackSaveValue}
        damage={damageNode}
        onClick={this.handleClick}
        notes={this.renderNotes()}
        showNotes={showNotes}
        diceEnabled={diceEnabled}
        onRoll={this.handleRoll}
        theme={theme}
        rollContext={rollContext}
      />
    );
  }
}

CombatActionAttack.contextType = GameLogContext;

export default CombatActionAttack;
