/**
 * unicodeKoreanChars
 * [!] 키 = 분리된 문자(NFKD), 값 = 완성된 문자
 * [!] key = decomposed character(NFKD), value = composed character
 */
export const unicodeKoreanChars: Record<string, string> = {
  ᄇ: "ㅂ",
  ᄌ: "ㅈ",
  ᄃ: "ㄷ",
  ᄀ: "ㄱ",
  ᄉ: "ㅅ",
  ᅭ: "ㅛ",
  ᅧ: "ㅕ",
  ᅣ: "ㅑ",
  ᅢ: "ㅐ",
  ᅦ: "ㅔ",
  ᄆ: "ㅁ",
  ᄂ: "ㄴ",
  ᄋ: "ㅇ",
  ᄅ: "ㄹ",
  ᄒ: "ㅎ",
  ᅩ: "ㅗ",
  ᅥ: "ㅓ",
  ᅡ: "ㅏ",
  ᅵ: "ㅣ",
  ᄏ: "ㅋ",
  ᄐ: "ㅌ",
  ᄎ: "ㅊ",
  ᄑ: "ㅍ",
  ᅲ: "ㅠ",
  ᅮ: "ㅜ",
  ᅳ: "ㅡ",
  ᄈ: "ㅃ",
  ᄍ: "ㅉ",
  ᄄ: "ㄸ",
  ᄁ: "ㄲ",
  ᄊ: "ㅆ",
  ᅤ: "ㅒ",
  ᅨ: "ㅖ",
  ᅪ: "ㅘ",
  ᅫ: "ㅙ",
  ᅬ: "ㅚ",
  ᅯ: "ㅝ",
  ᅰ: "ㅞ",
  ᅱ: "ㅟ",
  ᅴ: "ㅢ",
  // 종성
  ᆨ: "ㄱ",
  ᆩ: "ㄲ",
  ᆫ: "ㄴ",
  ᆮ: "ㄷ",
  ᆯ: "ㄹ",
  ᆷ: "ㅁ",
  ᆸ: "ㅂ",
  ᆺ: "ㅅ",
  ᆻ: "ㅆ",
  ᆼ: "ㅇ",
  ᇂ: "ㅎ",
  ᆽ: "ㅈ",
  ᆾ: "ㅊ",
  ᆿ: "ㅋ",
  ᇀ: "ㅊ",
  ᇁ: "ㅍ",
  ㄸ: "ㄸ",
  ㅉ: "ㅉ",
} as const;

/**
 * qwertyKorKeys
 */
export const qwertyKorKeys: Record<string, string> = {
  q: "ᄇ",
  w: "ᄌ",
  e: "ᄃ",
  r: "ᄀ",
  t: "ᄉ",
  y: "ᅭ",
  u: "ᅧ",
  i: "ᅣ",
  o: "ᅢ",
  p: "ᅦ",
  a: "ᄆ",
  s: "ᄂ",
  d: "ᄋ",
  f: "ᄅ",
  g: "ᄒ",
  h: "ᅩ",
  j: "ᅥ",
  k: "ᅡ",
  l: "ᅵ",
  z: "ᄏ",
  x: "ᄐ",
  c: "ᄎ",
  v: "ᄑ",
  b: "ᅲ",
  n: "ᅮ",
  m: "ᅳ",
  Q: "ᄈ",
  W: "ᄍ",
  E: "ᄄ",
  R: "ᄁ",
  T: "ᄊ",
  O: "ᅤ",
  P: "ᅨ",
} as const;

/**
 * qwertyEngKeys
 */
export const qwertyEngKeys: Record<string, string> = Object.entries(
  qwertyKorKeys,
).reduce(
  (acc, [key, value]) => {
    acc[value] = key;
    return acc;
  },
  {} as Record<string, string>,
);

/**
 * initialConsonants
 * [!] 키 = 종성, 값 = 초성
 * [!] key = final consonant, value = initial consonant
 */
export const initialConsonants: Record<string, string> = {
  ᆨ: "ᄀ",
  ᆩ: "ᄁ",
  ᆫ: "ᄂ",
  ᆮ: "ᄃ",
  ᆯ: "ᄅ",
  ᆷ: "ᄆ",
  ᆸ: "ᄇ",
  ᆺ: "ᄉ",
  ᆻ: "ᄊ",
  ᆼ: "ᄋ",
  ᄒ: "ᇂ",
  ᆽ: "ᄌ",
  ᆾ: "ᄎ",
  ᆿ: "ᄏ",
  ᇀ: "ᄐ",
  ᇁ: "ᄑ",
  ㄸ: "ᄄ",
  ㅉ: "ᄍ",
  ᄈ: "",
} as const;

