///|
/// RouteAble is an offline-first, explainable accessibility route planner.
/// Applications supply a small campus graph, community map, or compatible
/// public data and receive a route plus a human-readable rationale.
pub(all) enum MobilityProfile {
  Wheelchair
  LowVision
  Stroller
  Standard
} derive(Eq, Debug)

///|
pub(all) enum Surface {
  Asphalt
  Paving
  Brick
  Gravel
  Indoor
} derive(Eq, Debug)

///|
pub(all) enum AlertLevel {
  Info
  Notice
  Warning
  Blocked
} derive(Eq, Debug)

///|
pub(all) struct Node {
  id : Int
  label : String
  zone : String
  x : Double
  y : Double
  has_rest_area : Bool
  has_tactile_paving : Bool
} derive(Debug)

///|
pub(all) struct Edge {
  id : Int
  from : Int
  to : Int
  meters : Int
  slope_percent : Int
  steps : Int
  lighting : Int
  crossing : Int
  construction : Bool
  surface : Surface
  width_cm : Int
  covered : Bool
  note : String
} derive(Debug)

///|
pub(all) struct ProfileConfig {
  max_slope : Int
  max_steps : Int
  minimum_lighting : Int
  minimum_width_cm : Int
  avoid_crossings : Bool
  avoid_gravel : Bool
  rest_bonus : Double
} derive(Debug)

///|
pub(all) struct RouteSegment {
  edge_id : Int
  from_label : String
  to_label : String
  meters : Int
  score : Double
  reasons : Array[String]
  alerts : Array[String]
} derive(Debug)

///|
pub(all) struct Route {
  profile : MobilityProfile
  node_ids : Array[Int]
  segments : Array[RouteSegment]
  total_meters : Int
  total_score : Double
  accessible : Bool
  summary : String
  warnings : Array[String]
} derive(Debug)

///|
pub(all) struct Graph {
  nodes : Array[Node]
  edges : Array[Edge]
} derive(Debug)

///|
pub fn profile_config(profile : MobilityProfile) -> ProfileConfig {
  match profile {
    Wheelchair =>
      {
        max_slope: 8,
        max_steps: 0,
        minimum_lighting: 2,
        minimum_width_cm: 90,
        avoid_crossings: false,
        avoid_gravel: true,
        rest_bonus: 8.0,
      }
    LowVision =>
      {
        max_slope: 14,
        max_steps: 24,
        minimum_lighting: 3,
        minimum_width_cm: 70,
        avoid_crossings: true,
        avoid_gravel: false,
        rest_bonus: 4.0,
      }
    Stroller =>
      {
        max_slope: 10,
        max_steps: 0,
        minimum_lighting: 2,
        minimum_width_cm: 95,
        avoid_crossings: false,
        avoid_gravel: true,
        rest_bonus: 6.0,
      }
    Standard =>
      {
        max_slope: 18,
        max_steps: 100,
        minimum_lighting: 1,
        minimum_width_cm: 50,
        avoid_crossings: false,
        avoid_gravel: false,
        rest_bonus: 1.0,
      }
  }
}

///|
pub fn profile_name(profile : MobilityProfile) -> String {
  match profile {
    Wheelchair => "wheelchair"
    LowVision => "low vision"
    Stroller => "stroller"
    Standard => "walking"
  }
}

///|
pub fn surface_name(surface : Surface) -> String {
  match surface {
    Asphalt => "asphalt"
    Paving => "paving"
    Brick => "brick"
    Gravel => "gravel"
    Indoor => "indoor"
  }
}

///|
pub fn valid_graph(graph : Graph) -> Bool {
  if graph.nodes.length() == 0 {
    return false
  }
  let mut index = 0
  while index < graph.nodes.length() {
    if graph.nodes[index].id != index {
      return false
    }
    index = index + 1
  }
  let mut edge_index = 0
  while edge_index < graph.edges.length() {
    let edge = graph.edges[edge_index]
    if edge.from < 0 ||
      edge.to < 0 ||
      edge.from >= graph.nodes.length() ||
      edge.to >= graph.nodes.length() {
      return false
    }
    if edge.meters <= 0 ||
      edge.width_cm <= 0 ||
      edge.lighting < 0 ||
      edge.lighting > 5 {
      return false
    }
    edge_index = edge_index + 1
  }
  true
}

