/**
 * @mergeTarget
 * @module Src/Utils
 */

import {
  LisioBooleanParameterNames,
  LisioNumericParameterNames,
  LisioProfile,
  LisioProfileFactory,
  LisioStringParameterNames,
  LisioUser,
} from "@lisio/lisio-profils";

/**
 * @async
 * Utils function to fetch resource with timeout
 * @param {string} resource - Resource to fetch
 * @param {any} options - Options of fetch
 * @returns Returns response
 * @source
 */
async function fetchWithTimeout(resource: string, options: any = {}) {
  const { timeout = 8000 } = options;

  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);

  const response = await fetch(resource, {
    ...options,
    signal: controller.signal,
  });
  clearTimeout(id);

  return response;
}

/**
 * Old stuff to convert new user system to old. Will probably be deprecated when client script will be reworks.
 * @param {LisioUser} user - User to convert
 * @returns Returns converted user as a string like old formating : V1.0/1/0/0/01/1/01/0/10/1/User1_1
 * @source
 */
function convertStorageFormatToFrontCookies(user: LisioUser): string {
  let ret: (string | number)[] = [
    0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  ].slice();
  for (const profilName of user.profiles) {
    const profile: LisioProfile =
      LisioProfileFactory.current.buildLisioProfile(profilName);
    for (const parameter of profile.parameters.values()) {
      switch (parameter.name) {
        case LisioNumericParameterNames.FONT_SIZE:
          ret[0] = parameter.value as number;
          break;
        case LisioNumericParameterNames.LINE_HEIGHT:
          ret[1] = parameter.value as number;
          break;
        case LisioNumericParameterNames.WORD_SPACING:
          ret[2] = parameter.value as number;
          break;
        case LisioNumericParameterNames.LETTER_SPACING:
          ret[3] = parameter.value as number;
          break;
        case LisioNumericParameterNames.CONTRAST:
          ret[4] = parameter.value as number;
          break;
        case LisioNumericParameterNames.ZOOM:
          ret[5] = parameter.value as number;
          break;
        case LisioNumericParameterNames.BIGGER_CLICK:
          ret[6] = parameter.value as number;
          break;
        case LisioStringParameterNames.THEME:
          ret[7] = convertStringParameter(
            parameter.name,
            parameter.value as string,
          );
          break;
        case LisioStringParameterNames.DALTONISM:
          ret[8] = convertStringParameter(
            parameter.name,
            parameter.value as string,
          );
          break;
        case LisioStringParameterNames.FONT_FAMILY:
          ret[9] = convertStringParameter(
            parameter.name,
            parameter.value as string,
          );
          break;
        case LisioStringParameterNames.TEXT_ALIGN:
          ret[10] = convertStringParameter(
            parameter.name,
            parameter.value as string,
          );
          break;
        case LisioBooleanParameterNames.VOCA:
          ret[11] = parameter.value ? 1 : 0;
          break;
        case LisioBooleanParameterNames.SHOW_ALT:
          ret[12] = parameter.value ? 1 : 0;
          break;
        case LisioBooleanParameterNames.HIGHLIGHT:
          ret[13] = parameter.value ? 1 : 0;
          break;
        case LisioBooleanParameterNames.DISABLE_ANIMATION:
          ret[14] = parameter.value ? 1 : 0;
          break;
        case LisioBooleanParameterNames.KEYBOARD_NAVIGATION:
          ret[15] = parameter.value ? 1 : 0;
          break;
        case LisioBooleanParameterNames.READING_MASK:
          ret[16] = parameter.value ? 1 : 0;
          break;
        case LisioStringParameterNames.GOOGLE_TRANSLATION:
          if (typeof ret[17] === "number") {
            ret[17] = `${convertStringParameter(
              parameter.name,
              parameter.value as string,
            )}-0`;
          } else {
            ret[17] = ret[17].replace(
              /\w+(-\w+)/,
              `${convertStringParameter(
                parameter.name,
                parameter.value as string,
              )}$1`,
            );
          }
          break;
        case LisioStringParameterNames.DEEPL_TRANSLATION:
          if (typeof ret[17] === "number") {
            ret[17] = `0-${convertStringParameter(
              parameter.name,
              parameter.value as string,
            )}`;
          } else {
            ret[17] = ret[17].replace(
              /(\w+-)\w+/,
              `$1${convertStringParameter(
                parameter.name,
                parameter.value as string,
              )}`,
            );
          }
          break;
        case LisioStringParameterNames.UNDERLINE_RED:
        case LisioStringParameterNames.UNDERLINE_GREEN:
        case LisioStringParameterNames.UNDERLINE_BLUE:
          if (typeof ret[18] === "number") {
            ret[18] = parameter.value as string;
          } else {
            ret[18] += ` ${parameter.value as string}`;
          }
          break;
        case LisioBooleanParameterNames.SPEECH_SYNTHESIS_STATE:
          if (typeof ret[19] === "number") {
            ret[19] = `${parameter.value ? 1 : 0}-0-0-0`;
          } else {
            ret[19] = ret[19].replace(/(^[01])/, parameter.value ? "1" : "0");
          }
          break;
        case LisioNumericParameterNames.SPEECH_SYNTHESIS_PITCH:
          if (typeof ret[19] === "number") {
            ret[19] = `0-0-${parameter.value as number}-0`;
          } else {
            ret[19] = ret[19].replace(
              /([01]-0-)\w+/,
              `$1${parameter.value as number}`,
            );
          }
          break;
        case LisioNumericParameterNames.SPEECH_SYNTHESIS_RATE:
          if (typeof ret[19] === "number") {
            ret[19] = `0-0-0-${parameter.value as number}`;
          } else {
            ret[19] = ret[19].replace(
              /([01]-0-\w+-)\w+/,
              `$1${parameter.value as number}`,
            );
          }
          break;
        case LisioBooleanParameterNames.LIST:
          ret[20] = parameter.value ? 1 : 0;
          break;
        case LisioStringParameterNames.CURSOR_SIZE:
          ret[21] = convertStringParameter(
            parameter.name,
            parameter.value as string,
          );
          break;
      }
    }
  }
  return ret.join("/");
}

