/**
 * @mergeTarget
 * @module Src/Versions
 */

import {
  LisioBooleanParameterNames,
  LisioCategory,
  LisioCategoryFactory,
  LisioCategoryNames,
  LisioNumericParameterNames,
  LisioParameterNames,
  LisioProfileNames,
  LisioStringParameterNames,
} from "@lisio/lisio-profils";
import { LisioScreen } from "../render/screens/lisio-screen";
import { UserController } from "../controllers/user-controller";
import { AccessibilityScreen } from "../render/screens/accessibility/accessibility-screen";
import { DaltonismScreen } from "../render/screens/daltonism/daltonism-screen";
import { ComfortScreen } from "../render/screens/comfort/comfort-screen";
import { DyslexiaScreen } from "../render/screens/dyslexia/dyslexia-screen";
import { TranslationScreen } from "../render/screens/translation/translation-screen";
import { SynthScreen } from "../render/screens/synth/synth-screen";
import { EcoScreen } from "../render/screens/eco/eco-screen";
import { SettingsScreen } from "../render/screens/settings/settings-screen";
import { ResetScreen } from "../render/screens/reset/reset-screen";
import { UsersScreen } from "../render/screens/users/users-screen";
import { MoreScreen } from "../render/screens/more/more-screen";
import { ManagementScreen } from "../render/screens/management/management-screen";
import { RuralScreen } from "../render/screens/rural/rural-screen";
import {
  ScreenNames,
  VisualCategoryNames,
} from "../render/screens/screen-types";
import { isTouchDevice } from "../../misc/functions";
import { InputManager } from "../managers/input-manager";

/**
 * Type describes a group of component in a screen, it can contains categories, profiles or parameters
 */
type Group = {
  categories: (LisioCategory | VisualCategoryNames)[];
  profiles: LisioProfileNames[];
  parameters: LisioParameterNames[];
  position: number;
};

/**
 * Enumerator of version name
 * @source
 */
enum LisioWidgetVersionNames {
  COMPLETE = "complete",
  ESSENTIAL = "essential",
  FIRST = "first",
  WELCOME = "welcome",
}

/**
 * Map to make corresponding version number and enumerator {@link Src/Versions.LisioWidgetVersionNames | LisioWidgetVersionNames}
 * @source
 */
const numberToVersion: Map<number, LisioWidgetVersionNames> = new Map<
  number,
  LisioWidgetVersionNames
>([
  [1, LisioWidgetVersionNames.ESSENTIAL],
  [2, LisioWidgetVersionNames.COMPLETE],
  [3, LisioWidgetVersionNames.FIRST],
  [4, LisioWidgetVersionNames.WELCOME],
]);

/**
 * ## How to create a new version ? Part 1
 *
 * To create a new version, choose a revelant name and add it to {@link LisioWidgetVersionNames | LisioWidgetVersionNames}.\
 * If needed, create a corresponding version number in {@link numberToVersion | numberToVersion}.\
 * Next create a new class extending WidgetVersion.\
 * Implements {@link WidgetVersion.initialize | WidgetVersion.initialize} in your new class to design your version.\
 * You just need to call differents add screen methods. For more details you can referer to existing version.\
 * You can specify screens you don't want to include in nav in constructor of version.\
 * For the last step see {@link Src/Versions.LisioWidgetVersionFactory | LisioWidgetVersionFactory}
 *
 * ## How to modify a version ?
 *
 * Modify {@link WidgetVersion.initialize | WidgetVersion.initialize} in your class to modify your version.\
 *
 * ## How to delete a version ? Part 1
 *
 * To delete a version, delete name it to {@link LisioWidgetVersionNames | LisioWidgetVersionNames}.\
 * If needed, delete the corresponding version number in {@link numberToVersion | numberToVersion}.\
 * Delete widget class.\
 * For the last step see {@link Src/Versions.LisioWidgetVersionFactory | LisioWidgetVersionFactory}
 *
 * ## Documentation
 *
 * Abstract class representing a widget version.\
 * It aims to centralize the logic of a widget version.\
 * A widget version allows to define which screen and features has to be included.\
 * To execute his responsability this class provides :
 *  * Initialization method
 *  * Methods to create each categories and they corresponding screen
 *  * Method to check if group is empty or not
 *  * Method to get group position
 */
abstract class WidgetVersion {
  /**
   * Private attribute to store main screen
   * @source
   */
  protected _mainScreen?: LisioScreen;

  /**
   * Private attribute to store all screens
   * @source
   */
  protected _screens: Map<string, LisioScreen> = new Map<string, LisioScreen>();