/**
 * finalConsonants
 * [!] 키 = 초성, 값 = 종성
 * [!] key = initial consonant, value = final consonant
 */
export const finalConsonants: Record<string, string> = Object.entries(
  initialConsonants,
).reduce(
  (acc, [key, value]) => {
    acc[value] = key;
    return acc;
  },
  {} as Record<string, string>,
);

/**
 * composedFinalConsonants
 * [!] 키 = 초성, 값 = 종성
 * [!] key = initial consonant, value = final consonant
 */
const composedFinalConsonants: Record<string, string> = {
  ᄀᄉ: "ᆳ",
  ᄂᄌ: "ᆬ",
  ᄂᄒ: "ᆭ",
  ᄅᄀ: "ᆰ",
  ᄅᄆ: "ᆱ",
  ᄅᄇ: "ᆲ",
  ᄅᄉ: "ᆳ",
  ᄅᄐ: "ᆴ",
  ᄅᄑ: "ᆵ",
  ᄅᄒ: "ᆶ",
  ᄇᄉ: "ᆹ",
};

/**
 * decomposedFinalConsonants
 * [!] 키 = 종성, 값 = 초성
 * [!] key = final consonant, value = initial consonant
 */
const decomposedFinalConsonants: Record<string, string> = Object.entries(
  composedFinalConsonants,
).reduce(
  (acc, [key, value]) => {
    acc[value] = key;
    return acc;
  },
  {} as Record<string, string>,
);

/**
 * diphthongs
 */
const diphthongs: Record<string, string> = {
  ᅩᅡ: "ᅪ",
  ᅩᅢ: "ᅫ",
  ᅩᅵ: "ᅬ",
  ᅮᅥ: "ᅯ",
  ᅮᅦ: "ᅰ",
  ᅮᅵ: "ᅱ",
  ᅳᅵ: "ᅴ",
};

/**
 * decomposedVowels
 */
const decomposedVowels: Record<string, string> = Object.entries(
  diphthongs,
).reduce(
  (acc, [key, value]) => {
    acc[value] = key;
    return acc;
  },
  {} as Record<string, string>,
);

/**
 * checkConsonant
 */
export const checkConsonant = (char: string) => {
  return char in finalConsonants || char in initialConsonants;
};

/**
 * convertEngKeyToKorKey
 */
export const convertEngKeyToKorKey = (engChar: string) => {
  if (engChar in qwertyKorKeys) {
    return qwertyKorKeys[engChar];
  }
  if (engChar.toLowerCase() in qwertyKorKeys) {
    return qwertyKorKeys[engChar.toLowerCase()];
  }
  if (engChar.toUpperCase() in qwertyKorKeys) {
    return qwertyKorKeys[engChar.toUpperCase()];
  }
  return engChar;
};

/**
 * convertEngToKor
 */
