/* Clavier hangeul virtuel partage.
   Il compose les syllabes en fin de champ : ㅎ + ㅏ + ㄴ => 한. */

const KO_INITIALS = ["ㄱ","ㄲ","ㄴ","ㄷ","ㄸ","ㄹ","ㅁ","ㅂ","ㅃ","ㅅ","ㅆ","ㅇ","ㅈ","ㅉ","ㅊ","ㅋ","ㅌ","ㅍ","ㅎ"];
const KO_VOWELS = ["ㅏ","ㅐ","ㅑ","ㅒ","ㅓ","ㅔ","ㅕ","ㅖ","ㅗ","ㅘ","ㅙ","ㅚ","ㅛ","ㅜ","ㅝ","ㅞ","ㅟ","ㅠ","ㅡ","ㅢ","ㅣ"];
const KO_FINALS = ["","ㄱ","ㄲ","ㄳ","ㄴ","ㄵ","ㄶ","ㄷ","ㄹ","ㄺ","ㄻ","ㄼ","ㄽ","ㄾ","ㄿ","ㅀ","ㅁ","ㅂ","ㅄ","ㅅ","ㅆ","ㅇ","ㅈ","ㅊ","ㅋ","ㅌ","ㅍ","ㅎ"];
const KO_INITIAL_INDEX = Object.fromEntries(KO_INITIALS.map((j, i) => [j, i]));
const KO_VOWEL_INDEX = Object.fromEntries(KO_VOWELS.map((j, i) => [j, i]));
const KO_FINAL_INDEX = Object.fromEntries(KO_FINALS.map((j, i) => [j, i]));
const KO_FINAL_FROM_CONSONANT = Object.fromEntries(KO_FINALS.filter(Boolean).map((j, i) => [j, i + 1]));
const KO_VOWEL_COMBOS = { "ㅗㅏ":"ㅘ", "ㅗㅐ":"ㅙ", "ㅗㅣ":"ㅚ", "ㅜㅓ":"ㅝ", "ㅜㅔ":"ㅞ", "ㅜㅣ":"ㅟ", "ㅡㅣ":"ㅢ" };
const KO_FINAL_COMBOS = { "ㄱㅅ":"ㄳ", "ㄴㅈ":"ㄵ", "ㄴㅎ":"ㄶ", "ㄹㄱ":"ㄺ", "ㄹㅁ":"ㄻ", "ㄹㅂ":"ㄼ", "ㄹㅅ":"ㄽ", "ㄹㅌ":"ㄾ", "ㄹㅍ":"ㄿ", "ㄹㅎ":"ㅀ", "ㅂㅅ":"ㅄ" };
const KO_FINAL_SPLIT = { "ㄳ":["ㄱ","ㅅ"], "ㄵ":["ㄴ","ㅈ"], "ㄶ":["ㄴ","ㅎ"], "ㄺ":["ㄹ","ㄱ"], "ㄻ":["ㄹ","ㅁ"], "ㄼ":["ㄹ","ㅂ"], "ㄽ":["ㄹ","ㅅ"], "ㄾ":["ㄹ","ㅌ"], "ㄿ":["ㄹ","ㅍ"], "ㅀ":["ㄹ","ㅎ"], "ㅄ":["ㅂ","ㅅ"] };
const KO_KEY_ROWS = [
  ["ㅂ","ㅈ","ㄷ","ㄱ","ㅅ","ㅛ","ㅕ","ㅑ","ㅐ","ㅔ"],
  ["ㅁ","ㄴ","ㅇ","ㄹ","ㅎ","ㅗ","ㅓ","ㅏ","ㅣ"],
  ["ㅋ","ㅌ","ㅊ","ㅍ","ㅠ","ㅜ","ㅡ","ㄲ","ㄸ","ㅃ","ㅆ","ㅉ"],
];

function koCharAtEnd(value) {
  const chars = Array.from(String(value || ""));
  return { chars, last: chars[chars.length - 1] || "" };
}

function koSyllableParts(char) {
  const code = char.charCodeAt(0);
  if (code < 0xac00 || code > 0xd7a3) return null;
  const n = code - 0xac00;
  const l = Math.floor(n / 588);
  const v = Math.floor((n % 588) / 28);
  const t = n % 28;
  return { l, v, t, initial: KO_INITIALS[l], vowel: KO_VOWELS[v], final: KO_FINALS[t] };
}

function koCompose(l, v, t) {
  return String.fromCharCode(0xac00 + (l * 588) + (v * 28) + (t || 0));
}

function koReplaceLast(value, char) {
  const { chars } = koCharAtEnd(value);
  chars[chars.length - 1] = char;
  return chars.join("");
}

