/**
 * @fileoverview
 * The ChineseLanguagePlugin class implements the LanguagePlugin interface
 * and provides methods for converting numbers, dates, and times into their
 * Chinese textual representation. It handles integer and decimal numbers,
 * negative values, Gregorian date strings, and time strings (HH:mm).
 *
 * Note: Since the Persian solar calendar is specific to Persian, for Chinese,
 * the Gregorian calendar is used and dates are formatted as "YYYY年MM月DD日".
 */

import { ConversionOptions, InputNumber, LanguagePlugin } from "../core";

export class ChineseLanguagePlugin implements LanguagePlugin {
  /**
   * Default separator used to join tokens.
   */
  private static readonly DEFAULT_SEPARATOR: string = " ";

  /**
   * Chinese word for zero.
   */
  private static readonly ZERO_WORD: string = "零";

  /**
   * Word for negative numbers.
   */
  private static readonly NEGATIVE_WORD: string = "负";

  /**
   * Large scale units in Chinese for grouping by 4 digits.
   * For example: 10^0, 10^4, 10^8, 10^12, ...
   */
  private static readonly SCALE: string[] = [
    "", // 10^0
    "万", // 10^4
    "亿", // 10^8
    "兆", // 10^12
  ];

  /**
   * Converts a number less than 10,000 into its Chinese textual representation.
   *
   * @param {number} n - Number less than 10,000.
   * @returns {string} The Chinese numeral representation.
   *
   * Examples:
   *   7    => "七"
   *   15   => "十五"
   *   42   => "四十二"
   *   300  => "三百"
   *   456  => "四百五十六"
   */
  private convertBelowTenThousand(n: number): string {
    const digits = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
    let result = "";

    if (n < 10) {
      return digits[n];
    }
    if (n < 100) {
      const tens = Math.floor(n / 10);
      const unit = n % 10;
      if (n === 10) {
        return "十";
      }
      // For numbers between 11 and 19, omit "一" before "十"
      if (tens === 1) {
        result = "十";
      } else {
        result = digits[tens] + "十";
      }
      if (unit !== 0) {
        result += digits[unit];
      }
      return result;
    }
    if (n < 1000) {
      const hundreds = Math.floor(n / 100);
      const remainder = n % 100;
      result = digits[hundreds] + "百";
      if (remainder === 0) {
        return result;
      }
      // If remainder is less than 10, insert "零"
      if (remainder < 10) {
        result += "零" + this.convertBelowTenThousand(remainder);
      } else {
        // If tens digit is zero, also insert "零"
        if (Math.floor(remainder / 10) === 0) {
          result += "零" + this.convertBelowTenThousand(remainder);
        } else {
          result += this.convertBelowTenThousand(remainder);
        }
      }
      return result;
    }
    if (n < 10000) {
      const thousands = Math.floor(n / 1000);
      const remainder = n % 1000;
      result = thousands === 1 ? "千" : digits[thousands] + "千";
      if (remainder === 0) {
        return result;
      }
      // If remainder < 100, may need a "零"
      if (remainder < 100) {
        result += "零" + this.convertBelowTenThousand(remainder);
      } else {
        result += this.convertBelowTenThousand(remainder);
      }
      return result;
    }
    return "";
  }

  /**
   * Converts a group of up to 4 digits (as InputNumber) into its Chinese textual representation.
   * This method is required by the LanguagePlugin interface.
   *
   * @param {InputNumber} num - The number (up to 4 digits) to convert.
   * @param {any} [lexicon] - Ignored in Chinese conversion.
   * @param {string} [_separator] - Ignored in Chinese conversion.
   * @returns {string} The textual representation.
   */
  public convertTripleToWords(
    num: InputNumber,
    lexicon?: any,
    _separator?: string
  ): string {
    const value =
      typeof num === "bigint" ? Number(num) : parseInt(num.toString(), 10);
    if (value === 0) return "";
    return this.convertBelowTenThousand(value);
  }

  /**
   * Splits a numeric string into groups of 4 digits (from right to left).
   *
   * Example: "12345678" => ["1234", "5678"]
   *
   * @param {string | number} num - The number to be split.
   * @returns {string[]} An array of 4-digit groups.
   */
  private static splitIntoQuadruples(num: number | string): string[] {
    let str: string = typeof num === "number" ? num.toString() : num;
    const groups: string[] = [];
    while (str.length > 0) {
      const end = str.length;
      const start = Math.max(0, end - 4);
      groups.unshift(str.substring(start, end));
      str = str.substring(0, start);
    }
    return groups;
  }

