///|
/// Helper function to create consistent parse error messages
fn create_parse_error(
  message : String,
  input : String,
) -> ParseSourceLocationError {
  ParseSourceLocationError::ParseError(
    "Invalid source location format: \{message} in '\{input}'",
  )
}

///|
/// Helper function to parse integer with error handling
fn parse_int_safe(
  value : String,
  name : String,
  input : String,
) -> Int raise ParseSourceLocationError {
  @strconv.parse_int(value) catch {
    _ => raise create_parse_error("invalid \{name} '\{value}'", input)
  }
}

///|
/// Helper function to validate positive integer
fn validate_positive(
  value : Int,
  name : String,
  input : String,
) -> Unit raise ParseSourceLocationError {
  if value <= 0 {
    raise create_parse_error("\{name} must be positive, got \{value}", input)
  }
}

///|
pub fn SourceLocation::parse(
  s : String,
) -> SourceLocation raise ParseSourceLocationError {
  // split two parts: path and position info
  // you should split this from the right, because paths may contain colons
  // e.g. `README.mbt.md:12:11-12:28`
  // e.g `/home/illu-quote.mbt/README.mbt.md:12:11-12:28`
  try {
    // Handle empty string
    if s.is_empty() {
      raise ParseSourceLocationError::ParseError("Empty source location string")
    }

    // We need to find the pattern :row:col-row:col at the end
    // Strategy: look for the last colon that's followed by a valid position pattern
    let mut position_start = -1
    let mut i = s.length() - 1

    // Work backwards to find a valid position pattern
    while i >= 0 {
      if s[i] == ':' {
        // Extract everything after this colon
        let candidate = s[i + 1:]

        // Check if this looks like a valid position (digits, colons, dashes only)
        let mut valid_position = true
        let mut has_content = false
        for j = 0; j < candidate.length(); j = j + 1 {
          let ch = candidate[j]
          if (ch >= '0' && ch <= '9') || ch == ':' || ch == '-' {
            has_content = true
          } else {
            valid_position = false
            break
          }
        }
        if valid_position && has_content {
          // Try to parse this as a position to see if it's valid
          let parts = candidate.split(":").collect()
          if parts.length() >= 2 {
            // Check if first part is all digits (row)
            let first_part = parts[0].to_string()
            let mut first_all_digits = true
            let mut first_has_digits = false
            for k = 0; k < first_part.length(); k = k + 1 {
              let ch = first_part[k]
              if ch >= '0' && ch <= '9' {
                first_has_digits = true
              } else {
                first_all_digits = false
                break
              }
            }
            if first_all_digits && first_has_digits {
              // Check if second part starts with digits (column)
              let second_part = parts[1].to_string()
              let dash_parts = second_part.split("-").collect()
              let col_part = dash_parts[0].to_string()
              let mut col_all_digits = true
              let mut col_has_digits = false
              for k = 0; k < col_part.length(); k = k + 1 {
                let ch = col_part[k]
                if ch >= '0' && ch <= '9' {
                  col_has_digits = true
                } else {
                  col_all_digits = false
                  break
                }
              }
              if col_all_digits && col_has_digits {
                position_start = i
                break
              }
            }
          }
        }
      }
      i = i - 1
    }
    if position_start == -1 {
      raise create_parse_error("no position information found", s)
    }

    // Extract path and position parts
    let path_str = s[0:position_start]
    let position_str = s[position_start + 1:]

    // Parse the path
    let path = @path.Path::parse(path_str)

    // Parse position string (format: row:column or row:column-endrow or row:column-endrow:endcol)
    let parts = position_str.split(":").collect()
    if parts.length() < 2 {
      raise create_parse_error("missing column information", s)
    }

    // Parse start row
    let start_row = parse_int_safe(parts[0].to_string(), "row number", s)
    validate_positive(start_row, "row number", s)

    // Parse start column (might include range)
    let col_part = parts[1].to_string()
    let dash_split = col_part.split("-").collect()
    let start_col = parse_int_safe(
      dash_split[0].to_string(),
      "column number",
      s,
    )
    validate_positive(start_col, "column number", s)
    let start_pos = Position::{ row: start_row, column: start_col }

    // Check for range
    if dash_split.length() == 1 {
      // No range, end is same as start
      SourceLocation::{ path, start: start_pos, end: start_pos }
    } else if dash_split.length() == 2 {
      let end_part = dash_split[1].to_string()
      if end_part.is_empty() {
        raise create_parse_error("empty range part", s)
      }

      // Check if we have more parts in the original split (for endrow:endcol case)
      if parts.length() == 2 {
        // Format is row:col-endrow
        let end_row = parse_int_safe(end_part, "end row number", s)
        validate_positive(end_row, "end row number", s)
        let end_pos = Position::{ row: end_row, column: start_col }
        SourceLocation::{ path, start: start_pos, end: end_pos }
      } else if parts.length() == 3 {
        // Format is row:col-endrow:endcol
        let end_row = parse_int_safe(end_part, "end row number", s)
        validate_positive(end_row, "end row number", s)
        let end_col = parse_int_safe(
          parts[2].to_string(),
          "end column number",
          s,
        )
        validate_positive(end_col, "end column number", s)
        let end_pos = Position::{ row: end_row, column: end_col }
        SourceLocation::{ path, start: start_pos, end: end_pos }
      } else {
        raise create_parse_error("invalid range format", s)
      }
    } else {
      raise create_parse_error("invalid range format", s)
    }
  } catch {
    ParseSourceLocationError::ParseError(_) as e => raise e
    _ as e =>
      raise ParseSourceLocationError::ParseError(
        "Unexpected error parsing source location '\{s}': \{e}",
      )
  }
}

