// 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 dotted_circle_codepoint : UInt = 0x25CCU

fn insert_dotted_circle_for_syllables(
  font : @font.Font,
  buffer : Buffer,
  syllables : Array[Int],
  broken_type : Int,
) -> Result[Bool, ShapeError] {
  if buffer.infos.length() != syllables.length() || buffer.infos.is_empty() {
    return Ok(false)
  }
  let has_dotted_circle = match font.glyph_for_codepoint(dotted_circle_codepoint) {
    Err(err) => return Err(Font(err))
    Ok(None) => false
    Ok(Some(_)) => true
  }
  if !has_dotted_circle {
    return Ok(false)
  }
  let next_infos : Array[GlyphInfo] = []
  let next_positions : Array[GlyphPosition] = []
  let mut inserted = false
  let mut last_syllable = -1
  for i in 0.. Result[Bool, ShapeError] {
  if buffer.infos.length() != syllables.length() || buffer.infos.is_empty() ||
    categories.length() != buffer.infos.length() {
    return Ok(false)
  }
  let has_dotted_circle = match font.glyph_for_codepoint(dotted_circle_codepoint) {
    Err(err) => return Err(Font(err))
    Ok(None) => false
    Ok(Some(_)) => true
  }
  if !has_dotted_circle {
    return Ok(false)
  }
  let next_infos : Array[GlyphInfo] = []
  let next_positions : Array[GlyphPosition] = []
  let mut inserted = false
  let mut last_syllable = -1
  let mut i = 0
  while i < buffer.infos.length() {
    let syllable = syllables[i]
    if syllable != last_syllable && (syllable & 0x0f) == broken_type {
      inserted = true
      last_syllable = syllable
      let mut j = i
      while j < buffer.infos.length() &&
        syllables[j] == syllable &&
        categories[j] == repha_category {
        next_infos.push(buffer.infos[j])
        next_positions.push(buffer.positions[j])
        j = j + 1
      }
      let cluster = if j < buffer.infos.length() && syllables[j] == syllable {
        buffer.infos[j].cluster
      } else {
        buffer.infos[i].cluster
      }
      next_infos.push(GlyphInfo::{ codepoint: dotted_circle_codepoint, cluster, unicode: dotted_circle_codepoint })
      next_positions.push(GlyphPosition::{ x_advance: 0, y_advance: 0, x_offset: 0, y_offset: 0 })
      while j < buffer.infos.length() && syllables[j] == syllable {
        next_infos.push(buffer.infos[j])
        next_positions.push(buffer.positions[j])
        j = j + 1
      }
      i = j
      continue
    }
    next_infos.push(buffer.infos[i])
    next_positions.push(buffer.positions[i])
    last_syllable = syllable
    i = i + 1
  }
  if inserted {
    buffer.infos = next_infos
    buffer.positions = next_positions
  }
  Ok(inserted)
}

///|
fn apply_gsub_features_for_syllables(
  font : @font.Font,
  buffer : Buffer,
  gsub : @ot_tables.GsubTable,
  feature_tags : Array[@common.Tag],
  syllables : Array[Int],
  glyph_masks? : Array[UInt] = [],
  script_tag? : @common.Tag = buffer.script.to_tag(),
) -> Result[Unit, ShapeError] {
  if syllables.is_empty() || syllables.length() != buffer.infos.length() {
    return Ok(())
  }
  let mut use_masks = !glyph_masks.is_empty()
  if use_masks && glyph_masks.length() != buffer.infos.length() {
    use_masks = false
  }
  let gdef = match font.gdef() {
    Err(err) => return Err(Font(err))
    Ok(value) => value
  }
  let script_tag = script_tag
  let lang_tag = language_to_ot_tag(buffer.language)
  let feature_lookups : Array[Array[Int]] = []
  for feature_tag in feature_tags {
    let lookup_offsets = match gsub.layout().select_lookup_offsets_for_feature(
      script_tag,
      feature_tag,
      language_tag=lang_tag,
      include_required=false,
    ) {
      Err(err) => return Err(Layout(err))
      Ok(value) => value
    }
    feature_lookups.push(lookup_offsets)
  }
  let mut has_lookups = false
  for lookup_offsets in feature_lookups {
    if !lookup_offsets.is_empty() {
      has_lookups = true
      break
    }
  }
  if !has_lookups {
    return Ok(())
  }
  let mut current_glyphs : Array[UInt] = []
  let mut current_clusters : Array[Int] = []
  let mut current_unicodes : Array[UInt] = []
  let mut current_masks : Array[UInt] = []
  for info in buffer.infos {
    current_glyphs.push(info.codepoint)
    current_clusters.push(info.cluster)
    current_unicodes.push(info.unicode)
  }
  if use_masks {
    for value in glyph_masks {
      current_masks.push(value)
    }
  }
  let mut changed = false
  let mut delta = 0
  let mut i = 0
  while i < syllables.length() {
    let syllable = syllables[i]
    let mut j = i + 1
    while j < syllables.length() && syllables[j] == syllable {
      j = j + 1
    }
    let start = i + delta
    let end = j + delta
    if start < 0 || end > current_glyphs.length() || start >= end {
      i = j
      continue
    }
    let mut sub_glyphs : Array[UInt] = []
    let mut sub_clusters : Array[Int] = []
    let mut sub_masks : Array[UInt] = []
    let sub_unicodes : Array[UInt] = []
    for k in start..
          match gsub.apply_with_lookups_and_masks(
            sub_glyphs,
            sub_clusters,
            lookup_offsets[:],
            masks=sub_masks,
            lookup_mask=lookup_mask,
          ) {
            Err(err) => return Err(Layout(err))
            Ok(value) => value
          }
        Some(gdef) =>
          match gsub.apply_with_lookups_and_masks(
            sub_glyphs,
            sub_clusters,
            lookup_offsets[:],
            gdef=gdef,
            masks=sub_masks,
            lookup_mask=lookup_mask,
          ) {
            Err(err) => return Err(Layout(err))
            Ok(value) => value
          }
      }
      let (next_glyphs, next_clusters, next_masks, did_change) = result
      sub_glyphs = next_glyphs
      sub_clusters = next_clusters
      if use_masks {
        sub_masks = next_masks
      }
      if did_change {
        sub_changed = true
      }
    }
    let old_len = end - start
    let new_len = sub_glyphs.length()
    if sub_changed || new_len != old_len {
      let next_glyphs : Array[UInt] = []
      let next_clusters : Array[Int] = []
      let next_unicodes : Array[UInt] = []
      let next_masks : Array[UInt] = []
      for k in 0.. return Err(err)
        Ok(value) => value
      }
      let unicode = if current_unicodes.length() == current_glyphs.length() {
        current_unicodes[idx]
      } else {
        0U
      }
      infos.push(GlyphInfo::{ codepoint: glyph, cluster: current_clusters[idx], unicode })
      positions.push(pos)
    }
    buffer.infos = infos
    buffer.positions = positions
  }
  Ok(())
}