  /**
   * Converts a given number (integer or decimal, possibly negative) into its Chinese textual form.
   * Handles custom options and converts the fractional part digit-by-digit using "点".
   *
   * @param {InputNumber} input - The number to be converted.
   * @param {ConversionOptions} [options] - Supported options:
   *         - customZeroWord: override the default word for zero.
   *         - customNegativeWord: override the default negative word.
   *         - customSeparator: override the default separator between tokens.
   * @returns {string} The Chinese textual representation of the number.
   * @throws {Error} If the input format is invalid or if the number exceeds allowed range.
   */
  public convertNumber(
    input: InputNumber,
    options?: ConversionOptions
  ): string {
    const effectiveOptions: ConversionOptions = { ...options };

    const zeroWord =
      effectiveOptions.customZeroWord || ChineseLanguagePlugin.ZERO_WORD;
    const negativeWord =
      effectiveOptions.customNegativeWord ||
      ChineseLanguagePlugin.NEGATIVE_WORD;
    const separator =
      effectiveOptions.customSeparator ||
      ChineseLanguagePlugin.DEFAULT_SEPARATOR;

    let rawInput: string =
      typeof input === "bigint" ? input.toString() : input.toString().trim();

    let isNegative = false;
    if (rawInput.startsWith("-")) {
      isNegative = true;
      rawInput = rawInput.slice(1).replace(/[,\s-]/g, "");
    } else {
      rawInput = rawInput.replace(/[,\s-]/g, "");
    }

    if (!/^\d+(\.\d+)?$/.test(rawInput)) {
      throw new Error("Error: Invalid input format.");
    }

    if (rawInput === "0" || rawInput === "0.0") {
      return zeroWord;
    }

    // Separate integer and fractional parts.
    let integerPart = rawInput;
    let fractionalPart = "";
    const pointIndex = rawInput.indexOf(".");
    if (pointIndex > -1) {
      integerPart = rawInput.substring(0, pointIndex);
      fractionalPart = rawInput.substring(pointIndex + 1);
    }

    // Safety check for integer length.
    if (integerPart.length > 66) {
      throw new Error("Error: Out of range.");
    }

    // Split integer part into groups of 4 digits.
    const groups = ChineseLanguagePlugin.splitIntoQuadruples(integerPart);
    const tokens: string[] = [];
    const scale = ChineseLanguagePlugin.SCALE;
    const numGroups = groups.length;

    for (let i = 0; i < numGroups; i++) {
      const groupNum = parseInt(groups[i], 10);
      if (groupNum === 0) continue;
      const text = this.convertBelowTenThousand(groupNum);
      const scaleIndex = numGroups - i - 1;
      let token = text;
      if (scaleIndex > 0) {
        token += scale[scaleIndex];
      }
      tokens.push(token);
    }

    let result = tokens.join(separator);

    // Process fractional part: convert each digit using Chinese numerals.
    if (fractionalPart.length > 0) {
      const digitNames = [
        "零",
        "一",
        "二",
        "三",
        "四",
        "五",
        "六",
        "七",
        "八",
        "九",
      ];
      const fracTokens = fractionalPart
        .split("")
        .map((d) => digitNames[parseInt(d, 10)]);
      result += separator + "点" + separator + fracTokens.join(separator);
    }

    if (isNegative) {
      result = negativeWord + separator + result;
    }
    return result;
  }

  /**
   * Converts a Gregorian date string (in "YYYY/MM/DD" or "YYYY-MM-DD" format)
   * into its Chinese textual representation.
   * The output format is "YYYY年MM月DD日", where each part is converted using convertNumber.
   *
   * @param {string} dateStr - The date string to be converted.
   * @param {"jalali" | "gregorian"} [calendar="gregorian"] - Only Gregorian is supported for Chinese.
   * @returns {string} The Chinese textual form of the date.
   * @throws {Error} If the format is invalid or if the month is out of range.
   */
  public convertDateToWords(
    dateStr: string,
    calendar: "jalali" | "gregorian" = "gregorian"
  ): string {
    // For Chinese, we use the Gregorian calendar and format as "YYYY年MM月DD日".
    const parts = dateStr.split(/[-\/]/);
    if (parts.length !== 3) {
      throw new Error(
        "Invalid date format. Expected 'YYYY/MM/DD' or 'YYYY-MM-DD'."
      );
    }
    const [yearStr, monthStr, dayStr] = parts;
    const monthNum = parseInt(monthStr, 10);
    if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
      throw new Error("Invalid month in date.");
    }

    // Convert year, month, and day using convertNumber.
    const yearWords = this.convertNumber(yearStr);
    const monthWords = this.convertNumber(monthStr);
    const dayWords = this.convertNumber(dayStr);

    return `${yearWords}年${monthWords}月${dayWords}日`;
  }

  /**
   * Converts a time string in "HH:mm" format to its Chinese textual representation.
   * The output format is "<hour>时<minute>分". If minute is zero, only "<hour>时" is returned.
   *
   * @param {string} timeStr - The time string in "HH:mm" format.
   * @returns {string} The Chinese textual representation of the time.
   * @throws {Error} If the format is invalid or if hours/minutes are out of range.
   */
  public convertTimeToWords(timeStr: string): string {
    const parts = timeStr.split(":");
    if (parts.length !== 2) {
      throw new Error("Invalid time format. Expected format 'HH:mm'.");
    }
    const [hourStr, minuteStr] = parts;
    const hour = parseInt(hourStr, 10);
    const minute = parseInt(minuteStr, 10);
    if (isNaN(hour) || isNaN(minute)) {
      throw new Error(
        "Invalid time format. Hours and minutes should be numbers."
      );
    }
    if (hour < 0 || hour > 23) {
      throw new Error("Invalid hour value. Hour should be between 0 and 23.");
    }
    if (minute < 0 || minute > 59) {
      throw new Error(
        "Invalid minute value. Minute should be between 0 and 59."
      );
    }

    const hourWords = this.convertNumber(hour);
    let result = `${hourWords}时`;
    if (minute !== 0) {
      const minuteWords = this.convertNumber(minute);
      result += `${minuteWords}分`;
    }
    return result;
  }
}
