// Copyright 2025 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.

///|
/// Edge grid-fitting for the autohinter.
///
/// Ported from `fontations/skrifa/src/outline/autohint/hint/edges.rs`
/// (Apache-2.0 OR MIT).

///|
fn autohint_snap_width(widths : Array[AutoHintScaledWidth], width : Int) -> Int {
  let mut best_dist = 64 + 32 + 2
  let mut ref_width = width
  for candidate in widths.iter() {
    let dist = (width - candidate.scaled).abs()
    if dist < best_dist {
      best_dist = dist
      ref_width = candidate.scaled
    }
  }
  let scaled = autohint_pix_round(ref_width)
  if width >= ref_width {
    if width < scaled + 48 {
      ref_width
    } else {
      width
    }
  } else if width > scaled - 48 {
    ref_width
  } else {
    width
  }
}

///|
fn autohint_stem_width(
  metrics : AutoHintScaledAxisMetrics,
  group : AutoHintScriptGroup,
  scale : AutoHintScale,
  width : Int,
  base_delta : Int,
  base_flags : Int,
  stem_flags : Int,
) -> Int {
  if (scale.flags & AUTOHINT_SCALE_STEM_ADJUST) == 0 ||
    (group is Default && metrics.width_metrics.is_extra_light) {
    return width
  }
  let is_vertical = metrics.dim == AUTOHINT_DIM_VERTICAL
  let sign = if width < 0 { -1 } else { 1 }
  let mut dist = width.abs()
  let do_smooth = (
      is_vertical && (scale.flags & AUTOHINT_SCALE_VERTICAL_SNAP) == 0
    ) ||
    (!is_vertical && (scale.flags & AUTOHINT_SCALE_HORIZONTAL_SNAP) == 0)
  if do_smooth {
    if group is Default {
      if (stem_flags & AUTOHINT_FLAG_SERIF) != 0 && is_vertical && dist < 3 * 64 {
        return dist * sign
      } else if (base_flags & AUTOHINT_FLAG_ROUND) != 0 {
        if dist < 80 {
          dist = 64
        }
      } else if dist < 56 {
        dist = 56
      }
    }
    if !metrics.widths.is_empty() {
      let min_width = metrics.widths.at(0).scaled
      let delta = (dist - min_width).abs()
      if delta < 40 {
        dist = min_width.max(48)
        return dist * sign
      }
      if group is Default {
        if dist < 3 * 64 {
          let delta = dist & 63
          dist = dist & -64
          if delta < 10 {
            dist = dist + delta
          } else if delta < 32 {
            dist = dist + 10
          } else if delta < 54 {
            dist = dist + 54
          } else {
            dist = dist + delta
          }
        } else {
          let mut new_base_delta = 0
          if (width > 0 && base_delta > 0) || (width < 0 && base_delta < 0) {
            if scale.size < 10.0 {
              new_base_delta = base_delta
            } else if scale.size < 30.0 {
              new_base_delta = base_delta * (30.0 - scale.size).to_int() / 20
            }
          }
          dist = (dist - new_base_delta.abs() + 32) & -64
        }
      } else if dist < 54 {
        dist = dist + (54 - dist) / 2
      } else if dist < 3 * 64 {
        let delta = dist & 63
        dist = dist & -64
        if delta < 10 {
          dist = dist + delta
        } else if delta < 22 {
          dist = dist + 10
        } else if delta < 42 {
          dist = dist + delta
        } else if delta < 54 {
          dist = dist + 54
        } else {
          dist = dist + delta
        }
      }
    }
  } else {
    let original_dist = dist
    dist = autohint_snap_width(metrics.widths, dist)
    if is_vertical {
      if dist >= 64 {
        dist = (dist + 16) & -64
      } else {
        dist = 64
      }
    } else if (scale.flags & AUTOHINT_SCALE_MONO) != 0 {
      if dist < 64 {
        dist = 64
      } else {
        dist = (dist + 32) & -64
      }
    } else if dist < 48 {
      dist = (dist + 64) >> 1
    } else if dist < 128 {
      dist = (dist + 22) & -64
      if group is Default {
        let delta = (dist - original_dist).abs()
        if delta >= 16 {
          dist = original_dist
          if dist < 48 {
            dist = (dist + 64) >> 1
          }
        }
      }
    } else {
      dist = (dist + 32) & -64
    }
  }
  dist * sign
}

