// 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 arabic_action_isol : Int = 0

///|
let arabic_action_fina : Int = 1

///|
let arabic_action_fin2 : Int = 2

///|
let arabic_action_fin3 : Int = 3

///|
let arabic_action_medi : Int = 4

///|
let arabic_action_med2 : Int = 5

///|
let arabic_action_init : Int = 6

///|
let arabic_action_none : Int = 7

///|
let arabic_feature_tags : Array[@common.Tag] = [
  @common.Tag::from_chars('i', 's', 'o', 'l'),
  @common.Tag::from_chars('f', 'i', 'n', 'a'),
  @common.Tag::from_chars('f', 'i', 'n', '2'),
  @common.Tag::from_chars('f', 'i', 'n', '3'),
  @common.Tag::from_chars('m', 'e', 'd', 'i'),
  @common.Tag::from_chars('m', 'e', 'd', '2'),
  @common.Tag::from_chars('i', 'n', 'i', 't'),
]

///|
priv struct ArabicStateEntry {
  prev_action : Int
  curr_action : Int
  next_state : Int
}

///|
let arabic_state_table : Array[Array[ArabicStateEntry]] = [
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 6 },
  ],
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_fin2, next_state: 5 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 6 },
  ],
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_init, curr_action: arabic_action_fina, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_init, curr_action: arabic_action_fina, next_state: 3 },
    ArabicStateEntry::{ prev_action: arabic_action_init, curr_action: arabic_action_fina, next_state: 4 },
    ArabicStateEntry::{ prev_action: arabic_action_init, curr_action: arabic_action_fina, next_state: 6 },
  ],
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_medi, curr_action: arabic_action_fina, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_medi, curr_action: arabic_action_fina, next_state: 3 },
    ArabicStateEntry::{ prev_action: arabic_action_medi, curr_action: arabic_action_fina, next_state: 4 },
    ArabicStateEntry::{ prev_action: arabic_action_medi, curr_action: arabic_action_fina, next_state: 6 },
  ],
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_med2, curr_action: arabic_action_isol, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_med2, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_med2, curr_action: arabic_action_fin2, next_state: 5 },
    ArabicStateEntry::{ prev_action: arabic_action_med2, curr_action: arabic_action_isol, next_state: 6 },
  ],
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_isol, curr_action: arabic_action_isol, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_isol, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_isol, curr_action: arabic_action_fin2, next_state: 5 },
    ArabicStateEntry::{ prev_action: arabic_action_isol, curr_action: arabic_action_isol, next_state: 6 },
  ],
  [
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_none, next_state: 0 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 1 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 2 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_fin3, next_state: 5 },
    ArabicStateEntry::{ prev_action: arabic_action_none, curr_action: arabic_action_isol, next_state: 6 },
  ],
]

///|
fn arabic_joining_actions(buffer : Buffer) -> Array[Int] {
  let infos = buffer.infos
  let actions : Array[Int] = Array::make(infos.length(), arabic_action_none)
  let mut state = 0
  let mut prev = -1
  for i in 0..= 6 {
      actions[i] = arabic_action_none
      continue
    }
    let entry = arabic_state_table[state][col]
    if entry.prev_action != arabic_action_none && prev >= 0 {
      actions[prev] = entry.prev_action
    }
    actions[i] = entry.curr_action
    prev = i
    state = entry.next_state
  }
  actions
}

///|
fn is_mongolian_variation_selector(u : UInt) -> Bool {
  (u >= 0x180BU && u <= 0x180DU) || u == 0x180FU
}

///|
fn apply_mongolian_variation_selectors(
  actions : Array[Int],
  infos : Array[GlyphInfo],
) -> Unit {
  if actions.length() != infos.length() {
    return ()
  }
  if infos.length() <= 1 {
    return ()
  }
  for i in 1.. Array[UInt] {
  let masks : Array[UInt] = Array::make(actions.length(), 0U)
  for i in 0.. Int? {
  if action == arabic_action_init {
    return Some(0)
  }
  if action == arabic_action_medi {
    return Some(1)
  }
  if action == arabic_action_fina || action == arabic_action_fin2 || action == arabic_action_fin3 {
    return Some(2)
  }
  if action == arabic_action_isol {
    return Some(3)
  }
  None
}

///|
fn apply_arabic_fallback(buffer : Buffer, actions : Array[Int]) -> Unit {
  if actions.length() != buffer.infos.length() {
    return ()
  }
  for i in 0.. ()
      Some(column) => {
        let codepoint = buffer.infos[i].codepoint
        match @unicode.arabic_shaping_form(codepoint, column) {
          None => ()
          Some(value) => {
            let info = buffer.infos[i]
            buffer.infos[i] = GlyphInfo::{ codepoint: value, cluster: info.cluster, unicode: value }
          }
        }
      }
    }
  }
}

