// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
let hangul_action_ljmo : Int = 0

///|
let hangul_action_vjmo : Int = 1

///|
let hangul_action_tjmo : Int = 2

///|
let hangul_action_none : Int = 3

///|
let hangul_feature_tags : Array[@common.Tag] = [
  @common.Tag::from_chars('l', 'j', 'm', 'o'),
  @common.Tag::from_chars('v', 'j', 'm', 'o'),
  @common.Tag::from_chars('t', 'j', 'm', 'o'),
]

///|
fn hangul_actions(buffer : Buffer) -> Array[Int] {
  let actions : Array[Int] = Array::make(buffer.infos.length(), hangul_action_none)
  for i in 0.. Array[UInt] {
  if !is_hangul_lv(u) {
    return [u]
  }
  match @unicode.decompose(u) {
    None => [u]
    Some((a, b)) => {
      if b == 0U {
        return [a]
      }
      if is_hangul_lv(a) {
        match @unicode.decompose(a) {
          None => [a, b]
          Some((l, v)) => {
            if v == 0U {
              [a, b]
            } else {
              [l, v, b]
            }
          }
        }
      } else {
        [a, b]
      }
    }
  }
}

///|
fn hangul_components_available(
  font : @font.Font,
  components : Array[UInt],
) -> Result[Bool, ShapeError] {
  for component in components {
    match font.glyph_for_codepoint(component) {
      Err(err) => return Err(Font(err))
      Ok(None) => return Ok(false)
      Ok(Some(_)) => ()
    }
  }
  Ok(true)
}

///|
fn preprocess_hangul_for_font(
  buffer : Buffer,
  font : @font.Font,
) -> Result[Unit, ShapeError] {
  if buffer.infos.is_empty() {
    return Ok(())
  }
  let mut changed = false
  let next_infos : Array[GlyphInfo] = []
  for info in buffer.infos {
    let codepoint = info.codepoint
    if is_hangul_lv(codepoint) {
      let has_glyph = match font.glyph_for_codepoint(codepoint) {
        Err(err) => return Err(Font(err))
        Ok(None) => false
        Ok(Some(_)) => true
      }
      if !has_glyph {
        let components = decompose_hangul_syllable(codepoint)
        if components.length() > 1 {
          match hangul_components_available(font, components) {
            Err(err) => return Err(err)
            Ok(false) => ()
            Ok(true) => {
              changed = true
              for component in components {
                next_infos.push(GlyphInfo::{ codepoint: component, cluster: info.cluster, unicode: component })
              }
              continue
            }
          }
        }
      }
    }
    next_infos.push(info)
  }
  if changed {
    ignore(buffer.replace_glyphs(next_infos))
  }
  Ok(())
}

///|
pub fn Buffer::shape_hangul(
  self : Buffer,
  font : @font.Font,
) -> Result[Unit, ShapeError] {
  match preprocess_hangul_for_font(self, font) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let (unicode_mark_flags, combining_classes) = collect_mark_fallback_data(self.infos)
  let actions = hangul_actions(self)
  match self.shape_basic(font) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let gsub = match font.gsub() {
    Err(err) => return Err(Font(err))
    Ok(value) => value
  }
  let mut current_glyphs : Array[UInt] = []
  let mut current_clusters : Array[Int] = []
  let mut current_unicodes : Array[UInt] = []
  for info in self.infos {
    current_glyphs.push(info.codepoint)
    current_clusters.push(info.cluster)
    current_unicodes.push(info.unicode)
  }
  let mut changed = false
  if gsub is Some(gsub) {
    let gdef = match font.gdef() {
      Err(err) => return Err(Font(err))
      Ok(value) => value
    }
    let script_tag = self.script.to_tag()
    let lang_tag = language_to_ot_tag(self.language)
    for feature_index in 0.. return Err(Layout(err))
        Ok(value) => value
      }
      if lookup_offsets.is_empty() {
        continue
      }
      let masks = build_action_mask(actions, feature_index)
      let result = match gdef {
        None =>
          match gsub.apply_with_lookups(
            current_glyphs,
            current_clusters,
            lookup_offsets[:],
            masks=masks,
            lookup_mask=1U,
          ) {
            Err(err) => return Err(Layout(err))
            Ok(value) => value
          }
        Some(gdef) =>
          match gsub.apply_with_lookups(
            current_glyphs,
            current_clusters,
            lookup_offsets[:],
            gdef=gdef,
            masks=masks,
            lookup_mask=1U,
          ) {
            Err(err) => return Err(Layout(err))
            Ok(value) => value
          }
      }
      let (next_glyphs, next_clusters, did_change) = result
      if next_glyphs.length() != current_unicodes.length() {
        current_unicodes = Array::make(next_glyphs.length(), 0U)
      }
      current_glyphs = next_glyphs
      current_clusters = next_clusters
      if did_change {
        changed = true
      }
    }
  }
  if changed || current_glyphs.length() != self.infos.length() {
    let infos : Array[GlyphInfo] = []
    let positions : Array[GlyphPosition] = []
    for i in 0.. return Err(err)
        Ok(value) => value
      }
      let unicode = if current_unicodes.length() == current_glyphs.length() {
        current_unicodes[i]
      } else {
        0U
      }
      infos.push(GlyphInfo::{ codepoint: glyph, cluster: current_clusters[i], unicode })
      positions.push(pos)
    }
    self.infos = infos
    self.positions = positions
  }
  apply_gpos(
    font,
    self,
    zero_width_marks=ZeroWidthMarks::Disabled,
    fallback_position=true,
    unicode_mark_flags=unicode_mark_flags,
    combining_classes=combining_classes,
  )
}