///|
fn autohint_align_linked_edge(
  axis : AutoHintAxis,
  metrics : AutoHintScaledAxisMetrics,
  group : AutoHintScriptGroup,
  scale : AutoHintScale,
  base_edge_ix : Int,
  stem_edge_ix : Int,
) -> Unit {
  let base = axis.edges.at(base_edge_ix)
  let stem = axis.edges.at(stem_edge_ix)
  let width = stem.opos - base.opos
  let base_delta = base.pos - base.opos
  let fitted = autohint_stem_width(
    metrics,
    group,
    scale,
    width,
    base_delta,
    base.flags,
    stem.flags,
  )
  axis.edges.at(stem_edge_ix).pos = base.pos + fitted
}

///|
fn autohint_align_serif_edge(
  axis : AutoHintAxis,
  base_edge_ix : Int,
  serif_edge_ix : Int,
) -> Unit {
  let base = axis.edges.at(base_edge_ix)
  let serif = axis.edges.at(serif_edge_ix)
  axis.edges.at(serif_edge_ix).pos = base.pos + (serif.opos - base.opos)
}

///|
const AUTOHINT_CJK_MAX_HORIZONTAL_GAP : Int = 9

///|
const AUTOHINT_CJK_MAX_VERTICAL_GAP : Int = 15

///|
const AUTOHINT_CJK_MAX_DELTA_ABS : Int = 14

///|
fn autohint_apply_stem_adjustment_cjk(
  axis : AutoHintAxis,
  edge_ix : Int,
  edge2_ix : Int,
  edge : AutoHintEdge,
  edge2 : AutoHintEdge,
  cur_pos1 : Int,
  cur_len : Int,
  delta : Int,
  do_stem_adjust : Bool,
) -> Int {
  let mut delta_adj = delta
  if !do_stem_adjust {
    if delta_adj < -AUTOHINT_CJK_MAX_DELTA_ABS {
      delta_adj = -AUTOHINT_CJK_MAX_DELTA_ABS
    } else if delta_adj > AUTOHINT_CJK_MAX_DELTA_ABS {
      delta_adj = AUTOHINT_CJK_MAX_DELTA_ABS
    }
  }
  let adjustment = cur_pos1 + delta_adj
  if edge.opos < edge2.opos {
    axis.edges.at(edge_ix).pos = adjustment
    axis.edges.at(edge2_ix).pos = adjustment + cur_len
  } else {
    axis.edges.at(edge2_ix).pos = adjustment
    axis.edges.at(edge_ix).pos = adjustment + cur_len
  }
  delta_adj
}