///|
test {
  let s = "/home/illu-quote.mbt/README.mbt.md:12:11-12:28"
  let p = SourceLocation::parse(s)
  inspect(
    p,
    content="{path: /home/illu-quote.mbt/README.mbt.md, start: {row: 12, column: 11}, end: {row: 12, column: 28}}",
  )
}

///|
pub fn SourceLocation::split_file(
  file : String,
  locs : Array[SourceLocation],
) -> Array[StringView] {
  // Split file content into lines
  let lines = file.split("\n").collect()
  let result : Array[StringView] = []
  for loc in locs {
    // Validate line numbers (1-indexed)
    if loc.start.row < 1 ||
      loc.start.row > lines.length() ||
      loc.end.row < 1 ||
      loc.end.row > lines.length() ||
      loc.start.row > loc.end.row {
      // Skip invalid locations, add empty StringView
      result.push("".to_string()[:])
      continue
    }

    // Handle single line case
    if loc.start.row == loc.end.row {
      let line = lines[loc.start.row - 1] // Convert to 0-indexed
      let start_col = if loc.start.column < 1 { 1 } else { loc.start.column }
      let end_col = if loc.end.column < 1 {
        line.length() + 1
      } else {
        loc.end.column
      }

      // Validate column bounds
      if start_col > line.length() + 1 {
        result.push("".to_string()[:])
        continue
      }
      let actual_end = if end_col > line.length() + 1 {
        line.length() + 1
      } else {
        end_col
      }
      let actual_start = start_col - 1 // Convert to 0-indexed
      let actual_end_idx = actual_end - 1 // Convert to 0-indexed
      if actual_start >= actual_end_idx {
        result.push("".to_string()[:])
      } else {
        result.push(try! line[actual_start:actual_end_idx])
      }
      continue
    }

    // Handle multi-line case
    let parts : Array[String] = []

    // First line: from start_col to end of line
    let first_line = lines[loc.start.row - 1]
    let start_col = if loc.start.column < 1 { 1 } else { loc.start.column }
    if start_col <= first_line.length() + 1 {
      let start_idx = start_col - 1 // Convert to 0-indexed
      let first_part = if start_idx < first_line.length() {
        (try! first_line[start_idx:]).to_string()
      } else {
        ""
      }
      parts.push(first_part)
    }

    // Middle lines: entire lines
    for i = loc.start.row + 1; i < loc.end.row; i = i + 1 {
      parts.push(lines[i - 1].to_string()) // Convert to 0-indexed and to String
    }

    // Last line: from beginning to end_col
    if loc.end.row > loc.start.row {
      let last_line = lines[loc.end.row - 1]
      let end_col = if loc.end.column < 1 {
        last_line.length() + 1
      } else {
        loc.end.column
      }
      let end_idx = if end_col > last_line.length() + 1 {
        last_line.length()
      } else {
        end_col - 1
      }
      let last_part = if end_idx > 0 {
        (try! last_line[0:end_idx]).to_string()
      } else {
        ""
      }
      parts.push(last_part)
    }

    // Join parts with newlines and convert to StringView
    let combined_content = parts.join("\n")
    result.push(combined_content[:])
  }
  result
}