///|
pub fn edge_block_reason(edge : Edge, profile : MobilityProfile) -> String? {
  let config = profile_config(profile)
  if edge.construction {
    return Some("该路段正在施工")
  }
  if edge.steps > config.max_steps {
    return Some(
      "包含 \{edge.steps} 级台阶,超过当前出行画像的限制",
    )
  }
  if edge.slope_percent > config.max_slope {
    return Some("坡度 \{edge.slope_percent}% 超过可接受范围")
  }
  if edge.width_cm < config.minimum_width_cm {
    return Some("有效宽度 \{edge.width_cm}cm 不足")
  }
  if config.avoid_gravel && edge.surface == Gravel {
    return Some("碎石路面对该出行方式不可通行")
  }
  None
}

///|
pub fn edge_alerts(edge : Edge, profile : MobilityProfile) -> Array[String] {
  let config = profile_config(profile)
  let alerts : Array[String] = []
  if edge.lighting < config.minimum_lighting {
    alerts.push("照明较弱,建议在白天通行")
  }
  if edge.crossing > 0 {
    if config.avoid_crossings {
      alerts.push(
        "需要通过 \{edge.crossing} 个路口,建议使用语音导航",
      )
    } else {
      alerts.push("需要通过 \{edge.crossing} 个路口,请注意来车")
    }
  }
  if edge.slope_percent >= config.max_slope - 2 && edge.slope_percent > 0 {
    alerts.push("坡度接近该画像阈值,请量力通行")
  }
  if !edge.covered {
    alerts.push("该路段无遮雨连廊,雨天需注意湿滑")
  }
  alerts
}

///|
pub fn edge_reasons(edge : Edge, profile : MobilityProfile) -> Array[String] {
  let reasons : Array[String] = []
  if edge.steps == 0 {
    reasons.push("全程无台阶")
  } else {
    reasons.push("包含 \{edge.steps} 级台阶")
  }
  if edge.slope_percent <= 4 {
    reasons.push("坡度平缓(\{edge.slope_percent}%)")
  } else {
    reasons.push("坡度为 \{edge.slope_percent}%")
  }
  if edge.lighting >= 4 {
    reasons.push("夜间照明良好")
  }
  if edge.width_cm >= 150 {
    reasons.push("通行宽度充足")
  }
  if edge.surface == Indoor {
    reasons.push("经过室内连廊")
  }
  if profile == LowVision && edge.crossing == 0 {
    reasons.push("无需穿越车行路口")
  }
  reasons
}

///|
pub fn edge_score(edge : Edge, profile : MobilityProfile) -> Double? {
  match edge_block_reason(edge, profile) {
    Some(_) => None
    None => {
      let config = profile_config(profile)
      let mut score = edge.meters.to_double()
      score = score + edge.slope_percent.to_double() * 12.0
      score = score + edge.steps.to_double() * 18.0
      score = score + (5 - edge.lighting).to_double() * 9.0
      let crossing_penalty = if config.avoid_crossings { 55.0 } else { 18.0 }
      score = score + edge.crossing.to_double() * crossing_penalty
      if edge.surface == Brick {
        score = score + 15.0
      }
      if edge.surface == Gravel {
        score = score + 60.0
      }
      if edge.width_cm < 120 {
        score = score + (120 - edge.width_cm).to_double() * 1.5
      }
      if !edge.covered {
        score = score + 5.0
      }
      Some(score)
    }
  }
}

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

///|
fn minimum_unvisited(distances : Array[Double], visited : Array[Bool]) -> Int {
  let mut best_index = -1
  let mut best_distance = 1.0e30
  let mut index = 0
  while index < distances.length() {
    if !visited[index] && distances[index] < best_distance {
      best_distance = distances[index]
      best_index = index
    }
    index = index + 1
  }
  best_index
}