///|
fn autohint_hint_normal_stem_cjk(
  axis : AutoHintAxis,
  metrics : AutoHintScaledAxisMetrics,
  group : AutoHintScriptGroup,
  scale : AutoHintScale,
  edge_ix : Int,
  edge2_ix : Int,
  anchor : Int,
) -> Int {
  let edge = axis.edges.at(edge_ix)
  let edge2 = axis.edges.at(edge2_ix)
  let do_stem_adjust = (scale.flags & AUTOHINT_SCALE_STEM_ADJUST) != 0
  let threshold_delta = if do_stem_adjust {
    0
  } else {
    let delta = if axis.dim == AUTOHINT_DIM_VERTICAL {
      AUTOHINT_CJK_MAX_HORIZONTAL_GAP
    } else {
      AUTOHINT_CJK_MAX_VERTICAL_GAP
    }
    if (edge.flags & AUTOHINT_FLAG_ROUND) != 0 &&
      (edge2.flags & AUTOHINT_FLAG_ROUND) != 0 {
      delta
    } else {
      delta / 3
    }
  }
  let threshold = 64 - threshold_delta
  let original_len = edge2.opos - edge.opos
  let cur_len = autohint_stem_width(
    metrics,
    group,
    scale,
    original_len,
    0,
    edge.flags,
    edge2.flags,
  )
  let original_center = (edge.opos + edge2.opos) / 2 + anchor
  let cur_pos1 = original_center - cur_len / 2
  let cur_pos2 = cur_pos1 + cur_len
  let mut d_off1 = cur_pos1 - autohint_pix_floor(cur_pos1)
  let mut d_off2 = cur_pos2 - autohint_pix_floor(cur_pos2)
  if d_off1 == 0 || d_off2 == 0 {
    return autohint_apply_stem_adjustment_cjk(
      axis, edge_ix, edge2_ix, edge, edge2, cur_pos1, cur_len, 0, do_stem_adjust,
    )
  }
  let mut u_off1 = 64 - d_off1
  let mut u_off2 = 64 - d_off2
  if cur_len <= threshold {
    let delta = if d_off2 < cur_len {
      if u_off1 <= d_off2 {
        u_off1
      } else {
        -d_off2
      }
    } else {
      0
    }
    return autohint_apply_stem_adjustment_cjk(
      axis, edge_ix, edge2_ix, edge, edge2, cur_pos1, cur_len, delta, do_stem_adjust,
    )
  }
  if threshold < 64 &&
    (
      d_off1 >= threshold ||
      u_off1 >= threshold ||
      d_off2 >= threshold ||
      u_off2 >= threshold
    ) {
    return autohint_apply_stem_adjustment_cjk(
      axis, edge_ix, edge2_ix, edge, edge2, cur_pos1, cur_len, 0, do_stem_adjust,
    )
  }
  let mut offset = cur_len & 63
  if offset < 32 {
    if u_off1 <= offset || d_off2 <= offset {
      return autohint_apply_stem_adjustment_cjk(
        axis, edge_ix, edge2_ix, edge, edge2, cur_pos1, cur_len, 0, do_stem_adjust,
      )
    }
  } else {
    offset = 64 - threshold
  }
  d_off1 = threshold - u_off1
  u_off1 = u_off1 - offset
  u_off2 = threshold - d_off2
  d_off2 = d_off2 - offset
  if d_off1 <= u_off1 {
    u_off1 = -d_off1
  }
  if d_off2 <= u_off2 {
    u_off2 = -d_off2
  }
  let delta = if u_off1.abs() <= u_off2.abs() { u_off1 } else { u_off2 }
  autohint_apply_stem_adjustment_cjk(
    axis, edge_ix, edge2_ix, edge, edge2, cur_pos1, cur_len, delta, do_stem_adjust,
  )
}

///|
priv enum AutoHintLinkDir {
  Prev
  Next
}

///|
fn autohint_edge_link(
  edge : AutoHintEdge,
  edges : Array[AutoHintEdge],
) -> AutoHintEdge? {
  match edge.link_ix {
    None => None
    Some(ix) => edges.get(ix)
  }
}