  /**
   * Private attribute to list all screens ingnored in nav
   * @source
   */
  protected _screensToIgnoreInNav: string[] = [];

  /**
   * Getter for attribute {@link _mainScreen | _mainScreen}
   * @returns Returns _mainScreen attribute
   * @source
   */
  public get mainScreen(): LisioScreen | undefined {
    return this._mainScreen;
  }

  /**
   * Getter for attribute {@link _screens | _screens}
   * @returns Returns _screens attribute
   * @source
   */
  public get screens(): Map<string, LisioScreen> {
    return this._screens;
  }

  /**
   * Getter for attribute {@link _screensToIgnoreInNav | _screensToIgnoreInNav}
   * @returns Returns _screensToIgnoreInNav attribute
   * @source
   */
  public get screensToIgnoreInNav(): string[] {
    return this._screensToIgnoreInNav;
  }

  /**
   * @async
   * Public abstract method to initialize version
   * @param {object} screensToHide - Object to indicates screens to hide
   * @property {LisioCategoryNames[]} screensToHide.categoriesToHide - Categories to hde
   * @property {LisioProfileNames[]} screensToHide.profilesToHide - Profiles to hide
   * @property {LisioParameterNames[]} screensToHide.parametersToHide - Parameters to hide
   * @param {LisioCategoryNames.GOOGLE_TRANSLATION | LisioCategoryNames.DEEPL_TRANSLATION | undefined} translationMode - Translation mode if version has translation
   * @param {object | undefined} groupsOrder - Groups order
   * @returns Returns a promise containing nothing
   * @source
   */
  public abstract initialize(
    screensToHide: {
      categoriesToHide: LisioCategoryNames[];
      profilesToHide: LisioProfileNames[];
      parametersToHide: LisioParameterNames[];
    },
    translationMode:
      | LisioCategoryNames.GOOGLE_TRANSLATION
      | LisioCategoryNames.DEEPL_TRANSLATION
      | undefined,
    priorizedLanguages: string[],
    translationLangISOsAuthorized: string[],
    isCompensation: boolean,
    isFlag: boolean,
    groupsOrder?: { groupId: number; position: number }[],
  ): Promise<void>;

  protected hideAllOfCategory(categoryNameToHide: LisioCategoryNames) {
    const categoryToHide = LisioCategoryFactory.current.buildLisioCategory(
      categoryNameToHide,
      [],
      [],
      [],
    );
    for (const parameterName of categoryToHide.parameterNames) {
      InputManager.current.addHiddenProfileOrParameter(parameterName);
    }
    for (const profileName of categoryToHide.profileNames) {
      InputManager.current.addHiddenProfileOrParameter(profileName);
    }
    for (const subcategoryName of categoryToHide.subCategoriesNames) {
      this.hideAllOfCategory(subcategoryName);
    }
  }

