///|
pub(all) struct TSEDOptions {
  apted_options : APTEDOptions
  min_lines : Int
  min_tokens : Int?
  size_penalty : Bool
}

///|
pub fn TSEDOptions::default() -> TSEDOptions {
  {
    apted_options: APTEDOptions::default(),
    min_lines: 5,
    min_tokens: None,
    size_penalty: true,
  }
}

///|
pub fn calculate_tsed(
  tree1 : TreeNode,
  tree2 : TreeNode,
  options : TSEDOptions,
) -> Double {
  let distance = compute_edit_distance(tree1, tree2, options.apted_options)
  let size1 = tree1.get_subtree_size().to_double()
  let size2 = tree2.get_subtree_size().to_double()
  let total_size = size1 + size2
  let tsed_similarity = if total_size > 0.0 {
    @cmp.maximum(1.0 - 2.0 * distance / total_size, 0.0)
  } else {
    1.0
  }
  let mut similarity = tsed_similarity
  let min_size = @cmp.minimum(size1, size2)
  let max_size = @cmp.maximum(size1, size2)
  let size_ratio = min_size / max_size

  if options.size_penalty && distance > 0.0 {
    // Only apply penalty when there are structural differences (distance > 0)
    // distance=0 means structurally identical - no penalty needed

    // Penalty for very small functions (high false positive risk)
    if min_size < 10.0 {
      similarity = similarity * 0.7
    }

    // Penalty for large functions with structural differences
    // Large functions sharing common patterns should be penalized
    if max_size > 50.0 {
      let distance_ratio = distance / max_size
      // More distance = more penalty for large functions
      let large_func_penalty = 1.0 - distance_ratio * 0.3
      similarity = similarity * large_func_penalty
    }

    // Penalty for significant size mismatch
    if size_ratio < 0.5 {
      similarity = similarity * size_ratio.sqrt()
    }
  }

  similarity
}