///|
fn apply_gpos(
  font : @font.Font,
  buffer : Buffer,
  zero_width_marks? : ZeroWidthMarks = ZeroWidthMarks::Disabled,
  fallback_position? : Bool = false,
  unicode_mark_flags? : Array[Bool] = [],
  combining_classes? : Array[Int] = [],
  gpos_tag? : @common.Tag? = None,
) -> Result[Unit, ShapeError] {
  let gpos = match font.gpos() {
    Err(err) => return Err(Font(err))
    Ok(value) => value
  }
  let mut gpos_to_use : @ot_tables.GposTable? = None
  match gpos {
    None => ()
    Some(gpos) => {
      match gpos_tag {
        None => {
          gpos_to_use = Some(gpos)
        }
        Some(tag) => {
          if buffer_layout_has_script(gpos.layout(), tag) {
            gpos_to_use = Some(gpos)
          }
        }
      }
    }
  }
  let mut gpos_lookups : Array[Int] = []
  if gpos_to_use is Some(gpos) {
    let script_tag = match gpos_tag {
      None => buffer.script.to_tag()
      Some(tag) => tag
    }
    let lang_tag = language_to_ot_tag(buffer.language)
    gpos_lookups = match gpos.layout().select_lookup_offsets_with_features(
      script_tag,
      language_tag=lang_tag,
      feature_tags=buffer.gpos_features,
    ) {
      Err(err) => return Err(Layout(err))
      Ok(value) => value
    }
  }
  let gpos_present = gpos_to_use is Some(_) && !gpos_lookups.is_empty()
  let kern_tag =
    if buffer.direction.is_horizontal() {
      @common.Tag::from_chars('k', 'e', 'r', 'n')
    } else {
      @common.Tag::from_chars('v', 'k', 'r', 'n')
    }
  let mut wants_kern = false
  for entry in buffer.gpos_features {
    if entry == kern_tag {
      wants_kern = true
      break
    }
  }
  let mut has_gpos_kern = false
  if wants_kern && gpos_to_use is Some(gpos) {
    let kern_lookups = match gpos.layout().select_lookup_offsets_for_feature(
      match gpos_tag {
        None => buffer.script.to_tag()
        Some(tag) => tag
      },
      kern_tag,
      language_tag=language_to_ot_tag(buffer.language),
      include_required=false,
    ) {
      Err(err) => return Err(Layout(err))
      Ok(value) => value
    }
    has_gpos_kern = !kern_lookups.is_empty()
  }
  match zero_width_marks {
    ZeroWidthMarks::ByGdefEarly =>
      match zero_marks_by_gdef_or_unicode(font, buffer, unicode_mark_flags, gpos_present) {
        Err(err) => return Err(err)
        Ok(_) => ()
      }
    _ => ()
  }
  if gpos_present && gpos_to_use is Some(gpos) {
    let gdef = match font.gdef() {
      Err(err) => return Err(Font(err))
      Ok(value) => value
    }
    let glyphs : Array[UInt] = []
    let clusters : Array[Int] = []
    for info in buffer.infos {
      glyphs.push(info.codepoint)
      clusters.push(info.cluster)
    }
    let advances_x : Array[Int] = []
    let advances_y : Array[Int] = []
    for pos in buffer.positions {
      advances_x.push(pos.x_advance)
      advances_y.push(pos.y_advance)
    }
    let deltas = match gdef {
      None =>
        match gpos.position_deltas_with_lookups(
          glyphs[:],
          gpos_lookups[:],
          clusters=clusters[:],
          advances_x=advances_x[:],
          advances_y=advances_y[:],
        ) {
          Err(err) => return Err(Layout(err))
          Ok(value) => value
        }
      Some(gdef) =>
        match gpos.position_deltas_with_lookups(
          glyphs[:],
          gpos_lookups[:],
          gdef=gdef,
          clusters=clusters[:],
          advances_x=advances_x[:],
          advances_y=advances_y[:],
        ) {
          Err(err) => return Err(Layout(err))
          Ok(value) => value
        }
    }
    let new_positions : Array[GlyphPosition] = []
    for i in 0.. return Err(Font(err))
      Ok(value) => value
    }
    if kern is Some(kern) {
      let new_positions : Array[GlyphPosition] = []
      for pos in buffer.positions {
        new_positions.push(pos)
      }
      if new_positions.length() >= 2 {
        for i in 0..<(new_positions.length() - 1) {
          let left = buffer.infos[i].codepoint
          let right = buffer.infos[i + 1].codepoint
          match kern.kern_value(left, right) {
            None => ()
            Some(value) => {
              let pos = new_positions[i]
              new_positions[i] = GlyphPosition::{
                x_advance: pos.x_advance + value,
                y_advance: pos.y_advance,
                x_offset: pos.x_offset,
                y_offset: pos.y_offset,
              }
            }
          }
        }
      }
      buffer.positions = new_positions
    }
  }
  match zero_width_marks {
    ZeroWidthMarks::ByGdefLate =>
      match zero_marks_by_gdef_or_unicode(font, buffer, unicode_mark_flags, gpos_present) {
        Err(err) => return Err(err)
        Ok(_) => ()
      }
    _ => ()
  }
  if fallback_position && !gpos_present {
    match fallback_mark_position(font, buffer, unicode_mark_flags, combining_classes) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
  }
  finalize_default_ignorables_and_variation_selectors(buffer)
  Ok(())
}

