// 行级 diff 引擎:把官方 core/diff 的编辑脚本转换为左右对照行。

///|
fn equal_row(
  old_line : String,
  new_line : String,
  old_no : Int,
  new_no : Int,
) -> Row {
  {
    kind: Equal,
    old_line: Some(old_line),
    new_line: Some(new_line),
    old_spans: [],
    new_spans: [],
    old_no: Some(old_no),
    new_no: Some(new_no),
  }
}

///|
fn delete_row(line : String, number : Int) -> Row {
  {
    kind: Delete,
    old_line: Some(line),
    new_line: None,
    old_spans: [],
    new_spans: [],
    old_no: Some(number),
    new_no: None,
  }
}

///|
fn insert_row(line : String, number : Int) -> Row {
  {
    kind: Insert,
    old_line: None,
    new_line: Some(line),
    old_spans: [],
    new_spans: [],
    old_no: None,
    new_no: Some(number),
  }
}

///|
fn change_row(
  old_line : String,
  new_line : String,
  old_no : Int,
  new_no : Int,
  word_level : Bool,
) -> Row {
  // 超长行不做字符级 diff:Levenshtein 是 O(n*m),避免 GUI 逐键输入时卡顿。
  let (old_spans, new_spans) = if word_level &&
    old_line.length() <= 2000 &&
    new_line.length() <= 2000 {
    word_spans(old_line, new_line)
  } else {
    ([], [])
  }
  {
    kind: Change,
    old_line: Some(old_line),
    new_line: Some(new_line),
    old_spans,
    new_spans,
    old_no: Some(old_no),
    new_no: Some(new_no),
  }
}

///|
fn build_rows(
  old_lines : Array[String],
  new_lines : Array[String],
  edits : ArrayView[@diff.Edit],
  word_level : Bool,
) -> Array[Row] {
  let rows : Array[Row] = []
  let mut i = 0
  while i < edits.length() {
    match edits[i] {
      @diff.Equal(old_index~, new_index~, len~) => {
        let mut k = 0
        while k < len {
          rows.push(
            equal_row(
              old_lines[old_index + k],
              new_lines[new_index + k],
              old_index + k + 1,
              new_index + k + 1,
            ),
          )
          k = k + 1
        }
        i = i + 1
      }
      @diff.Delete(old_index~, old_len~, ..) => {
        let mut paired = false
        if i + 1 < edits.length() {
          match edits[i + 1] {
            @diff.Insert(new_index~, new_len~, ..) => {
              let pair_count = if old_len < new_len { old_len } else { new_len }
              let mut k = 0
              while k < pair_count {
                rows.push(
                  change_row(
                    old_lines[old_index + k],
                    new_lines[new_index + k],
                    old_index + k + 1,
                    new_index + k + 1,
                    word_level,
                  ),
                )
                k = k + 1
              }
              while k < old_len {
                rows.push(
                  delete_row(old_lines[old_index + k], old_index + k + 1),
                )
                k = k + 1
              }
              while k < new_len {
                rows.push(
                  insert_row(new_lines[new_index + k], new_index + k + 1),
                )
                k = k + 1
              }
              i = i + 2
              paired = true
            }
            _ => ()
          }
        }
        if !paired {
          let mut k = 0
          while k < old_len {
            rows.push(delete_row(old_lines[old_index + k], old_index + k + 1))
            k = k + 1
          }
          i = i + 1
        }
      }
      @diff.Insert(new_index~, new_len~, ..) => {
        let mut k = 0
        while k < new_len {
          rows.push(insert_row(new_lines[new_index + k], new_index + k + 1))
          k = k + 1
        }
        i = i + 1
      }
    }
  }
  rows
}

///|
fn compute_stats(rows : Array[Row]) -> DiffStats {
  let mut added = 0
  let mut removed = 0
  let mut changed = 0
  let mut unchanged = 0
  for row in rows {
    match row.kind {
      Equal => unchanged = unchanged + 1
      Delete => removed = removed + 1
      Insert => added = added + 1
      Change => changed = changed + 1
    }
  }
  { added, removed, changed, unchanged, }
}

///|
/// 计算两份文本的差异。
pub fn diff_text(
  old_text : String,
  new_text : String,
  options : DiffOptions,
) -> DiffResult {
  let old_lines = split_lines(old_text)
  let new_lines = split_lines(new_text)
  let algorithm = match options.algorithm {
    Myers => @diff.Myers
    Patience => @diff.Patience
  }
  let comparison = @diff.Diff(old=old_lines[:], new=new_lines[:], algorithm~)
  let rows = build_rows(
    old_lines,
    new_lines,
    comparison.edits(),
    options.word_level,
  )
  let patch_builder = StringBuilder()
  for hunk in comparison.group(context=options.context) {
    patch_builder.write_string(hunk.render(show=fn(line : String) { line }))
  }
  { rows, stats: compute_stats(rows), patch: patch_builder.to_string(), }
}