/**
 * @mergeTarget
 * @module Src/Managers
 */
import { WidgetVersion } from "../versions/widget-version";
import {
  LisioBooleanParameterNames,
  LisioParameterNames,
  LisioProfileNames,
} from "@lisio/lisio-profils";
import { LisioScreen } from "../render/screens/lisio-screen";
import {
  CategoryRenderObject,
  ScreenNames,
  MergedCategoriesNames,
} from "../render/screens/screen-types";
import { CustomHeader } from "../render/components/custom-header/custom-header";
import { CustomNav } from "../render/components/custom-nav/custom-nav";
import { ErrorCodes } from "../../misc/error-codes";
import { HomeScreen } from "../render/screens/home/home-screen";
import { CoreErrorCodes, Engine } from "@lisio/lisio-engine";
import { UserController } from "../controllers/user-controller";
import { CustomText } from "../render/components/custom-text/custom-text";
import { BackButton } from "../render/components/back-button/back-button";

/**
 * Class representing a screen manager.\
 * It aims to centralize the logic of widget screens management.\
 * If something need to manage or use mutliple screens it has to use this class.\
 * To execute his responsability this class does :
 *  * Changes displayed screen
 *  * Displays breadcrumb
 */
class ScreenManager {
  /**
   * Private attribute to store the widget header
   * @source
   */
  private _customHeader: CustomHeader;

  /**
   * Private attribute to store the current widget version
   * @source
   */
  private _version: WidgetVersion;

  /**
   * Private attribute to store the current screen
   * @source
   */
  private _currentScreen?: LisioScreen;

  /**
   * Private attribute to store the navigation component
   * @source
   */
  private _nav?: CustomNav;
  /**
   * Private attribute to store the back button
   * @source
   */
  private _onlyTradManagementBackButton?: BackButton;
  /**
   * Setter for attribute {@link _onlyTradManagementBackButton | _onlyTradManagementBackButton}
   * @param {BackButton} value - New value for attribute {@link _onlyTradManagementBackButton | _onlyTradManagementBackButton}
   * @source
   */
  public set onlyTradManagementBackButton(
    onlyTradManagementBackButton: BackButton,
  ) {
    this._onlyTradManagementBackButton = onlyTradManagementBackButton;
  }
  /**
   * Private attribute to store the previous screen
   * @source
   */
  private _previousScreenName: string = HomeScreen.name;
  /**
   * Getter for attribute {@link _version | version}
   * @source
   */
  public get version() {
    return this._version;
  }

  public get currentScreen(): LisioScreen | undefined {
    return this._currentScreen;
  }

  public get nav(): CustomNav | undefined {
    return this._nav;
  }

  /**
   * Setter for attribute {@link _nav | _nav}
   * @param {CustomNav} value - New value for attribute {@link _nav | _nav}
   * @source
   */
  public set nav(value: CustomNav) {
    this._nav = value;
  }

  /**
   * Private attribute to store instance of {@link ScreenManager | ScreenManager}
   * @source
   */
  private static _current: ScreenManager;

  /**
   * @static
   * Getter for attribute {@link _current | _current}
   * @returns Returns _current attribute
   * @source
   */
  public static get current(): ScreenManager {
    return ScreenManager._current;
  }