///|
pub fn Buffer::shape_arabic(
  self : Buffer,
  font : @font.Font,
) -> Result[Unit, ShapeError] {
  let actions = arabic_joining_actions(self)
  if self.script == @common.script_mongolian {
    apply_mongolian_variation_selectors(actions, self.infos)
  }
  let gsub = match font.gsub() {
    Err(err) => return Err(Font(err))
    Ok(value) => value
  }
  if self.script == @common.script_syriac {
    let mut has_syrc = false
    if gsub is Some(gsub) {
      let script_tag = self.script.to_tag()
      if buffer_layout_has_script(gsub.layout(), script_tag) {
        has_syrc = true
      }
    }
    if !has_syrc {
      return self.shape_ot(font)
    }
  }
  let (unicode_mark_flags, combining_classes) = collect_mark_fallback_data(self.infos)
  if actions.is_empty() {
    match self.shape_basic(font) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
    return apply_gpos(
      font,
      self,
      zero_width_marks=ZeroWidthMarks::ByGdefLate,
      fallback_position=true,
      unicode_mark_flags=unicode_mark_flags,
      combining_classes=combining_classes,
    )
  }
  let mut current_glyphs : Array[UInt] = []
  let mut current_clusters : Array[Int] = []
  let mut current_unicodes : Array[UInt] = []
  let mut changed = false
  let mut has_gsub_features = false
  if gsub is Some(gsub) {
    let script_tag = self.script.to_tag()
    let lang_tag = language_to_ot_tag(self.language)
    for feature_tag in arabic_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
      }
      if !lookup_offsets.is_empty() {
        has_gsub_features = true
        break
      }
    }
  }
  if !has_gsub_features {
    if self.script == @common.script_arabic {
      apply_arabic_fallback(self, actions)
    }
    match self.shape_basic(font) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
    return apply_gpos(
      font,
      self,
      zero_width_marks=ZeroWidthMarks::ByGdefLate,
      fallback_position=true,
      unicode_mark_flags=unicode_mark_flags,
      combining_classes=combining_classes,
    )
  }
  if gsub is Some(gsub) {
    match self.shape_basic(font) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
    for info in self.infos {
      current_glyphs.push(info.codepoint)
      current_clusters.push(info.cluster)
      current_unicodes.push(info.unicode)
    }
    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::ByGdefLate,
    fallback_position=true,
    unicode_mark_flags=unicode_mark_flags,
    combining_classes=combining_classes,
  )
}