import React, { useState } from "react";
import { connect, DispatchProp } from "react-redux";
import {
  matchPath,
  Route,
  Routes,
  useLocation,
  useNavigate,
  useSearchParams,
} from "react-router-dom";
import { AnyAction } from "redux";

import { CharacterAvatarPortrait } from "@dndbeyond/character-components/es";
import {
  CampaignDataContract,
  characterSelectors,
  CharacterStatusSlug,
} from "@dndbeyond/character-rules-engine";
import {
  DecorationInfo,
  DecorationUtils,
  FormatUtils,
  rulesEngineSelectors,
  characterActions,
} from "@dndbeyond/character-rules-engine/es";

import { Link } from "~/components/Link";
import { NotificationSystem } from "~/components/NotificationSystem";
import config from "~/config";
import { useHeadContext } from "~/contexts/Head";
import { useDispatch } from "~/hooks/useDispatch";
import { PortraitName } from "~/subApps/builder/components/PortraitName/PortraitName";
import { BuilderMethod } from "~/subApps/builder/constants";
import { ClassProvider } from "~/subApps/builder/contexts/Class";
import { ModalManagerProvider } from "~/subApps/builder/contexts/ModalManager";
import { SpeciesProvider } from "~/subApps/builder/contexts/Species";
import { BuilderTypeChoicePage } from "~/subApps/builder/routes/BuilderTypeChoicePage";
import pageStyles from "~/subApps/builder/styles/page.module.css";

import { appEnvActions, toastMessageActions } from "../../../Shared/actions";
import LoadingBlocker from "../../../Shared/components/LoadingBlocker";
import { BuilderLinkButton } from "../../../Shared/components/common/LinkButton";
import AppErrorTypeEnum from "../../../Shared/constants/AppErrorTypeEnum";
import UserRoles from "../../../Shared/constants/UserRoles";
import { DiceContainer } from "../../../Shared/containers/DiceContainer";
import {
  appEnvSelectors,
  appInfoSelectors,
  toastMessageSelectors,
} from "../../../Shared/selectors";
import { AppInfoErrorState } from "../../../Shared/stores/typings";
import { builderActions } from "../../actions/builder";
import { BUILDER_MQ_IS_MOBILE_SIZE } from "../../config";
import {
  getCurrentRouteDef,
  RouteDef,
  ROUTE_DEFINITIONS,
} from "../../config/navigation";
import { builderSelectors } from "../../selectors";
import { BuilderAppState } from "../../typings";
import { getUrlWithParams } from "../../utils/navigationUtils";
import NavigationSections from "../NavigationSections";
import SynchronousBlocker from "../SynchronousBlocker";
import TodoNavigation from "../TodoNavigation";
import styles from "./styles.module.css";

const BASE_PATHNAME = config.basePathname;

// 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> {
  appError: AppInfoErrorState | null;
  toastMessages: any;
  builderMethod: string | null;
  isCharacterLoading: boolean;
  isCharacterLoaded: boolean;
  isMobile: boolean;
  name: string | null;
  username: string | null;
  redirect: string;
  characterId: number | null;
  diceEnabled: boolean;
  decorationInfo: DecorationInfo;
  canEdit: boolean;
  isReadonly: boolean;
  userRoles: string[] | null | undefined;
  characterStatus: string | null;
  userId: number;
  campaign: CampaignDataContract | null;
  isVttView: boolean;
}