export const convertEngToKor = (engString: string) => {
  const ret = [];
  for (let i = 0; i < engString.length; i += 1) {
    const engChar = engString[i];
    let isConsonantPrevChar = false;
    let isConsonantNextChar = false;
    if (i > 0) {
      const prevChar = engString[i - 1];
      const prevKorChar = convertEngKeyToKorKey(prevChar);
      isConsonantPrevChar = checkConsonant(prevKorChar);
    }
    if (engString.length - 1 !== i) {
      const nextChar = engString[i + 1];
      const nextKorChar = convertEngKeyToKorKey(nextChar);
      isConsonantNextChar = checkConsonant(nextKorChar);
    }
    const currKorChar = convertEngKeyToKorKey(engChar);
    const isConsonantCurrChar = checkConsonant(currKorChar);
    if (
      isConsonantCurrChar === false &&
      isConsonantNextChar === false &&
      engString.length - 1 > i
    ) {
      /**
       * 현재 문자와 다음 문자가 자음이 아닌 경우 복합 종성 처리
       * If the current and next characters are not consonants, process the composed final consonant
       */
      const nextChar = engString[i + 1];
      const nextKorChar = convertEngKeyToKorKey(nextChar);
      const result = diphthongs[currKorChar + nextKorChar];
      if (result) {
        ret.push(result);
        i += 1;
      } else {
        /**
         * 복합 중성으로 처리되지 않은 경우
         * If not processed as a composed vowel
         */
        ret.push(currKorChar);
      }
    } else if (
      isConsonantPrevChar === false &&
      isConsonantCurrChar &&
      isConsonantNextChar &&
      i > 0
    ) {
      /**
       * 이전 문자가 자음이 아니고, 현재, 다음 문자가 모두 자음인 경우 복합 종성으로 변경
       * If the previous character is not a consonant and the current and next characters are all consonants, change to a composed final consonant
       */
      const nextChar = engString[i + 1];
      const nextKorChar = convertEngKeyToKorKey(nextChar);
      const complexFinalConsonant =
        composedFinalConsonants[currKorChar + nextKorChar];
      if (complexFinalConsonant) {
        ret.push(complexFinalConsonant);
        /**
         * 다음 문자를 처리하지 않음(현재 문자 + 다음 문자 = 복합 종성처리)
         * Do not process the next character (current character + next character = composed final consonant processing)
         */
        i += 1;
      } else {
        /**
         * 복합 종성으로 변경되지 않은 경우 종성 처리
         * If not changed to a composed final consonant, process the final consonant
         */
        ret.push(finalConsonants[currKorChar] || currKorChar);
      }
    } else if (i === engString.length - 1 && isConsonantCurrChar) {
      /**
       * 현재 문자가 초성이고 마지막 문자인 경우 종성으로 변경
       * If the current character is an initial consonant and the last character, change to a final consonant
       */
      ret.push(finalConsonants[currKorChar] || currKorChar);
    } else {
      /**
       * 그 외의 경우
       * Other cases
       */
      ret.push(currKorChar);
    }
  }
  /**
   * 한글 문자열로 변환
   * Convert to Korean string
   */
  const outputString = ret.join("").normalize("NFC");
  /**
   * 완성형 문자중 누락된 한글 자음 또는 모음을 원래 문자열로 매핑
   * Map missing Korean consonants or vowels in composed string to original string
   */
  return [...outputString]
    .map((char) => unicodeKoreanChars[char] || char)
    .join("");
};

/**
 * convertKorToEng
 */
export const convertKorToEng = (korString: string) => {
  const ret: string[] = [];
  const decomposedKorCharList = korString.normalize("NFKD").split("");
  const korCharList: string[] = [];
  decomposedKorCharList.forEach((decomposedChar) => {
    /**
     * 복합 문자열 변환 처리
     */
    if (decomposedChar in decomposedVowels) {
      decomposedVowels[decomposedChar].split("").forEach((char) => {
        korCharList.push(char);
      });
    } else {
      korCharList.push(decomposedChar);
    }
  });
  for (let i = 0; i < korCharList.length; i += 1) {
    const currKorChar = korCharList[i];
    if (currKorChar in qwertyEngKeys) {
      /**
       * 쿼티 매핑
       * QWERTY mapping
       */
      ret.push(qwertyEngKeys[currKorChar]);
    } else if (currKorChar in initialConsonants) {
      /**
       * 초성 처리
       * Initial consonants
       */
      const key = initialConsonants[currKorChar];
      ret.push(qwertyEngKeys[key]);
    } else if (currKorChar in decomposedFinalConsonants) {
      /**
       * 복합 종성 처리
       * Composed final consonants
       */
      const [firstChar, secondChar] =
        decomposedFinalConsonants[currKorChar].split("");
      ret.push(qwertyEngKeys[firstChar]);
      ret.push(qwertyEngKeys[secondChar]);
    } else if (currKorChar in decomposedVowels) {
      /**
       * 복합 모음 처리
       * Composed vowels
       */
      const [firstChar, secondChar] = decomposedVowels[currKorChar].split("");
      ret.push(qwertyEngKeys[firstChar]);
      ret.push(qwertyEngKeys[secondChar]);
    } else {
      /**
       * 그 외의 경우
       * Other cases
       */
      ret.push(currKorChar);
    }
  }
  /**
   * 완성형 문자열로 변환
   * Convert to composed string
   */
  const outputString = ret.join("").normalize("NFC");
  /**
   * 완성형 문자중 누락된 한글 자음 또는 모음을 원래 문자열로 매핑
   * Map missing Korean consonants or vowels in composed string to original string
   */
  return [...outputString]
    .map((char) => unicodeKoreanChars[char] || char)
    .join("");
};
