///|
/// Line segment detector parameters.
pub(all) struct LsdParameters {
  scale : Double
  sigma_scale : Double
  quant : Double
  ang_th : Double
  log_eps : Double
  density_th : Double
  n_bins : Int
} derive(Debug, Eq)

///|
/// Creates default LSD parameters.
pub fn LsdParameters::default() -> LsdParameters {
  {
    scale: 0.8,
    sigma_scale: 0.6,
    quant: 2.0,
    ang_th: 22.5, // Degrees
    log_eps: 0.0,
    density_th: 0.7,
    n_bins: 1024,
  }
}

///|
/// Detected 2D Line Segment with confidence score and angle.
pub(all) struct LineSegment {
  p1 : Point2D
  p2 : Point2D
  width : Double
  p_val : Double
  nfa : Double
} derive(Debug, Eq)

///|
/// Creates a LineSegment.
pub fn LineSegment::new(
  p1 : Point2D,
  p2 : Point2D,
  width : Double,
  p_val : Double,
  nfa : Double,
) -> LineSegment {
  { p1, p2, width, p_val, nfa }
}

///|
/// Fast Line Segment Detector (LSD) implementation for extracting straight edge segments.
pub fn detect_line_segments(
  img : Image,
  min_length? : Double = 10.0,
  gradient_threshold? : Double = 20.0,
) -> Array[LineSegment] {
  let w = img.width
  let h = img.height
  let segments : Array[LineSegment] = []

  if w < 10 || h < 10 {
    return segments
  }

  // 1. Compute gradients
  let (gx, gy) = filter_sobel(img)
  let (mag, angle) = compute_gradient_magnitude_and_angle(gx, gy)

  // 2. Threshold gradient magnitude and find edge seed pixels
  let used = Array::make(w * h, false)

  for y in 1..<(h - 1) {
    for x in 1..<(w - 1) {
      let idx = y * w + x
      if !used[idx] && mag[idx] >= gradient_threshold {
        // Region growing along level-line direction (perpendicular to gradient: theta + pi/2)
        let seed_angle = angle[idx] + 1.5707963267948966 // + pi/2
        let region_pts : Array[Point2D] = [
          Point2D::new(x.to_double(), y.to_double()),
        ]
        used[idx] = true

        let mut search_x = x
        let mut search_y = y
        let dx = @math.cos(seed_angle).round().to_int()
        let dy = @math.sin(seed_angle).round().to_int()

        for _step in 0..<100 {
          search_x += dx
          search_y += dy
          if search_x >= 0 && search_x < w && search_y >= 0 && search_y < h {
            let n_idx = search_y * w + search_x
            if !used[n_idx] && mag[n_idx] >= gradient_threshold {
              let diff_angle = (angle[n_idx] + 1.5707963267948966 - seed_angle).abs()
              if diff_angle < 0.5 {
                used[n_idx] = true
                region_pts.push(
                  Point2D::new(search_x.to_double(), search_y.to_double()),
                )
              } else {
                break
              }
            } else {
              break
            }
          } else {
            break
          }
        }

        if region_pts.length() >= 5 {
          let p_first = region_pts[0]
          let p_last = region_pts[region_pts.length() - 1]
          let seg_len = p_first.distance(p_last)
          if seg_len >= min_length {
            segments.push(LineSegment::new(p_first, p_last, 2.0, 0.95, 1.0))
          }
        }
      }
    }
  }

  segments
}