///|
/// Route quality analysis built on top of the planner's public route result.
/// These helpers make it possible for a UI to compare options without
/// duplicating accessibility policy outside MoonBit.
pub(all) struct RouteMetrics {
  distance_meters : Int
  average_slope : Double
  total_crossings : Int
  low_light_segments : Int
  covered_segments : Int
  rest_stops : Int
  warning_count : Int
  estimated_minutes : Int
} derive(Debug)

///|
pub(all) struct ProfileComparison {
  profile : MobilityProfile
  reachable : Bool
  distance_meters : Int
  friction_score : Double
  grade : String
  message : String
} derive(Debug)

///|
fn find_edge_by_id(graph : Graph, id : Int) -> Edge? {
  let mut index = 0
  while index < graph.edges.length() {
    if graph.edges[index].id == id {
      return Some(graph.edges[index])
    }
    index = index + 1
  }
  None
}

///|
pub fn walking_speed_meters_per_minute(profile : MobilityProfile) -> Int {
  match profile {
    Wheelchair => 48
    LowVision => 55
    Stroller => 52
    Standard => 75
  }
}

///|
pub fn estimate_minutes(
  distance_meters : Int,
  profile : MobilityProfile,
) -> Int {
  if distance_meters <= 0 {
    0
  } else {
    let speed = walking_speed_meters_per_minute(profile)
    (distance_meters + speed - 1) / speed
  }
}

///|
pub fn route_metrics(graph : Graph, route : Route) -> RouteMetrics {
  let mut slope_total = 0
  let mut crossings = 0
  let mut low_light = 0
  let mut covered = 0
  let mut rests = 0
  let mut index = 0
  while index < route.segments.length() {
    let segment = route.segments[index]
    match find_edge_by_id(graph, segment.edge_id) {
      Some(edge) => {
        slope_total = slope_total + edge.slope_percent
        crossings = crossings + edge.crossing
        if edge.lighting <= 2 {
          low_light = low_light + 1
        }
        if edge.covered {
          covered = covered + 1
        }
      }
      None => ()
    }
    index = index + 1
  }
  let mut node_index = 0
  while node_index < route.node_ids.length() {
    if graph.nodes[route.node_ids[node_index]].has_rest_area {
      rests = rests + 1
    }
    node_index = node_index + 1
  }
  let average_slope = if route.segments.length() == 0 {
    0.0
  } else {
    slope_total.to_double() / route.segments.length().to_double()
  }
  {
    distance_meters: route.total_meters,
    average_slope,
    total_crossings: crossings,
    low_light_segments: low_light,
    covered_segments: covered,
    rest_stops: rests,
    warning_count: route.warnings.length(),
    estimated_minutes: estimate_minutes(route.total_meters, route.profile),
  }
}

///|
pub fn route_quality_label(metrics : RouteMetrics) -> String {
  if metrics.warning_count == 0 &&
    metrics.average_slope <= 4.0 &&
    metrics.low_light_segments == 0 {
    "excellent"
  } else if metrics.warning_count <= 2 && metrics.average_slope <= 6.0 {
    "good"
  } else if metrics.warning_count <= 5 {
    "usable with care"
  } else {
    "higher attention needed"
  }
}

///|
pub fn route_briefing(graph : Graph, route : Route) -> String {
  let metrics = route_metrics(graph, route)
  let mut text = "Route quality: " + route_quality_label(metrics)
  text = text + "\nDistance: \{metrics.distance_meters}m"
  text = text + "\nEstimated time: \{metrics.estimated_minutes} min"
  text = text + "\nAverage slope: \{metrics.average_slope} %"
  text = text + "\nRoad crossings: \{metrics.total_crossings}"
  text = text + "\nCovered segments: \{metrics.covered_segments}"
  text = text + "\nRest stops: \{metrics.rest_stops}"
  text
}

///|
pub fn compare_profiles(
  graph : Graph,
  start : Int,
  destination : Int,
) -> Array[ProfileComparison] {
  let profiles = [Wheelchair, LowVision, Stroller, Standard]
  let comparisons : Array[ProfileComparison] = []
  let mut index = 0
  while index < profiles.length() {
    let profile = profiles[index]
    match plan(graph, start, destination, profile) {
      Some(route) =>
        comparisons.push({
          profile,
          reachable: true,
          distance_meters: route.total_meters,
          friction_score: route.total_score,
          grade: accessibility_grade(route),
          message: "A compatible route is available.",
        })
      None =>
        comparisons.push({
          profile,
          reachable: false,
          distance_meters: 0,
          friction_score: 0.0,
          grade: "unavailable",
          message: "No route satisfies this profile's hard constraints.",
        })
    }
    index = index + 1
  }
  comparisons
}

///|
pub fn reachable_profile_count(
  graph : Graph,
  start : Int,
  destination : Int,
) -> Int {
  let comparisons = compare_profiles(graph, start, destination)
  let mut count = 0
  let mut index = 0
  while index < comparisons.length() {
    if comparisons[index].reachable {
      count = count + 1
    }
    index = index + 1
  }
  count
}

///|
pub fn route_has_rest_stop(graph : Graph, route : Route) -> Bool {
  match nearest_rest_stop(graph, route) {
    Some(_) => true
    None => false
  }
}

///|
pub fn route_can_be_recommended(graph : Graph, route : Route) -> Bool {
  let metrics = route_metrics(graph, route)
  route.accessible &&
  metrics.distance_meters > 0 &&
  metrics.average_slope <= 14.0
}