///|
fn autohint_find_bounding_completed_edges(
  edges : Array[AutoHintEdge],
  ix : Int,
) -> (Int?, Int?) {
  let mut before : Int? = None
  if ix > 0 {
    let mut j = ix - 1
    while j >= 0 {
      if (edges.at(j).flags & AUTOHINT_FLAG_DONE) != 0 {
        before = Some(j)
        break
      }
      if j == 0 {
        break
      }
      j = j - 1
    }
  }
  let mut after : Int? = None
  for j in (ix + 1).. Unit {
  let edge = edges.at(edge_ix)
  let (edge2, prev_edge) = if link_dir is Next {
    if edge_ix + 1 >= edges.length() {
      return
    }
    let edge2 = edges.at(edge_ix + 1)
    if (edge2.flags & AUTOHINT_FLAG_DONE) == 0 {
      return
    }
    if edge_ix == 0 {
      return
    }
    (edge2, edges.at(edge_ix - 1))
  } else {
    if edge_ix == 0 {
      return
    }
    let e2 = edges.at(edge_ix - 1)
    (e2, e2)
  }
  let pos1 = edge.pos
  let pos2 = edge2.pos
  let order_check = match (link_dir, top_to_bottom_hinting) {
    (Prev, true) => pos1 > pos2
    (Next, false) => pos1 > pos2
    (Prev, false) => pos1 < pos2
    (Next, true) => pos1 < pos2
  }
  if !order_check {
    return
  }
  let link = match autohint_edge_link(edge, edges) {
    None => return
    Some(l) => l
  }
  if (link.pos - prev_edge.pos).abs() > 16 {
    edges.at(edge_ix).pos = edge2.pos
  }
}

///|
fn autohint_align_stem_edges(
  axis : AutoHintAxis,
  metrics : AutoHintScaledAxisMetrics,
  group : AutoHintScriptGroup,
  scale : AutoHintScale,
  top_to_bottom_hinting : Bool,
  anchor_ix : Int?,
) -> (Int, Int?) {
  let mut serif_count = 0
  let mut anchor_ix = anchor_ix
  let mut last_stem_pos : Int? = None
  let mut delta = 0
  for edge_ix in 0.. {
        serif_count = serif_count + 1
        continue
      }
      Some(ix) => ix
    }
    if edge2_ix < 0 || edge2_ix >= axis.edges.length() {
      serif_count = serif_count + 1
      continue
    }
    if !(group is Default) {
      match last_stem_pos {
        Some(last_pos) =>
          if edge.pos < last_pos + 64 ||
            axis.edges.at(edge2_ix).pos < last_pos + 64 {
            serif_count = serif_count + 1
            continue
          }
        None => ()
      }
    }
    if axis.edges.at(edge2_ix).blue_edge is Some(_) {
      axis.edges.at(edge2_ix).flags = axis.edges.at(edge2_ix).flags |
        AUTOHINT_FLAG_DONE
      autohint_align_linked_edge(axis, metrics, group, scale, edge2_ix, edge_ix)
      continue
    }
    if group is Default {
      match anchor_ix {
        Some(anchor_ix0) => {
          let anchor = axis.edges.at(anchor_ix0)
          let edge2 = axis.edges.at(edge2_ix)
          let original_pos = anchor.pos + (edge.opos - anchor.opos)
          let original_len = edge2.opos - edge.opos
          let original_center = original_pos + (original_len >> 1)
          let cur_len = autohint_stem_width(
            metrics,
            group,
            scale,
            original_len,
            0,
            edge.flags,
            edge2.flags,
          )
          if (edge2.flags & AUTOHINT_FLAG_DONE) != 0 {
            axis.edges.at(edge_ix).pos = edge2.pos - cur_len
          } else if cur_len < 96 {
            let cur_pos1 = autohint_pix_round(original_center)
            let (u_off, d_off) = if cur_len <= 64 { (32, 32) } else { (38, 26) }
            let delta1 = (original_center - (cur_pos1 - u_off)).abs()
            let delta2 = (original_center - (cur_pos1 + d_off)).abs()
            let cur_pos1 = if delta1 < delta2 {
              cur_pos1 - u_off
            } else {
              cur_pos1 + d_off
            }
            axis.edges.at(edge_ix).pos = cur_pos1 - cur_len / 2
            axis.edges.at(edge2_ix).pos = cur_pos1 + cur_len / 2
          } else {
            let cur_pos1 = autohint_pix_round(original_pos)
            let delta1 = (cur_pos1 + (cur_len >> 1) - original_center).abs()
            let cur_pos2 = autohint_pix_round(original_pos + original_len) -
              cur_len
            let delta2 = (cur_pos2 + (cur_len >> 1) - original_center).abs()
            let new_pos = if delta1 < delta2 { cur_pos1 } else { cur_pos2 }
            axis.edges.at(edge_ix).pos = new_pos
            axis.edges.at(edge2_ix).pos = new_pos + cur_len
          }
          axis.edges.at(edge_ix).flags = axis.edges.at(edge_ix).flags |
            AUTOHINT_FLAG_DONE
          axis.edges.at(edge2_ix).flags = axis.edges.at(edge2_ix).flags |
            AUTOHINT_FLAG_DONE
          if edge_ix > 0 {
            autohint_adjust_link(
              axis.edges,
              edge_ix,
              Prev,
              top_to_bottom_hinting,
            )
          }
        }
        None => {
          let edge2 = axis.edges.at(edge2_ix)
          let original_len = edge2.opos - edge.opos
          let cur_len = autohint_stem_width(
            metrics,
            group,
            scale,
            original_len,
            0,
            edge.flags,
            edge2.flags,
          )
          let (u_off, d_off) = if cur_len <= 64 { (32, 32) } else { (38, 26) }
          if cur_len < 96 {
            let original_center = edge.opos + (original_len >> 1)
            let mut cur_pos1 = autohint_pix_round(original_center)
            let error1 = (original_center - (cur_pos1 - u_off)).abs()
            let error2 = (original_center - (cur_pos1 + d_off)).abs()
            if error1 < error2 {
              cur_pos1 = cur_pos1 - u_off
            } else {
              cur_pos1 = cur_pos1 + d_off
            }
            let edge_pos = cur_pos1 - cur_len / 2
            axis.edges.at(edge_ix).pos = edge_pos
            axis.edges.at(edge2_ix).pos = edge_pos + cur_len
          } else {
            axis.edges.at(edge_ix).pos = autohint_pix_round(edge.opos)
          }
          axis.edges.at(edge_ix).flags = axis.edges.at(edge_ix).flags |
            AUTOHINT_FLAG_DONE
          autohint_align_linked_edge(
            axis, metrics, group, scale, edge_ix, edge2_ix,
          )
          axis.edges.at(edge2_ix).flags = axis.edges.at(edge2_ix).flags |
            AUTOHINT_FLAG_DONE
          anchor_ix = Some(edge_ix)
        }
      }
    } else {
      if edge2_ix < edge_ix {
        last_stem_pos = Some(edge.pos)
        axis.edges.at(edge_ix).flags = axis.edges.at(edge_ix).flags |
          AUTOHINT_FLAG_DONE
        autohint_align_linked_edge(
          axis, metrics, group, scale, edge2_ix, edge_ix,
        )
        continue
      }
      if axis.dim != AUTOHINT_DIM_VERTICAL && anchor_ix is None {
        delta = autohint_hint_normal_stem_cjk(
          axis, metrics, group, scale, edge_ix, edge2_ix, delta,
        )
      } else {
        autohint_hint_normal_stem_cjk(
          axis, metrics, group, scale, edge_ix, edge2_ix, delta,
        )
        |> ignore
      }
      anchor_ix = Some(edge_ix)
      axis.edges.at(edge_ix).flags = axis.edges.at(edge_ix).flags |
        AUTOHINT_FLAG_DONE
      axis.edges.at(edge2_ix).flags = axis.edges.at(edge2_ix).flags |
        AUTOHINT_FLAG_DONE
      last_stem_pos = Some(axis.edges.at(edge2_ix).pos)
    }
  }
  (serif_count, anchor_ix)
}