function koAppendJamo(value, jamo) {
  value = String(value || "");
  const { chars, last } = koCharAtEnd(value);
  if (!last) return jamo;

  const parts = koSyllableParts(last);
  const isConsonant = KO_INITIAL_INDEX[jamo] != null;
  const isVowel = KO_VOWEL_INDEX[jamo] != null;

  if (isVowel) {
    if (KO_INITIAL_INDEX[last] != null) {
      return koReplaceLast(value, koCompose(KO_INITIAL_INDEX[last], KO_VOWEL_INDEX[jamo], 0));
    }
    if (KO_VOWEL_INDEX[last] != null) {
      const combo = KO_VOWEL_COMBOS[last + jamo];
      return combo ? koReplaceLast(value, combo) : value + jamo;
    }
    if (parts && !parts.t) {
      const combo = KO_VOWEL_COMBOS[parts.vowel + jamo];
      return combo ? koReplaceLast(value, koCompose(parts.l, KO_VOWEL_INDEX[combo], 0)) : value + jamo;
    }
    if (parts && parts.t) {
      const split = KO_FINAL_SPLIT[parts.final];
      if (split) {
        const [remain, carry] = split;
        chars[chars.length - 1] = koCompose(parts.l, parts.v, KO_FINAL_INDEX[remain] || 0);
        return chars.join("") + koCompose(KO_INITIAL_INDEX[carry], KO_VOWEL_INDEX[jamo], 0);
      }
      if (KO_INITIAL_INDEX[parts.final] != null) {
        chars[chars.length - 1] = koCompose(parts.l, parts.v, 0);
        return chars.join("") + koCompose(KO_INITIAL_INDEX[parts.final], KO_VOWEL_INDEX[jamo], 0);
      }
    }
    return value + jamo;
  }

  if (isConsonant) {
    if (parts && !parts.t && KO_FINAL_FROM_CONSONANT[jamo]) {
      return koReplaceLast(value, koCompose(parts.l, parts.v, KO_FINAL_FROM_CONSONANT[jamo]));
    }
    if (parts && parts.t) {
      const combo = KO_FINAL_COMBOS[parts.final + jamo];
      if (combo) return koReplaceLast(value, koCompose(parts.l, parts.v, KO_FINAL_INDEX[combo]));
    }
  }
  return value + jamo;
}

function koBackspace(value) {
  value = String(value || "");
  const { chars, last } = koCharAtEnd(value);
  if (!last) return "";
  const parts = koSyllableParts(last);
  if (!parts) return chars.slice(0, -1).join("");
  if (parts.t) return koReplaceLast(value, koCompose(parts.l, parts.v, 0));
  return koReplaceLast(value, parts.initial);
}

function koIsJamo(value) {
  return KO_INITIAL_INDEX[value] != null || KO_VOWEL_INDEX[value] != null;
}

function KoreanKeyboard({ value, onChange, compact = false, label = "Clavier coréen" }) {
  const [open, setOpen] = useState(false);
  const [breakNext, setBreakNext] = useState(false);
  const emit = (next) => onChange && onChange(next);
  const press = (key) => {
    if (key === "space") {
      setBreakNext(false);
      emit(String(value || "") + " ");
    } else if (key === "period") {
      setBreakNext(false);
      emit(String(value || "") + ".");
    } else if (key === "question") {
      setBreakNext(false);
      emit(String(value || "") + "?");
    } else if (key === "comma") {
      setBreakNext(false);
      emit(String(value || "") + ",");
    } else if (key === "backspace") {
      setBreakNext(false);
      emit(koBackspace(value));
    } else if (breakNext && koIsJamo(key)) {
      setBreakNext(false);
      emit(String(value || "") + key);
    } else {
      setBreakNext(false);
      emit(koAppendJamo(value, key));
    }
  };

  return (
    <div className={`ko-keyboard ${compact ? "ko-keyboard-compact" : ""}`}>
      <button type="button" className="ko-keyboard-toggle" aria-expanded={open} onClick={() => setOpen(o => !o)}>
        <span className="kr">한</span>
        <span>{label}</span>
      </button>
      {open && (
        <div className="ko-keyboard-panel fade-up" aria-label={label}>
          {KO_KEY_ROWS.map((row, i) => (
            <div key={i} className="ko-keyboard-row">
              {row.map(key => (
                <button key={key} type="button" className="ko-key kr" onClick={() => press(key)}>{key}</button>
              ))}
            </div>
          ))}
          <div className="ko-keyboard-row ko-keyboard-actions">
            <button type="button" className={`ko-key ko-key-action ${breakNext ? "ko-key-action-on" : ""}`} aria-pressed={breakNext} onClick={() => setBreakNext(v => !v)}>Syllabe suivante</button>
            <button type="button" className="ko-key ko-key-action" onClick={() => press("space")}>Espace</button>
            <button type="button" className="ko-key ko-key-action" aria-label="Point" title="Point" onClick={() => press("period")}>.</button>
            <button type="button" className="ko-key ko-key-action" aria-label="Point d'interrogation" title="Point d'interrogation" onClick={() => press("question")}>?</button>
            <button type="button" className="ko-key ko-key-action" aria-label="Virgule" title="Virgule" onClick={() => press("comma")}>,</button>
            <button type="button" className="ko-key ko-key-action" onClick={() => press("backspace")}>Effacer</button>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { KoreanKeyboard, koAppendJamo, koBackspace });
