import React from "react";
import { connect, DispatchProp } from "react-redux";
import { useSearchParams } from "react-router-dom";
import { AnyAction } from "redux";

import {
  AnySimpleDataType,
  characterActions,
  CharacterPreferences,
  CharacterTheme,
  CharacterUtils,
  CharClass,
  ClassSpellListSpellsLookup,
  ClassUtils,
  Constants,
  DataOriginRefData,
  HelperUtils,
  HtmlSelectOption,
  Race,
  RuleData,
  RuleDataUtils,
  rulesEngineSelectors,
  RaceUtils,
} from "@dndbeyond/character-rules-engine/es";

import { CheckboxGroup, CheckboxInfo } from "~/components/CheckboxGroup";
import { PrivacyTypeRadio } from "~/components/PrivacyTypeRadio";
import { SourceCategoryDescription } from "~/constants";
import { useSidebar } from "~/contexts/Sidebar";
import { PaneInfo } from "~/contexts/Sidebar/Sidebar";
import { Header } from "~/subApps/sheet/components/Sidebar/components/Header";
import { PaneComponentEnum } from "~/subApps/sheet/components/Sidebar/types";

import config from "../../../../config";
import { appEnvActions } from "../../../actions";
import { appEnvSelectors } from "../../../selectors";
import { SharedAppState } from "../../../stores/typings";
import { PaneIdentifierUtils } from "../../../utils";
import PreferencesPaneSelectField from "./PreferencesPaneSelectField";
import styles from "./styles.module.css";

// 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> {
  activeSourceCategories: Array<number>;
  preferences: CharacterPreferences;
  ruleData: RuleData;
  diceEnabled: boolean;
  classes: Array<CharClass>;
  species: Race | null;
  dataOriginRefData: DataOriginRefData;
  classSpellListSpellsLookup: ClassSpellListSpellsLookup;
  characterTheme: CharacterTheme;
  paneHistoryStart: PaneInfo["paneHistoryStart"];
  isVttView: boolean;
}
class PreferencesPane extends React.PureComponent<Props> {
  static defaultProps = {
    diceEnabled: false,
    isVttView: false,
  };

  handlePreferenceChange = (
    prefKey: string,
    value: AnySimpleDataType
  ): void => {
    const { dispatch } = this.props;
    const typedPrefKey = CharacterUtils.getPreferenceKey(prefKey);
    if (typedPrefKey !== null) {
      dispatch(characterActions.preferenceChoose(typedPrefKey, value));
    }
  };

  handleIntPreferenceChange = (prefKey: string, value: number | null): void => {
    const { dispatch } = this.props;
    const typedPrefKey = CharacterUtils.getPreferenceKey(prefKey);
    if (typedPrefKey !== null) {
      dispatch(characterActions.preferenceChoose(typedPrefKey, value));
    }
  };

  handleSourceCategoryChange = (sourceId: number, isActive: boolean): void => {
    const { dispatch, activeSourceCategories } = this.props;
    let newSourceCats: Array<number> = [];

    if (isActive) {
      newSourceCats = [...activeSourceCategories, sourceId];
    } else {
      newSourceCats = activeSourceCategories.filter((id) => id !== sourceId);
    }

    dispatch(characterActions.activeSourceCategoriesSet(newSourceCats));
  };

  handleOptionalClassFeaturesPreferenceChangePromise = (
    newIsEnabled: boolean,
    accept: () => void,
    reject: () => void
  ): void => {
    const { paneHistoryStart, classes, classSpellListSpellsLookup } =
      this.props;
    const spellListIds: Array<number> =
      ClassUtils.getUpdateEnableOptionalClassFeaturesSpellListIdsToRemove(
        classes,
        newIsEnabled
      );

    const hasSpellsToRemove = spellListIds.some((id) =>
      classSpellListSpellsLookup.hasOwnProperty(id)
    );

    if (!hasSpellsToRemove) {
      this.handlePreferenceChange("enableOptionalClassFeatures", newIsEnabled);
      accept();
    } else {
      paneHistoryStart(
        PaneComponentEnum.PREFERENCES_OPTIONAL_CLASS_FEATURES_CONFIRM,
        PaneIdentifierUtils.generatePreferenceOptionalClassFeaturesConfirm(
          spellListIds,
          newIsEnabled
        )
      );
      reject();
    }
  };

