///|
pub(all) enum ReadinessLevel {
  Ready
  Conditional
  Blocked
} derive(Debug, Eq, Compare)

///|
pub fn ReadinessLevel::to_string(self : ReadinessLevel) -> String {
  match self {
    Ready => "Ready"
    Conditional => "Conditional"
    Blocked => "Blocked"
  }
}

///|
pub(all) struct OperationalReadinessReport {
  readiness_level : ReadinessLevel
  ready_for_deployment : Bool
  readiness_score : Double
  total_validation_issue_count : Int
  critical_issue_count : Int
  error_issue_count : Int
  warning_issue_count : Int
  orphan_node_ids : Array[String]
  inactive_node_ids : Array[String]
  inactive_edge_ids : Array[String]
  unpowered_asset_ids : Array[String]
  disconnected_controller_ids : Array[String]
  congested_edge_ids : Array[String]
  active_node_ratio_percent : Double
  active_edge_ratio_percent : Double
  mean_asset_availability_percent : Double
  active_flow_layer_count : Int
  capacity_margin_percent : Double
  recommendations : Array[String]
} derive(Debug, Eq)

///|
fn append_unique(values : Array[String], value : String) -> Unit {
  if !values.contains(value) {
    values.push(value)
  }
}

///|
fn is_readiness_asset(node : @graph.Node) -> Bool {
  let node_type = node.node_type()
  node_type == @types.NodeType::Workstation ||
  node_type == @types.NodeType::Machine ||
  node_type == @types.NodeType::RobotArm
}

///|
fn clamp_percent(value : Double) -> Double {
  if value < 0.0 {
    0.0
  } else if value > 100.0 {
    100.0
  } else {
    value
  }
}

///|
fn active_ratio(active : Int, total : Int) -> Double {
  if total == 0 {
    100.0
  } else {
    active.to_double() / total.to_double() * 100.0
  }
}

///|
fn add_recommendation(
  recommendations : Array[String],
  message : String,
) -> Unit {
  if !recommendations.contains(message) {
    recommendations.push(message)
  }
}

///|
fn classify_readiness(
  critical_count : Int,
  error_count : Int,
  inactive_nodes : Int,
  inactive_edges : Int,
  score : Double,
) -> ReadinessLevel {
  if critical_count > 0 || error_count > 0 {
    Blocked
  } else if inactive_nodes > 0 || inactive_edges > 0 || score < 80.0 {
    Conditional
  } else {
    Ready
  }
}

///|
fn score_readiness(
  critical_count : Int,
  error_count : Int,
  warning_count : Int,
  inactive_node_ratio : Double,
  inactive_edge_ratio : Double,
  congested_count : Int,
) -> Double {
  let issue_penalty = critical_count.to_double() * 30.0 +
    error_count.to_double() * 18.0 +
    warning_count.to_double() * 4.0
  let topology_penalty = inactive_node_ratio * 0.20 + inactive_edge_ratio * 0.15
  let congestion_penalty = congested_count.to_double() * 5.0
  clamp_percent(100.0 - issue_penalty - topology_penalty - congestion_penalty)
}

///|
fn collect_validation_findings(
  issues : Array[@types.ValidationIssue],
  unpowered_assets : Array[String],
  disconnected_controllers : Array[String],
) -> (Int, Int, Int) {
  let mut critical_count = 0
  let mut error_count = 0
  let mut warning_count = 0
  for issue in issues {
    match issue.severity {
      @types.ValidationSeverity::Critical => critical_count = critical_count + 1
      @types.ValidationSeverity::Error => error_count = error_count + 1
      @types.ValidationSeverity::Warning => warning_count = warning_count + 1
      @types.ValidationSeverity::Info => ()
    }
    if issue.rule_id == "RULE-UNPOWERED-ASSET" {
      append_unique(unpowered_assets, issue.target_id)
    }
    if issue.rule_id == "RULE-PLC-DISCONNECTED" {
      append_unique(disconnected_controllers, issue.target_id)
    }
  }
  (critical_count, error_count, warning_count)
}

///|
fn collect_node_findings(
  graph : @graph.FactoryGraph,
  inactive_nodes : Array[String],
  unpowered_assets : Array[String],
  disconnected_controllers : Array[String],
) -> Double {
  let nodes = graph.get_all_nodes()
  let mut availability_sum = 0.0
  for node in nodes {
    availability_sum = availability_sum + node.availability() * 100.0
    if !node.is_operational() {
      inactive_nodes.push(node.id())
    }
    if is_readiness_asset(node) {
      let power_edges = graph.get_layer_in_edges(
        @types.FlowType::EnergyFlow,
        node.id(),
      )
      if power_edges.length() == 0 {
        append_unique(unpowered_assets, node.id())
      }
    }
    if node.node_type() == @types.NodeType::PLCController {
      let control_edges = graph.get_layer_out_edges(
        @types.FlowType::ControlFlow,
        node.id(),
      )
      if control_edges.length() == 0 {
        append_unique(disconnected_controllers, node.id())
      }
    }
  }
  if nodes.length() == 0 {
    100.0
  } else {
    availability_sum / nodes.length().to_double()
  }
}