  /**
   * Protected method to add accessibility screen
   * @param {Group} group - Group of all features included in accessibility
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addAccessibility(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    profilesToHide.push(
      LisioProfileNames.GLASS_X2,
      LisioProfileNames.GLASS_X4,
      LisioProfileNames.GESTURES,
      LisioProfileNames.DARK_MODE,
    );
    if (isTouchDevice()) {
      profilesToHide.push(LisioProfileNames.FOCUS);
      profilesToHide.push(LisioProfileNames.MOVEMENTS);
      parametersToHide.push(LisioNumericParameterNames.BIGGER_CLICK);
      parametersToHide.push(LisioBooleanParameterNames.SHOW_ALT);
      parametersToHide.push(LisioBooleanParameterNames.LIST);
      parametersToHide.push(LisioNumericParameterNames.ZOOM);
    }
    const accessibilityCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.ACCESSIBILITY,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(accessibilityCategory);
    const isFirefox =
      typeof CSS !== "undefined" && CSS.supports("-moz-appearance", "none");

    const accessibilityScreen: LisioScreen = new AccessibilityScreen(
      [
        ...accessibilityCategory.subCategoriesNames
          .map((subCategoryName: LisioCategoryNames) =>
            LisioCategoryFactory.current.buildLisioCategory(
              subCategoryName,
              subCategoriesToHide,
              profilesToHide,
              parametersToHide,
            ),
          )
          .filter(() => !isFirefox),
      ],
      accessibilityCategory.profileNames,
      profilesToHide,
      isCompensation,
      UserController.current.currentUser,
    );
    this._screens.set(
      ScreenNames[AccessibilityScreen.name],
      accessibilityScreen,
    );
    if (!isFirefox) {
      const daltonismScreen: LisioScreen = new DaltonismScreen(
        UserController.current.currentUser,
      );
      this._screens.set(ScreenNames[DaltonismScreen.name], daltonismScreen);
    }
  }
  /**
   * Protected method to add comfort screen
   * @param {Group} group - Group of all features included in comfort
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addComfort(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    const comfortCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.COMFORT,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(comfortCategory);
    const comfortScreen: LisioScreen = new ComfortScreen(
      [
        ...comfortCategory.subCategoriesNames.map(
          (subCategoryName: LisioCategoryNames) =>
            LisioCategoryFactory.current.buildLisioCategory(
              subCategoryName,
              subCategoriesToHide,
              profilesToHide,
              parametersToHide,
            ),
        ),
      ],
      comfortCategory.profileNames,
      profilesToHide,
      isCompensation,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[ComfortScreen.name], comfortScreen);
    const dyslexiaCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.DYSLEXIA_COMFORT,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    const dyslexiaScreen: LisioScreen = new DyslexiaScreen(
      dyslexiaCategory.profileNames,
      [],
      dyslexiaCategory.parameterNames,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[DyslexiaScreen.name], dyslexiaScreen);
  }
  /**
   * Protected method to add low network screen
   * @param {Group} group - Group of all features included in low network
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addLowNetwork(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    const ruralCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.RURAL,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(ruralCategory);
    const ruralScreen: LisioScreen = new RuralScreen(
      isCompensation,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[RuralScreen.name], ruralScreen);
  }
  /**
   * Protected method to add translation screen
   * @param {Group} group - Group of all features included in translation
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @param {LisioCategoryNames} translationMode - Translation mode
   * @param {string[]} priorizedLanguages - Languages to priorize
   * @param {string[]} authorizedLanguagesISOs - Authorized languages
   * @returns Returns nothing
   * @source
   */
  protected addTranslation(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    translationMode: LisioCategoryNames,
    priorizedLanguages: string[],
    authorizedLanguagesISOs: string[],
    isCompensation?: boolean,
  ): void {
    const translationCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        translationMode,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(translationCategory);
    const translationScreen: LisioScreen = new TranslationScreen(
      translationCategory,
      priorizedLanguages,
      authorizedLanguagesISOs,
      isCompensation,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[TranslationScreen.name], translationScreen);
  }
  /**
   * Protected method to add speech synthesis screen
   * @param {Group} group - Group of all features included in speech synthesis
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addSpeechSynthesis(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    const speechCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.SPEECH_SYNTHESIS,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(speechCategory);
    const synthScreen: LisioScreen = new SynthScreen(
      isCompensation,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[SynthScreen.name], synthScreen);
  }
  /**
   * Protected method to add eco screen
   * @param {Group} group - Group of all features included in eco
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addEco(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    const ecologicalCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.ECOLOGICAL,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(ecologicalCategory);
    const ecoScreen: LisioScreen = new EcoScreen(
      ecologicalCategory,
      isCompensation || false,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[EcoScreen.name], ecoScreen);
  }
  /**
   * Protected method to add light eco screen
   * @param {Group} group - Group of all features included in light eco
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addLightEco(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    const lightEcologicalCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.LIGHT_ECOLOGICAL,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(lightEcologicalCategory);
    const ecoScreen: LisioScreen = new EcoScreen(
      lightEcologicalCategory,
      isCompensation || false,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[EcoScreen.name], ecoScreen);
  }
  /**
   * Protected method to add more screen
   * @returns Returns nothing
   * @source
   */
  protected addMore(): void {
    const moreScreen = new MoreScreen();
    this._screens.set(ScreenNames[MoreScreen.name], moreScreen);
  }
  /**
   * Protected method to add settings screen
   * @param {Group} group - Group of all features included in settings
   * @param {LisioCategoryNames[]} subCategoriesToHide - Sub-categories to hide
   * @param {LisioProfileNames[]} profilesToHide - Profiles to hide
   * @param {LisioParameterNames[]} parametersToHide - Parameters to hide
   * @returns Returns nothing
   * @source
   */
  protected addSettings(
    group: Group,
    subCategoriesToHide: LisioCategoryNames[],
    profilesToHide: LisioProfileNames[],
    parametersToHide: LisioParameterNames[],
    isCompensation?: boolean,
  ): void {
    const settingsCategory: LisioCategory =
      LisioCategoryFactory.current.buildLisioCategory(
        LisioCategoryNames.SETTINGS,
        subCategoriesToHide,
        profilesToHide,
        parametersToHide,
      );
    group.categories.push(settingsCategory);

    const isFirefox =
      typeof CSS !== "undefined" && CSS.supports("-moz-appearance", "none");

    const parametersToRender = [
      ...settingsCategory.parameterNames.filter(
        (parameterName) =>
          parameterName !== LisioNumericParameterNames.CONTRAST ||
          (!isFirefox && parameterName === LisioNumericParameterNames.CONTRAST),
      ),
    ];

    const slidersSection: LisioParameterNames[] = [];

    const switchsSection: LisioParameterNames[] = [];

    const radiosSection: LisioParameterNames[] = [];

    for (const parameterName of parametersToRender) {
      const upperName: string = parameterName.toUpperCase();
      if (upperName in LisioBooleanParameterNames) {
        switchsSection.push(parameterName);
      } else if (upperName in LisioNumericParameterNames) {
        slidersSection.push(parameterName);
      } else if (upperName in LisioStringParameterNames) {
        radiosSection.push(parameterName);
      }
    }

    const contentAndDisplay: {
      slidersSection: LisioParameterNames[];
      switchsSection: LisioParameterNames[];
      radiosSection: LisioParameterNames[];
      title: "content-settings-screen-title" | "display-settings-screen-title";
    } = {
      slidersSection: slidersSection.filter((parameter)=>
        parameter !== LisioNumericParameterNames.DALTONISM_R && parameter !== LisioNumericParameterNames.DALTONISM_G && parameter !== LisioNumericParameterNames.DALTONISM_B && parameter !== LisioNumericParameterNames.DALTONISM_HUE && parameter !== LisioNumericParameterNames.DALTONISM_SATURATION && parameter !== LisioNumericParameterNames.DALTONISM_BRIGHTNESS
      ),
      switchsSection: switchsSection.filter((parameter) =>
        isTouchDevice()
          ? parameter !== LisioBooleanParameterNames.READING_MASK &&
            parameter !== LisioBooleanParameterNames.KEYBOARD_NAVIGATION
          : parameter,
      ),
      radiosSection: radiosSection.filter((parameter) =>
        isTouchDevice()
          ? parameter !== LisioStringParameterNames.CURSOR_SIZE
          : parameter,
      ),
      title: "content-settings-screen-title",
    };
    const settingsScreen: LisioScreen = new SettingsScreen(
      contentAndDisplay,
      parametersToHide,
      isCompensation,
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[SettingsScreen.name], settingsScreen);
  }
  /**
   * Protected method to add management screen
   * @returns Returns nothing
   * @source
   */
  protected addManagement(): void {
    const usersGroup: Group = {
      categories: [VisualCategoryNames.USERS],
      profiles: [],
      parameters: [],
      position: 0,
    };

    const isActiveGroup: Group = {
      categories: [],
      profiles: [],
      parameters: [LisioBooleanParameterNames.IS_ACTIVE],
      position: 1,
    };

    const resetGroup: Group = {
      categories: [VisualCategoryNames.RESET],
      profiles: [],
      parameters: [],
      position: 2,
    };
    const managementScreen: LisioScreen = new ManagementScreen(
      [usersGroup, resetGroup, isActiveGroup],
      UserController.current.currentUser,
    );
    this._screens.set(ScreenNames[ManagementScreen.name], managementScreen);
    const usersScreen: LisioScreen = new UsersScreen();
    this._screens.set(ScreenNames[UsersScreen.name], usersScreen);

    const resetScreen: LisioScreen = new ResetScreen();
    this._screens.set(ScreenNames[ResetScreen.name], resetScreen);
  }

  /**
   * Protected method to add group in screen if not empty
   * @param {Group} group - Group of all features included in screen
   * @param {Group[]} parentGroup - Parent group
   * @returns Returns nothing
   * @source
   */
  protected addGroupIfNotEmpty(group: Group, parentGroup: Group[]): void {
    if (
      group.categories.length != 0 ||
      group.profiles.length != 0 ||
      group.parameters.length != 0
    ) {
      parentGroup.push(group);
    }
  }

  /**
   * Protected method to determines group order
   * @param {number} groupId - Group id
   * @param {object[]} groupsOrder - Group order
   * @property {number} groupsOrder.groupId - Group order
   * @property {number} groupsOrder.position - Group order
   * @returns Returns group position
   * @source
   */
  protected getGroupPosition(
    groupId: number,
    groupsOrder: { groupId: number; position: number }[] = [],
  ): number {
    const groupOrderFound = groupsOrder.find(
      (groupOrder) => groupOrder.groupId === groupId,
    );
    if (groupOrderFound == undefined) {
      return groupId;
    } else {
      return groupOrderFound.position;
    }
  }
}

export { WidgetVersion, LisioWidgetVersionNames, numberToVersion };
export type { Group };
