/* 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/. */

const TIMEZONES = {
  IDLW: -12,
  NT: -11,
  HST: -10,
  AKST: -9,
  PST: -8,
  AKDT: -8,
  MST: -7,
  PDT: -7,
  CST: -6,
  MDT: -6,
  EST: -5,
  CDT: -5,
  EDT: -4,
  AST: -4,
  GUY: -3,
  ADT: -3,
  AT: -2,
  UTC: 0,
  GMT: 0,
  Z: 0,
  WET: 0,
  WEST: 1,
  CET: 1,
  BST: 1,
  IST: 1,
  CEST: 2,
  EET: 2,
  EEST: 3,
  MSK: 3,
  MSD: 4,
  ZP4: 4,
  ZP5: 5,
  ZP6: 6,
  WAST: 7,
  AWST: 8,
  WST: 8,
  JST: 9,
  ACST: 9.5,
  ACDT: 10.5,
  AEST: 10,
  AEDT: 11,
  NZST: 12,
  IDLE: 12,
  NZD: 13,
};

const TIME_REGEX = "([0-2]?[0-9])(:([0-5][0-9]))?\\s*([ap]m)?";
const TIMEZONE_REGEX = "\\w+";

// NOTE: This regex need to be localized upon supporting multi locales
//       since it supports en-US input format only.
const QUERY_REGEX = new RegExp(
  `^(${TIME_REGEX}|now)\\s*(${TIMEZONE_REGEX})?(?:\\s+in\\s+|\\s+to\\s+|\\s*=\\s*)(${TIMEZONE_REGEX}|here)`,
  "i"
);

const KEYWORD_HERE = "HERE";
const KEYWORD_NOW = "NOW";

/**
 * This module converts timezone.
 */
export class UnitConverterTimezone {
  /**
   * Convert the given search string.
   *
   * @param {string} searchString
   *   The string to be converted
   * @returns {string} conversion result.
   */
  convert(searchString) {
    const regexResult = QUERY_REGEX.exec(searchString);
    if (!regexResult) {
      return null;
    }

    const inputTime = regexResult[1].toUpperCase();
    const inputTimezone = regexResult[6]?.toUpperCase();
    let outputTimezone = regexResult[7].toUpperCase();

    if (
      (inputTimezone &&
        inputTimezone !== KEYWORD_NOW &&
        !(inputTimezone in TIMEZONES)) ||
      (outputTimezone !== KEYWORD_HERE && !(outputTimezone in TIMEZONES))
    ) {
      return null;
    }

    const inputDate = new Date();
    let isMeridiemNeeded = false;
    if (inputTime === KEYWORD_NOW) {
      inputDate.setUTCHours(inputDate.getHours());
      inputDate.setUTCMinutes(inputDate.getMinutes());
    } else {
      // If the input was given as AM/PM, we need to convert it to 24h.
      // 12AM is converted to 00, and for PM times we add 12 to the hour value except for 12PM.
      // If the input is for example 23PM, we use 23 as the hour - we don't add 12 as this would result in a date increment.
      const inputAMPM = regexResult[5]?.toLowerCase() || "";
      const inputHours =
        regexResult[2] === "12" && inputAMPM === "am"
          ? 0
          : Number(regexResult[2]);
      const inputMinutes = regexResult[4] ? Number(regexResult[4]) : 0;
      const inputMeridianHourShift =
        inputAMPM === "pm" && inputHours < 12 ? 12 : 0;
      inputDate.setUTCHours(inputHours + inputMeridianHourShift);
      inputDate.setUTCMinutes(inputMinutes);
      isMeridiemNeeded = !!inputAMPM;
    }

    const inputOffset = inputTimezone
      ? TIMEZONES[inputTimezone] * 60
      : -inputDate.getTimezoneOffset();
    let outputOffset;
    if (outputTimezone === KEYWORD_HERE) {
      outputOffset = -inputDate.getTimezoneOffset();
      const sign = -inputDate.getTimezoneOffset() > 0 ? "+" : "-";
      const hours = Math.floor(Math.abs(outputOffset) / 60);
      const minutes = formatMinutes((outputOffset % 60) * 60);
      outputTimezone = `UTC${sign}${hours}${minutes}`;
    } else {
      outputOffset = TIMEZONES[outputTimezone] * 60;
    }

    const outputDate = new Date(inputDate.getTime());
    outputDate.setUTCMinutes(
      outputDate.getUTCMinutes() - inputOffset + outputOffset
    );

    const time = new Intl.DateTimeFormat("en-US", {
      timeStyle: "short",
      hour12: isMeridiemNeeded,
      timeZone: "UTC",
    }).format(outputDate);

    return `${time} ${outputTimezone}`;
  }
}

function formatMinutes(minutes) {
  return minutes.toString().padStart(2, "0");
}