///|
fn autohint_hint_lowercase_m(
  edges : Array[AutoHintEdge],
  group : AutoHintScriptGroup,
) -> Unit {
  let (edge1_ix, edge2_ix, edge3_ix) = if edges.length() == 6 {
    (0, 2, 4)
  } else {
    (1, 5, 9)
  }
  let edge1 = edges.at(edge1_ix)
  let edge2 = edges.at(edge2_ix)
  let edge3 = edges.at(edge3_ix)
  let dist1 = edge2.opos - edge1.opos
  let dist2 = edge3.opos - edge2.opos
  let span = (dist1 - dist2).abs()
  if !(group is Default) {
    let ok1 = match edge1.link_ix {
      Some(ix) => ix == edge1_ix + 1
      _ => false
    }
    let ok2 = match edge2.link_ix {
      Some(ix) => ix == edge2_ix + 1
      _ => false
    }
    let ok3 = match edge3.link_ix {
      Some(ix) => ix == edge3_ix + 1
      _ => false
    }
    if !(ok1 && ok2 && ok3) {
      return
    }
  }
  if span < 8 {
    let delta = edge3.pos - (2 * edge2.pos - edge1.pos)
    let link_ix = edge3.link_ix
    edges.at(edge3_ix).pos = edges.at(edge3_ix).pos - delta
    edges.at(edge3_ix).flags = edges.at(edge3_ix).flags | AUTOHINT_FLAG_DONE
    match link_ix {
      None => ()
      Some(ix) =>
        if ix >= 0 && ix < edges.length() {
          edges.at(ix).pos = edges.at(ix).pos - delta
          edges.at(ix).flags = edges.at(ix).flags | AUTOHINT_FLAG_DONE
        }
    }
    if edges.length() == 12 {
      edges.at(8).pos = edges.at(8).pos - delta
      edges.at(11).pos = edges.at(11).pos - delta
    }
  }
}

