import React from "react";

import {
  ApiAdapterPromise,
  ApiAdapterRequestConfig,
  ApiResponse,
  CharacterPreferences,
  CharClass,
  ChoiceData,
  ChoiceUtils,
  ClassDefinitionContract,
  ClassFeature,
  ClassFeatureUtils,
  ClassUtils,
  Constants,
  DefinitionPool,
  EntitledEntity,
  FeatDefinitionContract,
  FeatLookup,
  FormatUtils,
  HelperUtils,
  InfusionChoice,
  InfusionChoiceUtils,
  InfusionDefinitionContract,
  InfusionUtils,
  InventoryManager,
  KnownInfusionUtils,
  Modifier,
  OptionalClassFeatureLookup,
  OptionalClassFeatureUtils,
  PrerequisiteData,
  RuleData,
  TypeValueLookup,
} from "@dndbeyond/character-rules-engine/es";

import { Accordion } from "~/components/Accordion";
import { CollapsibleContent } from "~/components/CollapsibleContent";
import { FeatureChoices } from "~/components/FeatureChoices";
import { Link } from "~/components/Link";
import { GrantedFeat } from "~/tools/js/CharacterBuilder/components/GrantedFeat/GrantedFeat";

import InfusionChoiceManager from "../../../../../Shared/components/InfusionChoiceManager";

interface Props {
  isActive: boolean;
  charClass: CharClass;
  choiceInfo: ChoiceData;
  feature: ClassFeature;
  prerequisiteData: PrerequisiteData;
  isStartingClass: boolean;
  preferences: CharacterPreferences;
  globalModifiers: Array<Modifier>;
  typeValueLookup: TypeValueLookup;
  ruleData: RuleData;
  definitionPool: DefinitionPool;
  knownInfusionLookup: Record<string, InfusionChoice>;
  knownReplicatedItems: Array<string>;
  onChoiceChange?: (
    classFeatureId: number,
    choiceId: string,
    type: any,
    value: number | null,
    parentChoiceId: string | null,
    hasItemMappings: boolean
  ) => void;
  onInfusionChoiceItemChangePromise?: (
    infusionChoiceKey: string,
    infusionId: string,
    itemDefinitionKey: string,
    itemName: string,
    accept: () => void,
    reject: () => void
  ) => void;
  onInfusionChoiceItemDestroyPromise?: (
    infusionChoiceKey: string,
    accept: () => void,
    reject: () => void
  ) => void;
  onInfusionChoiceChangePromise?: (
    infusionChoiceKey: string,
    infusionId: string,
    accept: () => void,
    reject: () => void
  ) => void;
  onInfusionChoiceDestroyPromise?: (
    infusionChoiceKey: string,
    accept: () => void,
    reject: () => void
  ) => void;
  onInfusionChoiceCreatePromise?: (
    infusionChoiceKey: string,
    infusionId: string,
    accept: () => void,
    reject: () => void
  ) => void;
  loadAvailableSubclasses: (
    baseClassId: number,
    additionalConfig?: Partial<ApiAdapterRequestConfig>
  ) => ApiAdapterPromise<ApiResponse<Array<ClassDefinitionContract>>>;
  loadAvailableFeats: (
    additionalConfig?: Partial<ApiAdapterRequestConfig>
  ) => ApiAdapterPromise<ApiResponse<Array<FeatDefinitionContract>>>;
  loadAvailableInfusions: (
    additionalConfig?: Partial<ApiAdapterRequestConfig>
  ) => ApiAdapterPromise<
    ApiResponse<EntitledEntity<InfusionDefinitionContract>>
  >;
  onDefinitionsLoaded?: (
    definitions: Array<InfusionDefinitionContract>,
    accessTypes: Record<string, number>
  ) => void;
  featLookup: FeatLookup;
  optionalClassFeatureLookup: OptionalClassFeatureLookup;
  inventoryManager: InventoryManager;
}
interface State {
  collapsibleOpened: boolean;
}
export default class ClassManagerFeature extends React.PureComponent<
  Props,
  State