  handleOptionalOriginsPreferenceChangePromise = (
    newIsEnabled: boolean,
    accept: () => void,
    reject: () => void
  ): void => {
    const { paneHistoryStart, species, classSpellListSpellsLookup } =
      this.props;

    if (!species) {
      this.handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
      accept();

      return;
    }

    const spellListIds: Array<number> =
      RaceUtils.getUpdateEnableOptionalOriginsSpellListIdsToRemove(
        species,
        newIsEnabled
      );

    const hasSpellsToRemove = spellListIds.some((id) =>
      classSpellListSpellsLookup.hasOwnProperty(id)
    );

    if (!hasSpellsToRemove) {
      this.handlePreferenceChange("enableOptionalOrigins", newIsEnabled);
      accept();
    } else {
      paneHistoryStart(
        PaneComponentEnum.PREFERENCES_OPTIONAL_ORIGINS_CONFIRM,
        PaneIdentifierUtils.generatePreferenceOptionalOriginsConfirm(
          spellListIds,
          newIsEnabled
        )
      );
      reject();
    }
  };

  handleProgressionPreferenceChangePromise = (
    newId: string,
    oldId: string,
    accept: () => void,
    reject: () => void
  ): void => {
    const { paneHistoryStart } = this.props;

    const newIdValue = HelperUtils.parseInputInt(newId);

    if (newIdValue !== null) {
      paneHistoryStart(
        PaneComponentEnum.PREFERENCES_PROGRESSION_CONFIRM,
        PaneIdentifierUtils.generatePreferenceProgressionConfirm(newIdValue)
      );
    }

    reject();
  };

  handleHitPointPreferenceChangePromise = (
    newId: string,
    oldId: string,
    accept: () => void,
    reject: () => void
  ): void => {
    const { paneHistoryStart } = this.props;

    const newIdValue = HelperUtils.parseInputInt(newId);

    if (newIdValue !== null) {
      paneHistoryStart(
        PaneComponentEnum.PREFERENCES_HIT_POINT_CONFIRM,
        PaneIdentifierUtils.generatePreferenceHitPointConfirm(newIdValue)
      );
    }

    reject();
  };

  handleDiceToggle = (): void => {
    const { dispatch, diceEnabled } = this.props;

    const newDiceEnabledSetting: boolean = !diceEnabled;

    try {
      localStorage.setItem("dice-enabled", newDiceEnabledSetting.toString());
    } catch (e) {}

    dispatch(
      appEnvActions.dataSet({
        diceEnabled: newDiceEnabledSetting,
      })
    );
  };

  handleChangePrivacy = (value: number | null): void => {
    const typedPrefKey = CharacterUtils.getPreferenceKey("privacyType");
    if (typedPrefKey !== null) {
      this.props.dispatch(
        characterActions.preferenceChoose(typedPrefKey, value)
      );
    }
  };