/**
 * Old stuff to convert string parameter to number. Will probably be deprecated when client script will be reworks.
 * @param {LisioStringParameterNames} parameterName - Parameter name
 * @param {string} value - Value of parameter
 * @returns Returns converted value as number
 * @source
 */
function convertStringParameter(
  parameterName: LisioStringParameterNames,
  value: string,
): number {
  const themes = ["defaut", "cYbgB", "cBbgW", "cWbgB", "cBLbgW", "cBbgG"];
  const filtres = [
    "defaut",
    "protanopia",
    "deuteranopia",
    "tritanopia",
    "monochromacy",
    "enhanceR",
    "enhanceG",
  ];
  const polices = [
    "defaut",
    "andika",
    "openDyslexic",
    "arial",
    "comic",
    "luciole",
    "inclusiveSans",
  ];
  const textAlign = ["defaut", "left", "center", "right", "justify"];
  const tailles = ["defaut", "big", "verybig"];
  const langsTranslatorAPI = [
    "defaut",
    "FR",
    "EN-GB",
    "EN-US",
    "ES",
    "IT",
    "DE",
    "JA",
    "RU",
    "ZH",
    "PT-PT",
    "PT-BR",
    "DA",
    "FI",
    "EL",
    "NL",
    "SV",
    "CS",
    "TR",
    "BG",
    "ET",
    "HU",
    "ID",
    "LT",
    "LV",
    "PL",
    "RO",
    "SK",
    "SL",
  ];
  const googleLangs = [
    "defaut",
    "fr",
    "en",
    "es",
    "it",
    "de",
    "ja",
    "ru",
    "zh-CN",
    "ar",
    "pt",
    "ca",
    "ko",
    "da",
    "fi",
    "cy",
    "el",
    "he",
    "hi",
    "nl",
    "no",
    "sv",
    "cs",
    "th",
    "tr",
    "uk",
    "vi",
    "yi",
    "lb",
    "ps",
    "bg",
    "et",
    "hu",
    "lv",
    "lt",
    "pl",
    "ro",
    "sk",
    "sl",
  ];
  switch (parameterName) {
    case LisioStringParameterNames.THEME:
      return themes.indexOf(value);
    case LisioStringParameterNames.DALTONISM:
      return filtres.indexOf(value);
    case LisioStringParameterNames.FONT_FAMILY:
      return polices.indexOf(value);
    case LisioStringParameterNames.TEXT_ALIGN:
      return textAlign.indexOf(value);
    case LisioStringParameterNames.CURSOR_SIZE:
      return tailles.indexOf(value);
    case LisioStringParameterNames.GOOGLE_TRANSLATION:
      return googleLangs.indexOf(value);
    case LisioStringParameterNames.DEEPL_TRANSLATION:
      return langsTranslatorAPI.indexOf(value);
    default:
      return -1;
  }
}