> {
  static defaultProps = {
    isActive: true,
    featLookup: {},
  };

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

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

  hasSubclassChoice = (): boolean => {
    const { feature } = this.props;

    const choices = ClassFeatureUtils.getChoices(feature);
    return choices.some(
      (choice) =>
        ChoiceUtils.getType(choice) ===
        Constants.BuilderChoiceTypeEnum.SUB_CLASS_OPTION
    );
  };

  handleCollapsibleOpened = (id: string, state: boolean): void => {
    this.setState({
      collapsibleOpened: state,
    });
  };

  handleChoiceChange = (
    id: string,
    type: number,
    value: any,
    parentChoiceId: string | null
  ): void => {
    const { onChoiceChange, feature } = this.props;

    if (onChoiceChange) {
      onChoiceChange(
        ClassFeatureUtils.getId(feature),
        id,
        type,
        HelperUtils.parseInputInt(value),
        parentChoiceId,
        ClassFeatureUtils.getHasItemMappings(feature)
      );
    }
  };

  getAvailableInfusionChoices = (): Array<InfusionChoice> => {
    const { feature } = this.props;

    return ClassFeatureUtils.getInfusionChoices(feature).filter(
      InfusionChoiceUtils.validateIsAvailable
    );
  };

  getInfusionChoiceCount = (): number => {
    return this.getAvailableInfusionChoices().length;
  };

  getTodoInfusionChoiceCount = (): number => {
    return this.getAvailableInfusionChoices().filter(
      (infusionChoice) =>
        InfusionChoiceUtils.getKnownInfusion(infusionChoice) === null
    ).length;
  };

  getInfusionItemChoiceCount = (): number => {
    return this.getAvailableInfusionChoices().filter((infusionChoice) => {
      const knownInfusion =
        InfusionChoiceUtils.getKnownInfusion(infusionChoice);
      if (knownInfusion === null) {
        return false;
      }
      const simulatedInfusion =
        KnownInfusionUtils.getSimulatedInfusion(knownInfusion);
      if (simulatedInfusion === null) {
        return false;
      }
      return (
        InfusionUtils.getType(simulatedInfusion) ===
        Constants.InfusionTypeEnum.REPLICATE
      );
    }).length;
  };

  getTodoInfusionItemChoiceCount = (): number => {
    return this.getAvailableInfusionChoices().filter((infusionChoice) => {
      const knownInfusion =
        InfusionChoiceUtils.getKnownInfusion(infusionChoice);
      if (knownInfusion === null) {
        return false;
      }
      const simulatedInfusion =
        KnownInfusionUtils.getSimulatedInfusion(knownInfusion);
      if (simulatedInfusion === null) {
        return false;
      }
      if (
        InfusionUtils.getType(simulatedInfusion) !==
        Constants.InfusionTypeEnum.REPLICATE
      ) {
        return false;
      }
      if (KnownInfusionUtils.getItemId(knownInfusion) !== null) {
        return false;
      }
      return true;
    }).length;
  };

  getChoiceCount = (): number => {
    const { feature, isActive } = this.props;
    return isActive ? ClassFeatureUtils.getChoices(feature).length : 0;
  };

  getTodoChoiceCount = (): number => {
    const { feature, isActive } = this.props;
    return ClassFeatureUtils.getChoices(feature).filter(
      (feature) => isActive && ChoiceUtils.isTodo(feature)
    ).length;
  };

  getTotalChoiceCount = (): number => {
    return (
      this.getChoiceCount() +
      this.getInfusionChoiceCount() +
      this.getInfusionItemChoiceCount()
    );
  };

  getTotalTodoCount = (): number => {
    return (
      this.getTodoChoiceCount() +
      this.getTodoInfusionChoiceCount() +
      this.getTodoInfusionItemChoiceCount()
    );
  };

  getClassFeatureDescription = (): string => {
    const { feature, isStartingClass } = this.props;

    const multiClassDescription =
      ClassFeatureUtils.getMultiClassDescription(feature);
    if (
      !isStartingClass &&
      multiClassDescription &&
      multiClassDescription.length > 0
    ) {
      return multiClassDescription;
    }

    const description = ClassFeatureUtils.getDescription(feature);
    return description === null ? "" : description;
  };

  getMetaItems = () => {
    const { feature, optionalClassFeatureLookup, definitionPool } = this.props;
    const choiceCount = this.getTotalChoiceCount();
    const featureType = ClassFeatureUtils.getFeatureType(feature);
    let metaItems: Array<string> = [];
    // Display the number of choices if there are any
    if (choiceCount) {
      metaItems.push(`${choiceCount} Choice${choiceCount !== 1 ? "s" : ""}`);
    }
    // Display the required level
    metaItems.push(
      `${FormatUtils.ordinalize(
        ClassFeatureUtils.getRequiredLevel(feature)
      )} level`
    );
    // Display that this is an optional class feature if it is
    if (featureType !== Constants.FeatureTypeEnum.GRANTED) {
      metaItems.push("Optional Class Feature");
    }
    // Display the feature that this feature replaces if it is a replacement
    if (featureType === Constants.FeatureTypeEnum.REPLACEMENT) {
      const optionalFeature = HelperUtils.lookupDataOrFallback(
        optionalClassFeatureLookup,
        ClassFeatureUtils.getDefinitionKey(feature)
      );

      if (optionalFeature) {
        const affectedDefinitionKey =
          OptionalClassFeatureUtils.getAffectedClassFeatureDefinitionKey(
            optionalFeature
          );

        if (affectedDefinitionKey) {
          const replacedFeature = ClassFeatureUtils.simulateClassFeature(
            affectedDefinitionKey,
            definitionPool
          );

          if (replacedFeature) {
            metaItems.push(
              `Replaces ${ClassFeatureUtils.getName(replacedFeature)}`
            );
          }
        }
      }
    }

    return metaItems;
  };

  render() {
    const {
      isActive,
      charClass,
      definitionPool,
      ruleData,
      globalModifiers,
      typeValueLookup,
      loadAvailableInfusions,
      onInfusionChoiceItemChangePromise,
      onInfusionChoiceItemDestroyPromise,
      onInfusionChoiceCreatePromise,
      onInfusionChoiceDestroyPromise,
      onInfusionChoiceChangePromise,
      onDefinitionsLoaded,
      knownReplicatedItems,
      knownInfusionLookup,
      feature,
      inventoryManager,
    } = this.props;
    const { collapsibleOpened } = this.state;

    // If the class feature grants feats, render those instead of the class feature.
    if (feature.featLists.length > 0) {
      return feature.featLists.map((fl) => (
        <GrantedFeat
          featList={fl}
          key={fl.definition.id}
          requiredLevel={ClassFeatureUtils.getRequiredLevel(feature)}
        />
      ));
    }

    const conClsNames: Array<string> = ["class-manager-feature-name"];

    if (isActive) {
      if (this.getTotalTodoCount() > 0) {
        conClsNames.push("collapsible-todo");
      }
    }
    const hasChoices = this.getTotalTodoCount() > 0;
    const accordionId = ClassFeatureUtils.getId(feature).toString();

    return (
      <Accordion
        id={accordionId}
        summary={ClassFeatureUtils.getName(feature)}
        summaryMetaItems={this.getMetaItems()}
        variant="paper"
        showAlert={hasChoices}
        handleIsOpen={this.handleCollapsibleOpened}
      >
        <CollapsibleContent className="class-manager-feature-description">
          {this.getClassFeatureDescription()}
        </CollapsibleContent>
        {isActive && this.hasSubclassChoice() && (
          <div className="ct-character-tools__marketplace-callout">
            Looking for something not in the list below? Unlock all official
            options in the <Link href="/marketplace">Marketplace</Link>.
          </div>
        )}
        {isActive && (
          <FeatureChoices
            choices={ClassFeatureUtils.getChoices(feature)}
            charClass={charClass}
            classFeature={feature}
            onChoiceChange={this.handleChoiceChange}
            shouldFetch={collapsibleOpened}
          />
        )}
        {isActive && (
          <InfusionChoiceManager
            infusionChoices={this.getAvailableInfusionChoices()}
            contextLevel={ClassUtils.getLevel(charClass)}
            definitionPool={definitionPool}
            ruleData={ruleData}
            globalModifiers={globalModifiers}
            typeValueLookup={typeValueLookup}
            knownInfusionLookup={knownInfusionLookup}
            knownReplicatedItems={knownReplicatedItems}
            loadAvailableInfusions={loadAvailableInfusions}
            onInfusionChoiceItemChangePromise={
              onInfusionChoiceItemChangePromise
            }
            onInfusionChoiceItemDestroyPromise={
              onInfusionChoiceItemDestroyPromise
            }
            onInfusionChoiceChangePromise={onInfusionChoiceChangePromise}
            onInfusionChoiceDestroyPromise={onInfusionChoiceDestroyPromise}
            onInfusionChoiceCreatePromise={onInfusionChoiceCreatePromise}
            onDefinitionsLoaded={onDefinitionsLoaded}
            inventoryManager={inventoryManager}
          />
        )}
      </Accordion>
    );
  }
}