const RenderAuthError: React.FC<{ appError: Props["appError"] }> | null = ({
  appError,
}) => {
  if (appError === null) {
    return null;
  }

  let contentNode: React.ReactNode;
  switch (appError.type) {
    case AppErrorTypeEnum.AUTH_MISSING:
      let signInUrl: string = `/sign-in?returnUrl=${encodeURIComponent(
        window.location.pathname
      )}`;
      contentNode = (
        <>
          <p>
            You are no longer signed into D&D Beyond. Go to the{" "}
            <Link href={signInUrl}>Sign In page</Link> to continue the
            adventure!
          </p>
        </>
      );
      break;

    case AppErrorTypeEnum.API_FAIL:
      contentNode = (
        <>
          <p>
            Whoops! We rolled a 1 on our API check. We're heading into town to
            visit the blacksmith for repairs.
          </p>
          <p>Try again after a Short Rest.</p>
        </>
      );
      break;

    case AppErrorTypeEnum.API_DATA_FAIL:
      contentNode = (
        <>
          <p>
            Whoops! We rolled a 1 on our API Data check. We're heading into town
            to visit the blacksmith for repairs.
          </p>
          <p>Try again after a Short Rest.</p>
        </>
      );
      break;

    case AppErrorTypeEnum.AUTH_FAIL:
      contentNode = (
        <>
          <p>
            Whoops! We rolled a 1 on our Authentication check. We're heading
            into town to visit the blacksmith for repairs.
          </p>
          <p>Try again after a Short Rest.</p>
        </>
      );
      break;

    case AppErrorTypeEnum.ACCESS_DENIED:
      contentNode = (
        <>
          <p>You don't have access to this page.</p>
          <p>
            If you are trying to access a character sheet, make sure its privacy
            setting is configured correctly for you to access it.
          </p>
          <p>
            It is also possible that you are trying to enter the 403rd level of
            the endless dungeon and the ancient dragon Rylzrayrth is rising up
            to block your path...
          </p>
        </>
      );
      break;
    case AppErrorTypeEnum.NOT_FOUND:
      appError.errorId = null;
      contentNode = (
        <>
          <p>
            We live in a world of uncertainty. But certainly, the page you were
            looking for isn’t here. Perhaps this halfling has stolen it and
            hidden it in another place. Try searching for what you were looking
            for in another realm.
          </p>
        </>
      );
      break;

    case AppErrorTypeEnum.GENERIC:
    default:
      contentNode = (
        <>
          <p>
            Whoops! We rolled a 1 on our check. We're heading into town to visit
            the blacksmith for repairs.
          </p>
          <p>Try again after a Short Rest.</p>
        </>
      );
      break;
  }

  return (
    <div className="ct-character-sheet--failed">
      <div
        className={[
          "ct-character-sheet__failed",
          `ct-character-sheet__failed--${FormatUtils.slugify(appError.type)}`,
        ].join(" ")}
      >
        <div className="ct-character-sheet__failed-content">
          {contentNode}
          {appError.errorId && (
            <>
              <div className="ct-character-sheet__failed-code">
                <div className="ct-character-sheet__failed-code-label">
                  Error Code
                </div>
                <div className="ct-character-sheet__failed-code-value">
                  {appError.errorId}
                </div>
              </div>
              <div className="ct-character-sheet__failed-code">
                <div className="ct-character-sheet__failed-code-label">
                  Version
                </div>
                <div className="ct-character-sheet__failed-code-value">
                  {config.version}
                </div>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
};

const checkForMessages = (
  toastMessages,
  dispatch,
  notificationSystem
): void => {
  Object.keys(toastMessages).forEach((messageId) => {
    if (!notificationSystem) {
      return;
    }

    const message = toastMessages[messageId];
    const { autoDismiss, dismissible, position } = message.meta;

    if (!notificationSystem.current) {
      return;
    }

    notificationSystem.current.addNotification({
      title: message.payload.title,
      message: message.payload.message,
      level: message.meta.level,
      autoDismiss,
      dismissible,
      position,
      uid: messageId,
      onRemove: () => dispatch(toastMessageActions.removeToast(messageId)),
    });
  });
};

const CharacterBuilder: React.FC<Props> | null = ({
  appError,
  toastMessages,
  builderMethod,
  isCharacterLoading,
  isCharacterLoaded,
  isMobile,
  name,
  username,
  redirect,
  characterId,
  diceEnabled,
  decorationInfo,
  canEdit,
  isReadonly,
  userRoles,
  characterStatus,
  userId,
  campaign,
  isVttView,
}) => {
  let mobileMql: MediaQueryList | null = null;
  // useImperativeHandle is hard to type when we us our own context this will just disapear
  const notificationSystem = React.createRef<any>();
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const { pathname } = useLocation();
  const { setTitle } = useHeadContext();
  const [isLoaded, setIsLoaded] = useState(false);
  const prevPathnameRef = React.useRef<string | null>(null);

  React.useEffect(() => {
    if (characterId !== null && !isLoaded) {
      dispatch(builderActions.characterLoadRequest());
      setIsLoaded(true);
    }
    mobileMql = window.matchMedia(BUILDER_MQ_IS_MOBILE_SIZE);
    dispatch(appEnvActions.mobileSet(mobileMql.matches));
    mobileMql.onchange = (e) => {
      dispatch(appEnvActions.mobileSet(e.matches));
    };
  }, [characterId]);

  React.useEffect(() => {
    if (
      isLoaded &&
      characterId !== null &&
      characterStatus === CharacterStatusSlug.PREMADE
    ) {
      const isLorekeeper =
        userRoles?.includes(UserRoles.LOREKEEPER) ||
        userRoles?.includes(UserRoles.ADMIN);
      isLorekeeper && dispatch(characterActions.premadeInfoGet(characterId));
    }
  }, [characterId, characterStatus, isLoaded, userRoles]);

  React.useEffect(() => {
    //check for isCharacterLoaded and canEdit to set isReadonly and redirect, or is Random build to set isReadonly
    if (isCharacterLoaded) {
      dispatch(
        appEnvActions.dataSet({
          isReadonly: !canEdit,
          isUserDM: Number(userId) === campaign?.dmUserId,
        })
      );
      if (!canEdit) {
        navigate(getUrlWithParams(`${BASE_PATHNAME}/${characterId}`));
      }
    }
  }, [
    isCharacterLoaded,
    canEdit,
    characterId,
    navigate,
    dispatch,
    builderMethod,
    userId,
  ]);

  React.useEffect(() => {
    checkForMessages(toastMessages, dispatch, notificationSystem);
  });

  React.useEffect(() => {
    setTitle("Character Builder");
  }, [setTitle]);

  if (appError !== null) {
    return <RenderAuthError appError={appError} />;
  }

  let clsNames: Array<string> = ["character-builder"];
  if (builderMethod) {
    clsNames.push(`character-builder-${FormatUtils.slugify(builderMethod)}`);
  }

  // useEffect above handles redirect but this blocks UI from showing
  if (isReadonly && isCharacterLoaded) {
    return null;
  }

  if (!isCharacterLoading && builderMethod === null) {
    let anonNode: React.ReactNode;
    if (!username) {
      anonNode = (
        <div className="character-builder-anon">
          <div className="character-builder-anon-heading">
            Start Your Adventure
          </div>
          <div className="character-builder-anon-content">
            <p>
              You need to be signed in to a D&amp;D Beyond account in order to
              save characters. You can use your character sheet anywhere, on any
              device.
            </p>
          </div>
          <div className="character-builder-anon-actions">
            <BuilderLinkButton size="oversized" url={redirect}>
              Sign in or Create Account
            </BuilderLinkButton>
          </div>
        </div>
      );
    }

    return (
      <Routes>
        <Route
          path="/"
          element={
            <div className={styles.characterBuilder}>
              <div className="character-builder-inner">
                {anonNode}
                {
                  <BuilderTypeChoicePage
                    className="builder-methods"
                    isEnabled={!!username}
                  />
                }
              </div>
            </div>
          }
        ></Route>
      </Routes>
    );
  }

  clsNames.push(styles.characterBuilder);

  if (diceEnabled) {
    clsNames.push("character-builder--dice-enabled");
  }

  clsNames.push(styles.characterBuilder);

  return (
    <Routes>
      <Route
        path="/*"
        element={
          <div className={clsNames.join(" ")}>
            {(characterId !== null || isCharacterLoading) && (
              <LoadingBlocker isFinished={isCharacterLoaded} />
            )}
            {!isCharacterLoading && (
              <ModalManagerProvider>
                <ClassProvider>
                  <SpeciesProvider>
                    <SynchronousBlocker />
                    <div className="character-builder-inner">
                      {builderMethod === BuilderMethod.STEP_BY_STEP && (
                        <>
                          {!isMobile && (
                            <div className="character-builder-page-header">
                              <h1 className="character-builder-page-header-heading">
                                Character Builder
                              </h1>
                              {name && (
                                <div className="character-builder-page-header-summary">
                                  <CharacterAvatarPortrait
                                    className={
                                      "character-builder-page-header-avatar"
                                    }
                                    avatarUrl={
                                      DecorationUtils.getAvatarInfo(
                                        decorationInfo
                                      ).avatarUrl
                                    }
                                  />
                                  <div className="character-builder-page-header-name">
                                    {name}
                                  </div>
                                </div>
                              )}
                            </div>
                          )}
                          <NavigationSections
                            pathname={pathname}
                            characterId={characterId}
                          />
                          <TodoNavigation
                            pathname={pathname}
                            characterId={characterId}
                          />
                          <PortraitName
                            useDefaultCharacterName={true}
                            className={styles.portraitName}
                          />
                          <hr className={pageStyles.divider} />
                        </>
                      )}
                      <Routes>
                        {(Object.values(ROUTE_DEFINITIONS) as RouteDef[]).map(
                          ({ key, path, getComponent }) => (
                            <Route
                              key={key}
                              path={path
                                .replace(`${BASE_PATHNAME}/builder/`, "")
                                .replace(
                                  `${BASE_PATHNAME}/:characterId/builder/`,
                                  ""
                                )}
                              element={getComponent()}
                            />
                          )
                        )}
                        <Route
                          path="/"
                          element={<BuilderTypeChoicePage />}
                        ></Route>
                      </Routes>
                    </div>
                  </SpeciesProvider>
                </ClassProvider>
              </ModalManagerProvider>
            )}
            <NotificationSystem ref={notificationSystem} />
            {diceEnabled && !isReadonly ? (
              <DiceContainer
                canShow={isCharacterLoaded}
                isVttView={isVttView}
              />
            ) : null}
          </div>
        }
      ></Route>
    </Routes>
  );
};

const CharacterBuilderWrapper = (props) => {
  const [searchParams] = useSearchParams();
  const isVttView = searchParams.get("view") === "vtt";
  return <CharacterBuilder {...props} isVttView={isVttView} />;
};

function mapStateToProps(state: BuilderAppState) {
  return {
    toastMessages: toastMessageSelectors.getToastMessages(state),
    builderMethod: builderSelectors.getBuilderMethod(state),
    isCharacterLoading: builderSelectors.getIsCharacterLoading(state),
    isCharacterLoaded: builderSelectors.getIsCharacterLoaded(state),
    isMobile: appEnvSelectors.getIsMobile(state),
    isReadonly: appEnvSelectors.getIsReadonly(state),
    name: rulesEngineSelectors.getName(state),
    decorationInfo: rulesEngineSelectors.getDecorationInfo(state),
    canEdit: rulesEngineSelectors.getCanEdit(state),
    username: appEnvSelectors.getUsername(state),
    redirect: appEnvSelectors.getRedirect(state),
    appError: appInfoSelectors.getError(state),
    characterId: appEnvSelectors.getCharacterId(state),
    diceEnabled: appEnvSelectors.getDiceEnabled(state),
    userRoles: appEnvSelectors.getUserRoles(state),
    characterStatus: characterSelectors.getStatusSlug(state),
    userId: appEnvSelectors.getUserId(state),
    campaign: characterSelectors.getCampaign(state),
  };
}

export default connect(mapStateToProps)(CharacterBuilderWrapper);
