/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import React, { useState, useEffect, useRef } from "react";
import { Localized } from "./MSLocalized";
import { AboutWelcomeUtils } from "../lib/aboutwelcome-utils.mjs";
import { MultiStageProtonScreen } from "./MultiStageProtonScreen";
import { useLanguageSwitcher } from "./LanguageSwitcher";
import { SubmenuButton } from "./SubmenuButton";
import { BASE_PARAMS, addUtmParams } from "../lib/addUtmParams.mjs";

// Amount of milliseconds for all transitions to complete (including delays).
const TRANSITION_OUT_TIME = 1000;
const LANGUAGE_MISMATCH_SCREEN_ID = "AW_LANGUAGE_MISMATCH";

export const MultiStageAboutWelcome = props => {
  let { defaultScreens } = props;
  const didFilter = useRef(false);
  const [didMount, setDidMount] = useState(false);
  const [screens, setScreens] = useState(defaultScreens);

  const [index, setScreenIndex] = useState(props.startScreen);
  const [previousOrder, setPreviousOrder] = useState(props.startScreen - 1);

  useEffect(() => {
    (async () => {
      // If we want to load index from history state, we don't want to send impression yet
      if (!didMount) {
        return;
      }
      // On about:welcome first load, screensVisited should be empty
      let screensVisited = didFilter.current ? screens.slice(0, index) : [];
      let upcomingScreens = defaultScreens
        .filter(s => !screensVisited.find(v => v.id === s.id))
        // Filter out Language Mismatch screen from upcoming
        // screens if screens set from useLanguageSwitcher hook
        // has filtered language screen
        .filter(
          upcomingScreen =>
            !(
              !screens.find(s => s.id === LANGUAGE_MISMATCH_SCREEN_ID) &&
              upcomingScreen.id === LANGUAGE_MISMATCH_SCREEN_ID
            )
        );

      let filteredScreens = screensVisited.concat(
        (await window.AWEvaluateScreenTargeting(upcomingScreens)) ??
          upcomingScreens
      );

      // Use existing screen for the filtered screen to carry over any modification
      // e.g. if AW_LANGUAGE_MISMATCH exists, use it from existing screens
      setScreens(
        filteredScreens.map(
          filtered => screens.find(s => s.id === filtered.id) ?? filtered
        )
      );

      didFilter.current = true;

      const screenInitials = filteredScreens
        .map(({ id }) => id?.split("_")[1]?.[0])
        .join("");
      // Send impression ping when respective screen first renders
      filteredScreens.forEach((screen, order) => {
        if (index === order) {
          const messageId = `${props.message_id}_${order}_${screen.id}_${screenInitials}`;

          AboutWelcomeUtils.sendImpressionTelemetry(messageId, {
            screen_family: props.message_id,
            screen_index: order,
            screen_id: screen.id,
            screen_initials: screenInitials,
          });

          window.AWAddScreenImpression?.(screen);
        }
      });

      // Remember that a new screen has loaded for browser navigation
      if (props.updateHistory && index > window.history.state) {
        window.history.pushState(index, "");
      }

      // Remember the previous screen index so we can animate the transition
      setPreviousOrder(index);
    })();
  }, [index, didMount]); // eslint-disable-line react-hooks/exhaustive-deps

  const [flowParams, setFlowParams] = useState(null);
  const { metricsFlowUri } = props;
  useEffect(() => {
    (async () => {
      if (metricsFlowUri) {
        setFlowParams(await AboutWelcomeUtils.fetchFlowParams(metricsFlowUri));
      }
    })();
  }, [metricsFlowUri]);

  // Allow "in" style to render to actually transition towards regular state,
  // which also makes using browser back/forward navigation skip transitions.
  const [transition, setTransition] = useState(props.transitions ? "in" : "");
  useEffect(() => {
    if (transition === "in") {
      requestAnimationFrame(() =>
        requestAnimationFrame(() => setTransition(""))
      );
    }
  }, [transition]);

  // Transition to next screen, opening about:home on last screen button CTA
  const handleTransition = () => {
    // Only handle transitioning out from a screen once.
    if (transition === "out") {
      return;
    }

    // Start transitioning things "out" immediately when moving forwards.
    setTransition(props.transitions ? "out" : "");

    // Actually move forwards after all transitions finish.
    setTimeout(
      () => {
        if (index < screens.length - 1) {
          setTransition(props.transitions ? "in" : "");
          setScreenIndex(prevState => prevState + 1);
        } else {
          window.AWFinish();
        }
      },
      props.transitions ? TRANSITION_OUT_TIME : 0
    );
  };

  useEffect(() => {
    // When about:welcome loads (on refresh or pressing back button
    // from about:home), ensure history state usEffect runs before
    // useEffect hook that send impression telemetry
    setDidMount(true);

    if (props.updateHistory) {
      // Switch to the screen tracked in state (null for initial state)
      // or last screen index if a user navigates by pressing back
      // button from about:home
      const handler = ({ state }) => {
        if (transition === "out") {
          return;
        }
        setTransition(props.transitions ? "out" : "");
        setTimeout(
          () => {
            setTransition(props.transitions ? "in" : "");
            setScreenIndex(Math.min(state, screens.length - 1));
          },
          props.transitions ? TRANSITION_OUT_TIME : 0
        );
      };

      // Handle page load, e.g., going back to about:welcome from about:home
      const { state } = window.history;
      if (state) {
        setScreenIndex(Math.min(state, screens.length - 1));
        setPreviousOrder(Math.min(state, screens.length - 1));
      }

      // Watch for browser back/forward button navigation events
      window.addEventListener("popstate", handler);
      return () => window.removeEventListener("popstate", handler);
    }
    return false;
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const [multiSelects, setMultiSelects] = useState({});

  // Save the active multi select state for each screen as an object keyed by
  // screen id. Each screen id has an array containing checkbox ids used in
  // handleAction to update MULTI_ACTION data. This allows us to remember the
  // state of each screen's multi select checkboxes when navigating back and
  // forth between screens, while also allowing a message to have more than one
  // multi select screen.
  const [activeMultiSelects, setActiveMultiSelects] = useState({});

  // Get the active theme so the rendering code can make it selected
  // by default.
  const [activeTheme, setActiveTheme] = useState(null);
  const [initialTheme, setInitialTheme] = useState(null);
  useEffect(() => {
    (async () => {
      let theme = await window.AWGetSelectedTheme();
      setInitialTheme(theme);
      setActiveTheme(theme);
    })();
  }, []);

  const { negotiatedLanguage, langPackInstallPhase, languageFilteredScreens } =
    useLanguageSwitcher(
      props.appAndSystemLocaleInfo,
      screens,
      index,
      setScreenIndex
    );

  useEffect(() => {
    setScreens(languageFilteredScreens);
  }, [languageFilteredScreens]);

  return (
    <React.Fragment>
      <div
        className={`outer-wrapper onboardingContainer proton transition-${transition}`}
        style={props.backdrop ? { background: props.backdrop } : {}}
      >
        {screens.map((screen, order) => {
          const isFirstScreen = screen === screens[0];
          const isLastScreen = screen === screens[screens.length - 1];
          const totalNumberOfScreens = screens.length;
          const isSingleScreen = totalNumberOfScreens === 1;

          const setActiveMultiSelect = valueOrFn =>
            setActiveMultiSelects(prevState => ({
              ...prevState,
              [screen.id]:
                typeof valueOrFn === "function"
                  ? valueOrFn(prevState[screen.id])
                  : valueOrFn,
            }));
          const setScreenMultiSelects = valueOrFn =>
            setMultiSelects(prevState => ({
              ...prevState,
              [screen.id]:
                typeof valueOrFn === "function"
                  ? valueOrFn(prevState[screen.id])
                  : valueOrFn,
            }));

          return index === order ? (
            <WelcomeScreen
              key={screen.id + order}
              id={screen.id}
              totalNumberOfScreens={totalNumberOfScreens}
              isFirstScreen={isFirstScreen}
              isLastScreen={isLastScreen}
              isSingleScreen={isSingleScreen}
              order={order}
              previousOrder={previousOrder}
              content={screen.content}
              navigate={handleTransition}
              messageId={`${props.message_id}_${order}_${screen.id}`}
              UTMTerm={props.utm_term}
              flowParams={flowParams}
              activeTheme={activeTheme}
              initialTheme={initialTheme}
              setActiveTheme={setActiveTheme}
              setInitialTheme={setInitialTheme}
              screenMultiSelects={multiSelects[screen.id]}
              setScreenMultiSelects={setScreenMultiSelects}
              activeMultiSelect={activeMultiSelects[screen.id]}
              setActiveMultiSelect={setActiveMultiSelect}
              autoAdvance={screen.auto_advance}
              negotiatedLanguage={negotiatedLanguage}
              langPackInstallPhase={langPackInstallPhase}
              forceHideStepsIndicator={screen.force_hide_steps_indicator}
              ariaRole={props.ariaRole}
              aboveButtonStepsIndicator={screen.above_button_steps_indicator}
            />
          ) : null;
        })}
      </div>
    </React.Fragment>
  );
};

export const SecondaryCTA = props => {
  const targetElement = props.position
    ? `secondary_button_${props.position}`
    : `secondary_button`;
  let buttonStyling = props.content.secondary_button?.has_arrow_icon
    ? `secondary arrow-icon`
    : `secondary`;
  const isPrimary = props.content.secondary_button?.style === "primary";
  const isTextLink =
    !["split", "callout"].includes(props.content.position) &&
    props.content.tiles?.type !== "addons-picker" &&
    !isPrimary;
  const isSplitButton =
    props.content.submenu_button?.attached_to === targetElement;
  let className = "secondary-cta";
  if (props.position) {
    className += ` ${props.position}`;
  }
  if (isSplitButton) {
    className += " split-button-container";
  }
  const isDisabled = React.useCallback(
    disabledValue =>
      disabledValue === "hasActiveMultiSelect"
        ? !(props.activeMultiSelect?.length > 0)
        : disabledValue,
    [props.activeMultiSelect?.length]
  );

  if (isTextLink) {
    buttonStyling += " text-link";
  }

  if (isPrimary) {
    buttonStyling = props.content.secondary_button?.has_arrow_icon
      ? `primary arrow-icon`
      : `primary`;
  }

  return (
    <div className={className}>
      <Localized text={props.content[targetElement].text}>
        <span />
      </Localized>
      <Localized text={props.content[targetElement].label}>
        <button
          className={buttonStyling}
          value={targetElement}
          disabled={isDisabled(props.content.secondary_button?.disabled)}
          onClick={props.handleAction}
        />
      </Localized>
      {isSplitButton ? (
        <SubmenuButton
          content={props.content}
          handleAction={props.handleAction}
        />
      ) : null}
    </div>
  );
};

export const StepsIndicator = props => {
  let steps = [];
  for (let i = 0; i < props.totalNumberOfScreens; i++) {
    let className = `${i === props.order ? "current" : ""} ${
      i < props.order ? "complete" : ""
    }`;
    steps.push(
      <div key={i} className={`indicator ${className}`} role="presentation" />
    );
  }
  return steps;
};

export const ProgressBar = ({ step, previousStep, totalNumberOfScreens }) => {
  const [progress, setProgress] = React.useState(
    previousStep / totalNumberOfScreens
  );
  useEffect(() => {
    // We don't need to hook any dependencies because any time the step changes,
    // the screen's entire DOM tree will be re-rendered.
    setProgress(step / totalNumberOfScreens);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
  return (
    <div
      className="indicator"
      role="presentation"
      style={{ "--progress-bar-progress": `${progress * 100}%` }}
    />
  );
};

export class WelcomeScreen extends React.PureComponent {
  constructor(props) {
    super(props);
    this.handleAction = this.handleAction.bind(this);
  }

  handleOpenURL(action, flowParams, UTMTerm) {
    let { type, data } = action;
    if (type === "SHOW_FIREFOX_ACCOUNTS") {
      let params = {
        ...BASE_PARAMS,
        utm_term: `${UTMTerm}-screen`,
      };
      if (action.addFlowParams && flowParams) {
        params = {
          ...params,
          ...flowParams,
        };
      }
      data = { ...data, extraParams: params };
    } else if (type === "OPEN_URL") {
      let url = new URL(data.args);
      addUtmParams(url, `${UTMTerm}-screen`);
      if (action.addFlowParams && flowParams) {
        url.searchParams.append("device_id", flowParams.deviceId);
        url.searchParams.append("flow_id", flowParams.flowId);
        url.searchParams.append("flow_begin_time", flowParams.flowBeginTime);
      }
      data = { ...data, args: url.toString() };
    }
    return AboutWelcomeUtils.handleUserAction({ type, data });
  }

  async handleAction(event) {
    let { props } = this;
    const value =
      event.currentTarget.value ?? event.currentTarget.getAttribute("value");
    const source = event.source || value;
    let targetContent =
      props.content[value] ||
      props.content.tiles ||
      props.content.languageSwitcher;

    if (value === "submenu_button" && event.action) {
      targetContent = { action: event.action };
    }

    if (!(targetContent && targetContent.action)) {
      return;
    }
    // Send telemetry before waiting on actions
    AboutWelcomeUtils.sendActionTelemetry(props.messageId, source, event.name);

    // Send additional telemetry if a messaging surface like feature callout is
    // dismissed via the dismiss button. Other causes of dismissal will be
    // handled separately by the messaging surface's own code.
    if (value === "dismiss_button" && !event.name) {
      AboutWelcomeUtils.sendDismissTelemetry(props.messageId, source);
    }

    let { action } = targetContent;
    action = JSON.parse(JSON.stringify(action));

    if (action.collectSelect) {
      this.setMultiSelectActions(action);
    }

    let actionResult;
    if (["OPEN_URL", "SHOW_FIREFOX_ACCOUNTS"].includes(action.type)) {
      actionResult = await this.handleOpenURL(
        action,
        props.flowParams,
        props.UTMTerm
      );
    } else if (action.type) {
      actionResult = await AboutWelcomeUtils.handleUserAction(action);
      if (action.type === "FXA_SIGNIN_FLOW") {
        AboutWelcomeUtils.sendActionTelemetry(
          props.messageId,
          actionResult ? "sign_in" : "sign_in_cancel",
          "FXA_SIGNIN_FLOW"
        );
      }
      // Wait until migration closes to complete the action
      const hasMigrate = a =>
        a.type === "SHOW_MIGRATION_WIZARD" ||
        (a.type === "MULTI_ACTION" && a.data?.actions?.some(hasMigrate));
      if (hasMigrate(action)) {
        await window.AWWaitForMigrationClose();
        AboutWelcomeUtils.sendActionTelemetry(props.messageId, "migrate_close");
      }
    }

    // A special tiles.action.theme value indicates we should use the event's value vs provided value.
    if (action.theme) {
      let themeToUse =
        action.theme === "<event>"
          ? event.currentTarget.value
          : this.props.initialTheme || action.theme;
      this.props.setActiveTheme(themeToUse);
      if (props.content.tiles?.category?.type === "wallpaper") {
        const theme = themeToUse.split("-")?.[1];
        let actionWallpaper = { ...props.content.tiles.category.action };
        actionWallpaper.data.actions.forEach(async wpAction => {
          if (wpAction.data.pref.name?.includes("dark")) {
            wpAction.data.pref.value = `dark-${theme}`;
          } else {
            wpAction.data.pref.value = `light-${theme}`;
          }
          await AboutWelcomeUtils.handleUserAction(actionWallpaper);
        });
      } else {
        window.AWSelectTheme(themeToUse);
      }
    }

    // If the action has persistActiveTheme: true, we set the initial theme to the currently active theme
    // so that it can be reverted to in the event that the user navigates away from the screen
    if (action.persistActiveTheme) {
      this.props.setInitialTheme(this.props.activeTheme);
    }

    // `navigate` and `dismiss` can be true/false/undefined, or they can be a
    // string "actionResult" in which case we should use the actionResult
    // (boolean resolved by handleUserAction)
    const shouldDoBehavior = behavior =>
      behavior === "actionResult" ? actionResult : behavior;

    if (shouldDoBehavior(action.navigate)) {
      props.navigate();
    }

    if (shouldDoBehavior(action.dismiss)) {
      window.AWFinish();
    }
  }

  setMultiSelectActions(action) {
    let { props } = this;
    // Populate MULTI_ACTION data actions property with selected checkbox
    // actions from tiles data
    if (action.type !== "MULTI_ACTION") {
      console.error(
        "collectSelect is only supported for MULTI_ACTION type actions"
      );
      action.type = "MULTI_ACTION";
    }
    if (!Array.isArray(action.data?.actions)) {
      console.error(
        "collectSelect is only supported for MULTI_ACTION type actions with an array of actions"
      );
      action.data = { actions: [] };
    }

    // Prepend the multi-select actions to the CTA's actions array, but keep
    // the actions in the same order they appear in. This way the CTA action
    // can go last, after the multi-select actions are processed. For example,
    // 1. checkbox action 1
    // 2. checkbox action 2
    // 3. radio action
    // 4. CTA action (which perhaps depends on the radio action)
    let multiSelectActions = [];
    for (const checkbox of props.content?.tiles?.data ?? []) {
      let checkboxAction;
      if (props.activeMultiSelect?.includes(checkbox.id)) {
        checkboxAction = checkbox.checkedAction ?? checkbox.action;
      } else {
        checkboxAction = checkbox.uncheckedAction;
      }

      if (checkboxAction) {
        multiSelectActions.push(checkboxAction);
      }
    }
    action.data.actions.unshift(...multiSelectActions);

    // Send telemetry with selected checkbox ids
    AboutWelcomeUtils.sendActionTelemetry(
      props.messageId,
      props.activeMultiSelect,
      "SELECT_CHECKBOX"
    );
  }

  render() {
    return (
      <MultiStageProtonScreen
        content={this.props.content}
        id={this.props.id}
        order={this.props.order}
        previousOrder={this.props.previousOrder}
        activeTheme={this.props.activeTheme}
        screenMultiSelects={this.props.screenMultiSelects}
        setScreenMultiSelects={this.props.setScreenMultiSelects}
        activeMultiSelect={this.props.activeMultiSelect}
        setActiveMultiSelect={this.props.setActiveMultiSelect}
        totalNumberOfScreens={this.props.totalNumberOfScreens}
        appAndSystemLocaleInfo={this.props.appAndSystemLocaleInfo}
        negotiatedLanguage={this.props.negotiatedLanguage}
        langPackInstallPhase={this.props.langPackInstallPhase}
        handleAction={this.handleAction}
        messageId={this.props.messageId}
        isFirstScreen={this.props.isFirstScreen}
        isLastScreen={this.props.isLastScreen}
        isSingleScreen={this.props.isSingleScreen}
        startsWithCorner={this.props.startsWithCorner}
        autoAdvance={this.props.autoAdvance}
        forceHideStepsIndicator={this.props.forceHideStepsIndicator}
        ariaRole={this.props.ariaRole}
        aboveButtonStepsIndicator={this.props.aboveButtonStepsIndicator}
      />
    );
  }
}