///|
fn autohint_align_remaining_edges(
  axis : AutoHintAxis,
  group : AutoHintScriptGroup,
  top_to_bottom_hinting : Bool,
  serif_count : Int,
  anchor_ix : Int?,
) -> Unit {
  if group is Default {
    let mut anchor_ix = anchor_ix
    for edge_ix in 0.. ()
        Some(serif_ix) =>
          if serif_ix >= 0 && serif_ix < axis.edges.length() {
            let serif = axis.edges.at(serif_ix)
            delta = (serif.opos - edge.opos).abs()
          }
      }
      if delta < 64 + 16 {
        match edge.serif_ix {
          None => ()
          Some(serif_ix) => autohint_align_serif_edge(axis, serif_ix, edge_ix)
        }
      } else {
        match anchor_ix {
          Some(anchor_ix0) => {
            let (before_ix, after_ix) = autohint_find_bounding_completed_edges(
              axis.edges,
              edge_ix,
            )
            match (before_ix, after_ix) {
              (Some(before_ix), Some(after_ix)) => {
                let before = axis.edges.at(before_ix)
                let after = axis.edges.at(after_ix)
                let new_pos = if after.opos == before.opos {
                  before.pos
                } else {
                  before.pos +
                  autohint_fixed_mul_div(
                    edge.opos - before.opos,
                    after.pos - before.pos,
                    after.opos - before.opos,
                  )
                }
                axis.edges.at(edge_ix).pos = new_pos
              }
              _ => {
                let anchor = axis.edges.at(anchor_ix0)
                let new_pos = anchor.pos +
                  ((edge.opos - anchor.opos + 16) & -32)
                axis.edges.at(edge_ix).pos = new_pos
              }
            }
          }
          None => {
            anchor_ix = Some(edge_ix)
            axis.edges.at(edge_ix).pos = autohint_pix_round(edge.opos)
          }
        }
      }
      axis.edges.at(edge_ix).flags = axis.edges.at(edge_ix).flags |
        AUTOHINT_FLAG_DONE
      autohint_adjust_link(axis.edges, edge_ix, Prev, top_to_bottom_hinting)
      autohint_adjust_link(axis.edges, edge_ix, Next, top_to_bottom_hinting)
    }
  } else {
    let mut serif_count = serif_count
    for edge_ix in 0.. ()
        Some(serif_ix) =>
          if serif_ix >= 0 && serif_ix < axis.edges.length() {
            axis.edges.at(edge_ix).flags = axis.edges.at(edge_ix).flags |
              AUTOHINT_FLAG_DONE
            autohint_align_serif_edge(axis, serif_ix, edge_ix)
            if serif_count > 0 {
              serif_count = serif_count - 1
            }
          }
      }
    }
    if serif_count == 0 {
      return
    }
    for edge_ix in 0..
          autohint_align_serif_edge(axis, before_ix, edge_ix)
        (None, Some(after_ix)) =>
          autohint_align_serif_edge(axis, after_ix, edge_ix)
        (Some(before_ix), Some(after_ix)) => {
          let before = axis.edges.at(before_ix)
          let after = axis.edges.at(after_ix)
          if after.fpos == before.fpos {
            axis.edges.at(edge_ix).pos = before.pos
          } else {
            axis.edges.at(edge_ix).pos = before.pos +
              autohint_fixed_mul_div(
                edge.fpos - before.fpos,
                after.pos - before.pos,
                after.fpos - before.fpos,
              )
          }
        }
        _ => ()
      }
    }
    top_to_bottom_hinting |> ignore
    anchor_ix |> ignore
  }
}