///|
fn collect_edge_findings(
  graph : @graph.FactoryGraph,
  inactive_edges : Array[String],
) -> (Int, Int) {
  let edges = graph.get_all_edges()
  let mut active_count = 0
  for edge in edges {
    if edge.is_active() {
      active_count = active_count + 1
    } else {
      inactive_edges.push(edge.id())
    }
  }
  (active_count, edges.length())
}

///|
fn collect_recommendations(
  level : ReadinessLevel,
  orphan_nodes : Array[String],
  inactive_nodes : Array[String],
  inactive_edges : Array[String],
  unpowered_assets : Array[String],
  disconnected_controllers : Array[String],
  congested_edges : Array[String],
  recommendations : Array[String],
) -> Unit {
  if level == ReadinessLevel::Ready {
    add_recommendation(
      recommendations, "Topology is ready for deployment; keep the validation report with the release artifact.",
    )
  }
  if orphan_nodes.length() > 0 {
    add_recommendation(
      recommendations,
      "Connect or explicitly document orphan assets before commissioning: " +
      orphan_nodes.length().to_string() +
      " asset(s).",
    )
  }
  if inactive_nodes.length() > 0 {
    add_recommendation(
      recommendations, "Review inactive or maintenance assets and confirm the intended production window.",
    )
  }
  if inactive_edges.length() > 0 {
    add_recommendation(
      recommendations, "Restore or approve inactive flow edges before relying on their capacity.",
    )
  }
  if unpowered_assets.length() > 0 {
    add_recommendation(
      recommendations, "Provide an EnergyFlow connection for every production asset without incoming power.",
    )
  }
  if disconnected_controllers.length() > 0 {
    add_recommendation(
      recommendations, "Connect PLC controllers to their field assets through the ControlFlow layer.",
    )
  }
  if congested_edges.length() > 0 {
    add_recommendation(
      recommendations, "Increase bandwidth or reduce demand on congested material edges before launch.",
    )
  }
  if level == ReadinessLevel::Conditional {
    add_recommendation(
      recommendations, "Resolve conditional findings and rerun the readiness audit after topology changes.",
    )
  }
  if level == ReadinessLevel::Blocked {
    add_recommendation(
      recommendations, "Do not deploy until critical and error-level topology findings are resolved.",
    )
  }
}

///|
/// Audit whether a plant graph is complete enough for operational deployment.
///
/// The report combines structural validation, node/edge operating status,
/// flow-layer completeness, material capacity, and asset availability. It is
/// intentionally deterministic so it can be used in release gates and in
/// offline engineering review tools.
pub fn audit_operational_readiness(
  graph : @graph.FactoryGraph,
  demand_per_hour? : Double = 0.0,
) -> OperationalReadinessReport {
  let issues = @graph.validate_graph(graph)
  let health = @graph.inspect_graph_health(graph)
  let capacity = analyze_material_capacity(graph, demand_per_hour~)
  let inactive_nodes : Array[String] = []
  let inactive_edges : Array[String] = []
  let unpowered_assets : Array[String] = []
  let disconnected_controllers : Array[String] = []

  let (critical_count, error_count, warning_count) = collect_validation_findings(
    issues, unpowered_assets, disconnected_controllers,
  )
  let mean_availability = collect_node_findings(
    graph, inactive_nodes, unpowered_assets, disconnected_controllers,
  )
  let (active_edge_count, total_edge_count) = collect_edge_findings(
    graph, inactive_edges,
  )
  let active_node_ratio = active_ratio(
    health.active_node_count,
    health.total_node_count,
  )
  let active_edge_ratio = active_ratio(active_edge_count, total_edge_count)
  let inactive_node_ratio = 100.0 - active_node_ratio
  let inactive_edge_ratio = 100.0 - active_edge_ratio
  let score = score_readiness(
    critical_count,
    error_count,
    warning_count,
    inactive_node_ratio,
    inactive_edge_ratio,
    capacity.overloaded_edge_ids.length(),
  )
  let level = classify_readiness(
    critical_count,
    error_count,
    inactive_nodes.length(),
    inactive_edges.length(),
    score,
  )
  let active_layers = health.layer_health
    .filter(fn(layer) { layer.active_edge_count > 0 })
    .length()
  let recommendations : Array[String] = []
  collect_recommendations(
    level,
    health.orphan_node_ids,
    inactive_nodes,
    inactive_edges,
    unpowered_assets,
    disconnected_controllers,
    capacity.overloaded_edge_ids,
    recommendations,
  )
  {
    readiness_level: level,
    ready_for_deployment: level == ReadinessLevel::Ready,
    readiness_score: score,
    total_validation_issue_count: issues.length(),
    critical_issue_count: critical_count,
    error_issue_count: error_count,
    warning_issue_count: warning_count,
    orphan_node_ids: health.orphan_node_ids,
    inactive_node_ids: inactive_nodes,
    inactive_edge_ids: inactive_edges,
    unpowered_asset_ids: unpowered_assets,
    disconnected_controller_ids: disconnected_controllers,
    congested_edge_ids: capacity.overloaded_edge_ids,
    active_node_ratio_percent: active_node_ratio,
    active_edge_ratio_percent: active_edge_ratio,
    mean_asset_availability_percent: mean_availability,
    active_flow_layer_count: active_layers,
    capacity_margin_percent: capacity.capacity_margin_percent,
    recommendations,
  }
}