  render() {
    const {
      preferences,
      activeSourceCategories,
      ruleData,
      diceEnabled,
      isVttView,
    } = this.props;

    const {
      useHomebrewContent,
      encumbranceType,
      hitPointType,
      progressionType,
      abilityScoreDisplayType,
      privacyType,
      ignoreCoinWeight,
      enforceFeatRules,
      enforceMulticlassRules,
      showScaledSpells,
      enableOptionalClassFeatures,
      enableOptionalOrigins,
      enableDarkMode,
    } = preferences;
    const sourceCategories = RuleDataUtils.getSourceCategories(ruleData);

    const encumbranceOptions: Array<HtmlSelectOption> = [
      {
        label: "Use Encumbrance",
        value: Constants.PreferenceEncumbranceTypeEnum.ENCUMBRANCE,
      },
      {
        label: "No Encumbrance",
        value: Constants.PreferenceEncumbranceTypeEnum.NONE,
      },
      {
        label: "Variant Encumbrance",
        value: Constants.PreferenceEncumbranceTypeEnum.VARIANT,
      },
    ];

    const hpOptions: Array<HtmlSelectOption> = [
      { label: "Fixed", value: Constants.PreferenceHitPointTypeEnum.FIXED },
      { label: "Manual", value: Constants.PreferenceHitPointTypeEnum.MANUAL },
    ];

    const advancementOptions: Array<HtmlSelectOption> = [
      {
        label: "Milestone",
        value: Constants.PreferenceProgressionTypeEnum.MILESTONE,
      },
      { label: "XP", value: Constants.PreferenceProgressionTypeEnum.XP },
    ];

    const abilityDisplayOptions: Array<HtmlSelectOption> = [
      {
        label: "Modifiers Top",
        value: Constants.PreferenceAbilityScoreDisplayTypeEnum.MODIFIERS_TOP,
      },
      {
        label: "Scores Top",
        value: Constants.PreferenceAbilityScoreDisplayTypeEnum.SCORES_TOP,
      },
    ];

    let sourceToggles: Array<CheckboxInfo> = [];
    let partneredSourceToggles: Array<CheckboxInfo> = [];

    sourceCategories.forEach((sourceCategory) => {
      if (!sourceCategory.isToggleable) {
        return null;
      }

      const toggle: CheckboxInfo = {
        label: `${sourceCategory.name}`,
        initiallyEnabled: activeSourceCategories.includes(sourceCategory.id),
        onChange: (e) =>
          this.handleSourceCategoryChange(sourceCategory.id, e.target.checked),
        sortOrder: sourceCategory.sortOrder,
      };

      if (sourceCategory.isPartneredContent) {
        partneredSourceToggles.push(toggle);
      } else {
        sourceToggles.push(toggle);
      }
    });

    return (
      <div className="ct-preferences-pane">
        <Header>Preferences</Header>
        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Sources"
          description={SourceCategoryDescription.official}
          name="source-categories"
          checkboxes={[
            ...sourceToggles,
            {
              label: "Homebrew",
              initiallyEnabled: useHomebrewContent,
              onChange: (e) =>
                this.handlePreferenceChange(
                  "useHomebrewContent",
                  e.target.checked
                ),
              sortOrder: 0,
            },
          ]}
          variant="collapse"
          accordionDefaultOpen={true}
          themed
        />
        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Partnered Content"
          description={SourceCategoryDescription.partnered}
          name="partnered-source-categories"
          checkboxes={partneredSourceToggles}
          variant="collapse"
          themed
        />
        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Underdark Mode"
          description="Enables dark mode for this character"
          name="enable-dark-mode"
          variant="inline"
          data-testid="underdark-mode-toggle"
          checkboxes={[
            {
              initiallyEnabled: enableDarkMode,
              label: "Enable dark mode",
              onChange: (e) =>
                this.handlePreferenceChange("enableDarkMode", e.target.checked),
            },
          ]}
          themed
        />
        {!isVttView && (
          <CheckboxGroup
            className={styles.checkboxGroup}
            title="Dice Rolling"
            description="Enables digital dice rolling for all characters on this browser"
            name="enable-dice-rolling"
            variant="inline"
            checkboxes={[
              {
                initiallyEnabled: diceEnabled,
                label: "Enable Dice Rolling",
                onChange: this.handleDiceToggle,
              },
            ]}
            themed
          />
        )}
        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Optional Features"
          description="Allow or restrict optional features for this character"
          name="optional-features"
          checkboxes={[
            {
              label: "Optional Class Features",
              initiallyEnabled: enableOptionalClassFeatures,
              onChangeConfirm:
                this.handleOptionalClassFeaturesPreferenceChangePromise,
            },
            {
              label: "Customize Your Origin",
              initiallyEnabled: enableOptionalOrigins,
              onChangeConfirm:
                this.handleOptionalOriginsPreferenceChangePromise,
            },
          ]}
          themed
        />

        <PreferencesPaneSelectField
          className={styles.selectField}
          heading="Advancement Type"
          description="Story-based character progression / XP-based character progression"
          onChangePromise={this.handleProgressionPreferenceChangePromise}
          initialOptionRemoved={true}
          options={advancementOptions}
          initialValue={progressionType}
          block={true}
        />
        <PreferencesPaneSelectField
          className={styles.selectField}
          heading="Hit Point Type"
          description="When leveling up, increase hit points by the fixed value for your chosen class or manually enter a rolled value"
          onChangePromise={this.handleHitPointPreferenceChangePromise}
          initialOptionRemoved={true}
          options={hpOptions}
          initialValue={hitPointType}
          block={true}
        />

        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Use Prerequisites"
          description="Allow or restrict choices based on rule prerequisites for the following for this character"
          name="use-prerequisites"
          checkboxes={[
            {
              label: "Feats",
              initiallyEnabled: enforceFeatRules,
              onChange: (e) =>
                this.handlePreferenceChange(
                  "enforceFeatRules",
                  e.target.checked
                ),
            },
            {
              label: "Multiclass Requirements",
              initiallyEnabled: enforceMulticlassRules,
              onChange: (e) =>
                this.handlePreferenceChange(
                  "enforceMulticlassRules",
                  e.target.checked
                ),
            },
          ]}
          themed={true}
        />
        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Show level-scaled spells"
          description="Display and highlight available spells to cast with higher level spell slots"
          name="show-scaled-spells"
          variant="inline"
          checkboxes={[
            {
              initiallyEnabled: showScaledSpells,
              label: "Show Level-Scaled Spells",
              onChange: (e) =>
                this.handlePreferenceChange(
                  "showScaledSpells",
                  e.target.checked
                ),
            },
          ]}
          themed
        />

        <PreferencesPaneSelectField
          className={styles.selectField}
          heading="Encumbrance Type"
          description="Use the standard encumbrance rules / Disable the encumbrance display / Use the more detailed rules for encumbrance"
          onChange={(value) =>
            this.handleIntPreferenceChange("encumbranceType", value)
          }
          initialOptionRemoved={true}
          options={encumbranceOptions}
          initialValue={encumbranceType}
          block={true}
        />

        <CheckboxGroup
          className={styles.checkboxGroup}
          title="Ignore Coin Weight"
          description="Coins do not count against your total weight carried (50 coins weigh 1 lb.)"
          name="ignore-coin-weight"
          variant="inline"
          checkboxes={[
            {
              initiallyEnabled: ignoreCoinWeight,
              label: "Ignore Coin Weight",
              onChange: (e) =>
                this.handlePreferenceChange(
                  "ignoreCoinWeight",
                  e.target.checked
                ),
            },
          ]}
          themed
        />

        <PreferencesPaneSelectField
          className={styles.selectField}
          heading="Ability Score/Modifier Display"
          description="Reverse the arrangement of ability modifiers and scores"
          onChange={(value) =>
            this.handleIntPreferenceChange("abilityScoreDisplayType", value)
          }
          initialOptionRemoved={true}
          options={abilityDisplayOptions}
          initialValue={abilityScoreDisplayType}
          block={true}
        />

        <PrivacyTypeRadio
          className={styles.privacyTypeRadio}
          initialValue={privacyType}
          onChange={(e) =>
            this.handleChangePrivacy(
              HelperUtils.parseInputInt(e?.target?.value)
            )
          }
          themed={true}
        />

        <footer className={styles.footer}>
          <strong>Version:</strong>
          {config.version || "@dndbeyond/character-app@dev"}
        </footer>
      </div>
    );
  }
}

function mapStateToProps(state: SharedAppState) {
  return {
    ruleData: rulesEngineSelectors.getRuleData(state),
    preferences: rulesEngineSelectors.getCharacterPreferences(state),
    activeSourceCategories:
      rulesEngineSelectors.getActiveSourceCategories(state),
    diceEnabled: appEnvSelectors.getDiceEnabled(state),
    classes: rulesEngineSelectors.getClasses(state),
    species: rulesEngineSelectors.getRace(state),
    classSpellListSpellsLookup:
      rulesEngineSelectors.getClassSpellListSpellsLookup(state),
    dataOriginRefData: rulesEngineSelectors.getDataOriginRefData(state),
    characterTheme: rulesEngineSelectors.getCharacterTheme(state),
  };
}

const PreferencesPaneContainer = (props) => {
  const {
    pane: { paneHistoryStart },
  } = useSidebar();
  const [searchParams] = useSearchParams();
  const isVttView = searchParams.get("view") === "vtt";
  return (
    <PreferencesPane
      paneHistoryStart={paneHistoryStart}
      isVttView={isVttView}
      {...props}
    />
  );
};

export default connect(mapStateToProps)(PreferencesPaneContainer);