/**
 * Util function to determine if a device has touche event. This function is used to determine if is a mobile device or computer. It does the job but need to be improve because some computers have touch screen too.
 * @returns If device is a touche device or not
 */
function isTouchDevice(): boolean {
  try {
    document.createEvent("TouchEvent");
    return true;
  } catch (e) {
    return false;
  }
}

let firstEl: HTMLElement | null = null;
let lastEl: HTMLElement | null = null;

function getFocusable() {
  const focusables = Array.from(
    document.querySelectorAll<HTMLElement>(
      `
      a[href],
      button:not([disabled]):not([tabindex="-1"]),
      input:not([disabled]):not([tabindex="-1"]),
      select:not([disabled]):not([tabindex="-1"]),
      textarea:not([disabled]):not([tabindex="-1"]),
      [tabindex]:not([tabindex="-1"])
      `
    )
  ).filter(el => el.offsetParent !== null);

  const radios = focusables.filter(
    (el): el is HTMLInputElement => el instanceof HTMLInputElement && el.type === "radio"
  );

  if (radios.length > 0) {
    const uniqueRadioGroups = new Map<string, HTMLInputElement>();
    for (const radio of radios) {
      if (!uniqueRadioGroups.has(radio.name)) {
        uniqueRadioGroups.set(radio.name, radio);
      }
    }
    const cleaned = [
      ...focusables.filter(el => !(el instanceof HTMLInputElement && el.type === "radio")),
      ...uniqueRadioGroups.values(),
    ];

    cleaned.sort((a, b) => (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1));
    firstEl = cleaned[0] ?? null;
    lastEl = cleaned[cleaned.length - 1] ?? null;
    return cleaned;
  }

  firstEl = focusables[0] ?? null;
  lastEl = focusables[focusables.length - 1] ?? null;
  return focusables;
}

function trapKeyDown(e: KeyboardEvent) {
  if (e.key !== "Tab") return;

  const list = getFocusable();
  if (list.length === 0) return;

  const active = document.activeElement as HTMLElement | null;

  // --- SHIFT + TAB ---
  if (e.shiftKey) {
    if (active === firstEl || active === document.body || active === null) {
      e.preventDefault();
      lastEl?.focus();
    }
  }

  // --- TAB simple ---
  else {
    if (active === lastEl || active === null) {
      e.preventDefault();
      firstEl?.focus();
    }
  }
}

function activateIframeFocusTrap() {
  document.addEventListener("keydown", trapKeyDown, true);
  // force un focus initial
  const list = getFocusable();
  list[0]?.focus();
}

function deactivateIframeFocusTrap() {
  document.removeEventListener("keydown", trapKeyDown, true);
}

export {
  fetchWithTimeout,
  convertStorageFormatToFrontCookies,
  isTouchDevice,
  activateIframeFocusTrap,
  deactivateIframeFocusTrap,
  trapKeyDown
};