///|
fn autohint_align_edges_to_blues(
  axis : AutoHintAxis,
  metrics : AutoHintScaledAxisMetrics,
  group : AutoHintScriptGroup,
  scale : AutoHintScale,
) -> Int? {
  let mut anchor_ix : Int? = None
  if group is Default && axis.dim != AUTOHINT_DIM_VERTICAL {
    return anchor_ix
  }
  for edge_ix in 0.. None
      Some(ix) => Some(ix)
    }
    // If we have two neutral zones, skip one of them.
    match (edge.blue_edge, edge2_ix) {
      (Some(_), Some(ix2)) => {
        let edge2 = axis.edges.at(ix2)
        match edge2.blue_edge {
          None => ()
          Some(_) => {
            let skip_ix = if (edge2.flags & AUTOHINT_FLAG_NEUTRAL) != 0 {
              Some(ix2)
            } else if (edge.flags & AUTOHINT_FLAG_NEUTRAL) != 0 {
              Some(edge_ix)
            } else {
              None
            }
            match skip_ix {
              None => ()
              Some(skip_ix) => {
                axis.edges.at(skip_ix).blue_edge = None
                axis.edges.at(skip_ix).flags = axis.edges.at(skip_ix).flags &
                  (-1 - AUTOHINT_FLAG_NEUTRAL)
              }
            }
          }
        }
      }
      _ => ()
    }
    let blue0 = axis.edges.at(edge_ix).blue_edge
    let (blue, edge1_ix, edge2_ix2) = match (blue0, edge2_ix) {
      (Some(b), _) => (b, Some(edge_ix), edge2_ix)
      (None, Some(ix2)) => {
        let b2 = axis.edges.at(ix2).blue_edge
        match b2 {
          None => (AutoHintScaledWidth::default(), None, None)
          Some(bb) => (bb, Some(ix2), Some(edge_ix))
        }
      }
      _ => (AutoHintScaledWidth::default(), None, None)
    }
    match edge1_ix {
      None => continue
      Some(edge1_ix) => {
        axis.edges.at(edge1_ix).pos = blue.fitted
        axis.edges.at(edge1_ix).flags = axis.edges.at(edge1_ix).flags |
          AUTOHINT_FLAG_DONE
        match edge2_ix2 {
          None => ()
          Some(edge2_ix) =>
            if axis.edges.at(edge2_ix).blue_edge is None {
              axis.edges.at(edge2_ix).flags = axis.edges.at(edge2_ix).flags |
                AUTOHINT_FLAG_DONE
              autohint_align_linked_edge(
                axis, metrics, group, scale, edge1_ix, edge2_ix,
              )
            }
        }
        if anchor_ix is None {
          anchor_ix = Some(edge_ix)
        }
      }
    }
  }
  anchor_ix
}

///|
fn autohint_hint_edges(
  axis : AutoHintAxis,
  metrics : AutoHintScaledAxisMetrics,
  group : AutoHintScriptGroup,
  scale : AutoHintScale,
  top_to_bottom_hinting : Bool,
) -> Unit {
  let mut top_to_bottom_hinting = top_to_bottom_hinting
  if axis.dim != AUTOHINT_DIM_VERTICAL {
    top_to_bottom_hinting = false
  }
  let anchor_ix = autohint_align_edges_to_blues(axis, metrics, group, scale)
  let (serif_count, anchor_ix) = autohint_align_stem_edges(
    axis, metrics, group, scale, top_to_bottom_hinting, anchor_ix,
  )
  if axis.dim == AUTOHINT_DIM_HORIZONTAL &&
    (axis.edges.length() == 6 || axis.edges.length() == 12) {
    autohint_hint_lowercase_m(axis.edges, group)
  }
  if serif_count > 0 || anchor_ix is None {
    autohint_align_remaining_edges(
      axis, group, top_to_bottom_hinting, serif_count, anchor_ix,
    )
  }
}