///|
/// Compute the lowest-friction route for a profile using constrained Dijkstra.
pub fn plan(
  graph : Graph,
  start : Int,
  destination : Int,
  profile : MobilityProfile,
) -> Route? {
  if !valid_graph(graph) ||
    start < 0 ||
    destination < 0 ||
    start >= graph.nodes.length() ||
    destination >= graph.nodes.length() {
    return None
  }
  let size = graph.nodes.length()
  let distances = Array::make(size, 1.0e30)
  let previous = Array::make(size, -1)
  let visited = Array::make(size, false)
  distances[start] = 0.0
  let mut round = 0
  while round < size {
    let current = minimum_unvisited(distances, visited)
    if current < 0 || current == destination {
      break
    }
    visited[current] = true
    let mut edge_index = 0
    while edge_index < graph.edges.length() {
      let edge = graph.edges[edge_index]
      if edge.from == current {
        match edge_score(edge, profile) {
          Some(cost) => {
            let candidate = distances[current] + cost
            if candidate < distances[edge.to] {
              distances[edge.to] = candidate
              previous[edge.to] = current
            }
          }
          None => ()
        }
      }
      edge_index = edge_index + 1
    }
    round = round + 1
  }
  if distances[destination] >= 1.0e29 {
    return None
  }
  let reversed : Array[Int] = []
  let mut cursor = destination
  reversed.push(cursor)
  while cursor != start {
    cursor = previous[cursor]
    if cursor < 0 {
      return None
    }
    reversed.push(cursor)
  }
  let node_ids : Array[Int] = []
  let mut reverse_index = reversed.length()
  while reverse_index > 0 {
    reverse_index = reverse_index - 1
    node_ids.push(reversed[reverse_index])
  }
  let segments : Array[RouteSegment] = []
  let warnings : Array[String] = []
  let mut total_meters = 0
  let mut path_index = 0
  while path_index + 1 < node_ids.length() {
    let from = node_ids[path_index]
    let to = node_ids[path_index + 1]
    match edge_between(graph, from, to) {
      Some(edge) => {
        let alerts = edge_alerts(edge, profile)
        let mut alert_index = 0
        while alert_index < alerts.length() {
          warnings.push(
            "\{graph.nodes[from].label}→\{graph.nodes[to].label}:\{alerts[alert_index]}",
          )
          alert_index = alert_index + 1
        }
        match edge_score(edge, profile) {
          Some(score) => {
            total_meters = total_meters + edge.meters
            segments.push({
              edge_id: edge.id,
              from_label: graph.nodes[from].label,
              to_label: graph.nodes[to].label,
              meters: edge.meters,
              score,
              reasons: edge_reasons(edge, profile),
              alerts,
            })
          }
          None => return None
        }
      }
      None => return None
    }
    path_index = path_index + 1
  }
  let summary = "为\{profile_name(profile)}推荐 \{graph.nodes[start].label} 至 \{graph.nodes[destination].label},全程约 \{total_meters} 米,途经 \{segments.length()} 段可通行路段。"
  Some({
    profile,
    node_ids,
    segments,
    total_meters,
    total_score: distances[destination],
    accessible: true,
    summary,
    warnings,
  })
}

///|
pub fn route_steps(route : Route) -> Array[String] {
  let lines : Array[String] = []
  let mut index = 0
  while index < route.segments.length() {
    let segment = route.segments[index]
    lines.push(
      "\{index + 1}. 从\{segment.from_label}前往\{segment.to_label},约\{segment.meters}米。\{segment.reasons.join(";")}。",
    )
    index = index + 1
  }
  lines
}

///|
pub fn route_markdown(route : Route) -> String {
  let mut text = "## \{profile_name(route.profile)}路线\n\n\{route.summary}\n\n### 分段说明\n"
  let steps = route_steps(route)
  let mut index = 0
  while index < steps.length() {
    text = text + steps[index] + "\n"
    index = index + 1
  }
  if route.warnings.length() > 0 {
    text = text + "\n### 通行提醒\n"
    let mut warning_index = 0
    while warning_index < route.warnings.length() {
      text = text + "- " + route.warnings[warning_index] + "\n"
      warning_index = warning_index + 1
    }
  }
  text
}

///|
pub fn alternative_destinations(
  graph : Graph,
  start : Int,
  profile : MobilityProfile,
) -> Array[Route] {
  let routes : Array[Route] = []
  let mut node_index = 0
  while node_index < graph.nodes.length() {
    if node_index != start {
      match plan(graph, start, node_index, profile) {
        Some(route) => routes.push(route)
        None => ()
      }
    }
    node_index = node_index + 1
  }
  routes
}

///|
pub fn nearest_rest_stop(graph : Graph, route : Route) -> Node? {
  let mut index = 0
  while index < route.node_ids.length() {
    let node = graph.nodes[route.node_ids[index]]
    if node.has_rest_area {
      return Some(node)
    }
    index = index + 1
  }
  None
}

///|
pub fn accessibility_grade(route : Route) -> String {
  let warning_count = route.warnings.length()
  if warning_count == 0 &&
    route.total_score < route.total_meters.to_double() * 1.25 {
    "A:低阻力路线"
  } else if warning_count <= 2 {
    "B:可通行,建议留意提醒"
  } else {
    "C:可通行但存在多项风险"
  }
}