  /**
   * @async
   * Public method to change navigate between screens.\
   * This method does :
   *  * Determines the next screen. If next screen is not passed in parameters, this method will choose previous screen as the next screen
   *  * Determines current screen if is unknow
   *  * Determines if next screen has to be renderer. If next screen was never rendered then the HTML renderer create the screen else this method removes the "hidden" class
   *  * Toggles focus on screen title if needed. See {@link Src/Components/CustomText.CustomText.toggleFocus | CustomText.toggleFocus}
   *  * Toggles "home button" if not the home screen. See {@link Src/Components/CustomHeader.CustomHeader.toggleHomeButton | CustomHeader.toggleHomeButton}
   * @param {string | undefined} nextScreenName - Next screen name to go to
   * @param {Number | undefined} eventDetail - Indicates if event was fired by click (= 0) or by keyboard (= 1)
   * @param {boolean | undefined} fromInfobox - Indicates if event was fired by click on an infobox button
   * @returns Returns a promise containing nothing
   * @throws If scrren or his id are undefined. See {@link Src/Utils.ErrorCodes.SCREEN_NOT_INITIALIZED | ErrorCodes.SCREEN_NOT_INITIALIZED}
   * @source
   */
  public async changeScreen(
    nextScreenName?: string,
    eventDetail?: Number,
    fromInfobox?: boolean,
  ): Promise<void> {
    if (nextScreenName != undefined && nextScreenName in ScreenNames) {
      nextScreenName = ScreenNames[nextScreenName];
    }
    if (this._currentScreen == undefined) {
      this._currentScreen = this._version.mainScreen;
    }
    if (nextScreenName === this._currentScreen?.componentName) {
      this._currentScreen?.screenTitle?.toggleFocus(eventDetail === 0);
      return;
    }
    if (nextScreenName == undefined) {
      nextScreenName = this._previousScreenName;
    }

    const nextScreen: LisioScreen | undefined =
      this._version.screens.get(nextScreenName);
    if (nextScreen == undefined) {
      throw new Error(
        `Code : ${ErrorCodes.SCREEN_NOT_INITIALIZED}. Screen or his id are undefined`,
      );
    }

    this._nav?.selectTab(nextScreenName);
    nextScreen.hasToLoadAssets = true;
    if (this._currentScreen?.htmlElement != undefined) {
      this._currentScreen.htmlElement.classList.toggle("hidden");
    }
    if (nextScreen.htmlElement == undefined) {
      await Engine.current.renderComponent(
        nextScreen,
        true,
        "append",
        undefined,
        false,
        true,
      );
      if (
        this._nav?.findChildById(nextScreenName + "-nav") &&
        nextScreen.htmlElement != undefined
      ) {
        (nextScreen!.htmlElement as HTMLElement).setAttribute(
          "aria-labelledby",
          nextScreenName + "-nav",
        );
      }
    } else if (nextScreen.htmlElement != undefined) {
      nextScreen.htmlElement.classList.toggle("hidden");
    }
    nextScreen.screenTitle?.toggleFocus(eventDetail === 0);

    // if (this._version.screens.size > 5) {
    //   this._customHeader.toggleHomeButton(
    //     nextScreen.componentName !== ScreenNames[HomeScreen.name] && ((UserController.current.currentUser != undefined && UserController.current.currentUser.customParameters.has(LisioBooleanParameterNames.IS_ACTIVE)) || UserController.current.currentUser == undefined),
    //   );
    // }
    // this._nav?.toggleNav(
    //   nextScreen.componentName !== ScreenNames[HomeScreen.name] && ((UserController.current.currentUser != undefined && UserController.current.currentUser.customParameters.has(LisioBooleanParameterNames.IS_ACTIVE)) || UserController.current.currentUser == undefined),
    // );
    if (
      (nextScreen.componentName == "settings-screen" ||
        nextScreen.componentName == "synth-screen") &&
      nextScreen.componentName != this.version.mainScreen?.componentName
    ) {
      if (fromInfobox) {
        nextScreen.backButton!.updateBackButton(
          this._currentScreen!.componentName.substring(
            0,
            this._currentScreen!.componentName.indexOf("-screen"),
          ) + "-title",
          this._currentScreen!.componentName,
        );
        // if (this._currentScreen?.componentName !== "home-screen") {
        nextScreen.backButton?.showBackButton();
        // } else {
        //   nextScreen.backButton?.hideBackButton();
        // }
      } else {
        nextScreen.backButton?.hideBackButton();
      }
    }

    if (
      nextScreen.componentName == "home-screen" &&
      nextScreen.componentName != this.version.mainScreen?.componentName
    ) {
      nextScreen.backButton!.updateBackButton(
        this._currentScreen!.componentName.substring(
          0,
          this._currentScreen!.componentName.indexOf("-screen"),
        ) + "-title",
        this._currentScreen!.componentName,
      );
      nextScreen.backButton?.showBackButton();
    }

    if (this._currentScreen != undefined) {
      this._previousScreenName = this._currentScreen.componentName;
    }

    this._currentScreen = nextScreen;
    this.toggleNavAndHeadersButtonAndUserManagementButtonAndChangeManagementScreenTitle(
      (UserController.current.currentUser != undefined &&
        UserController.current.currentUser.customParameters.has(
          LisioBooleanParameterNames.IS_ACTIVE,
        )) ||
        UserController.current.currentUser == undefined,
    );
  }

  /**
   * Public method to toggle navigation, header buttons, users button and to change the management screen title's text
   * @param {boolean} isShown - Indicates if home button has to be shown
   * @returns Returns nothing
   * @source
   */
  public toggleNavAndHeadersButtonAndUserManagementButtonAndChangeManagementScreenTitle(
    isShown: boolean,
  ): void {
    if (this._version.screens.size > 5) {
      this._customHeader.toggleHomeButton(
        (isShown &&
          this._currentScreen != undefined &&
          this._currentScreen.componentName !== "home-screen") ||
          (this._currentScreen != undefined &&
            this._currentScreen.componentName !== "management-screen" &&
            this._currentScreen.componentName !== "home-screen" &&
            this._currentScreen.componentName !== "reset-screen"),
      );
      this._customHeader.toggleManagementButton(
        isShown && UserController.current.users.size > 0,
      );
    }
    this._nav?.toggleNav(
      (isShown &&
        this._currentScreen != undefined &&
        this._currentScreen.componentName !== "home-screen") ||
        (this._currentScreen != undefined &&
          this._currentScreen.componentName !== "management-screen" &&
          this._currentScreen.componentName !== "home-screen" &&
          this._currentScreen.componentName !== "users-screen" &&
          this._currentScreen.componentName !== "reset-screen"),
    );
    if (isShown) {
      (
        this._version.screens
          .get("management-screen")
          ?.findChildById("screen-management-title") as CustomText
      ).changeText("management-title");
      (
        this._version.screens
          .get("management-screen")
          ?.findChildById("infobox-adaptation") as CustomText
      ).changeText("infobox-adaptation");
    } else {
      (
        this._version.screens
          .get("management-screen")
          ?.findChildById("screen-management-title") as CustomText
      ).changeText("management-title-off");
      (
        this._version.screens
          .get("management-screen")
          ?.findChildById("infobox-adaptation") as CustomText
      ).changeText("infobox-adaptation-off");
    }
    if (!isShown) {
      this._onlyTradManagementBackButton?.htmlElement?.setAttribute(
        "disabled",
        "true",
      );
    } else {
      this._onlyTradManagementBackButton?.htmlElement?.removeAttribute(
        "disabled",
      );
    }
  }

  /**
   * Public method to highlight categories, parameters or profiles if they are enabled.\
   * This method does :
   *  * Checks in current category if a parameter, profile or subcategory is also active
   *  * Calls itself recursively to active parent until top screen is reached
   * @param {LisioProfileNames | LisioParameterNames | LisioCategoryNames} name - Name of targeted parameter, profile or category
   * @param {LisioCategoryNames} categoryName - Name of the category in which the targeted parameter, profile or category is included
   * @param {string} parentScreenName - Parent screen name of current screen including targeted parameter, profile or category in screen tree
   * @param {boolean} isActive - If parameter or profile, this paramater indicates their state, if category this parameter indicates if the category has at least one parameter, profile or category active
   * @returns Returns nothing
   * @source
   */
  public breadcrumb(
    name: LisioProfileNames | LisioParameterNames | MergedCategoriesNames,
    screenName: string,
    categoryName: MergedCategoriesNames,
    parentScreenName: string,
    isActive: boolean,
  ): void {
    parentScreenName = ScreenNames[parentScreenName];
    const parentScreen: LisioScreen | undefined =
      this._version.screens.get(parentScreenName);
    if (parentScreen != undefined) {
      const categoryRenderObject: CategoryRenderObject | undefined =
        parentScreen.categoryRenderObjects.get(categoryName);
      if (categoryRenderObject != undefined) {
        if (isActive) {
          categoryRenderObject.activeSubObjects.add(name);
        } else {
          categoryRenderObject.activeSubObjects.delete(name);
        }
        categoryRenderObject.component?.setActive(
          categoryRenderObject.activeSubObjects.size > 0,
        );

        this._nav?.breadcrumb(
          categoryRenderObject.activeSubObjects.size === 0,
          screenName,
          parentScreenName,
        );

        if (
          categoryRenderObject.categoryName != undefined &&
          categoryRenderObject.parentScreenName != undefined
        ) {
          this.breadcrumb(
            categoryName,
            parentScreenName,
            categoryRenderObject.categoryName,
            categoryRenderObject.parentScreenName,
            categoryRenderObject.activeSubObjects.size !== 0,
          );
        }
      }
    }
  }

  public getScreen<T>(screenClassName: string): T | undefined {
    if (ScreenNames[screenClassName] != undefined) {
      return this._version.screens.get(ScreenNames[screenClassName]) as T;
    } else {
      return undefined;
    }
  }

  /**
   * Constructor of class {@link ScreenManager | ScreenManager}
   * @param {WidgetVersion} version - Current widget version
   * @param {CustomHeader} customHeader - Widget header
   * @throws If singleton already initialize.\
   * See {@link https://env-preprod-docs.lisio.fr/lisio-engine/enums/Src_Core.CoreErrorCodes.html#SINGLETON_NOT_UNIQUE | CoreErrorCodes.SINGLETON_NOT_UNIQUE}
   * @source
   */
  constructor(version: WidgetVersion, customHeader: CustomHeader) {
    if (ScreenManager._current == undefined) {
      ScreenManager._current = this;
      this._version = version;
      this._customHeader = customHeader;
    } else {
      throw new Error(
        `Code : ${CoreErrorCodes.SINGLETON_NOT_UNIQUE}. Singleton already initialize`,
      );
    }
  }
}

export { ScreenManager };
