///|
priv struct DagrePipelineKernel {
  graph : @model.MermaidGraph
}

///|
fn main {
  run()
}

///|
fn print_usage() -> Unit {
  let usage =
    #|Usage:
    #|  moon run cmd/dagre_pipeline_trace -- --case case1
    #|  moon run cmd/dagre_pipeline_trace -- --case case2
    #|  moon run cmd/dagre_pipeline_trace -- --case case3
    #|  moon run cmd/dagre_pipeline_trace -- --case all
    #|  moon run cmd/dagre_pipeline_trace -- --source "graph LR\nA --> B"
    #|Flags:
    #|  --case   case1 | case2 | case3 | all
    #|  --source  Mermaid source text
    #|  --help   Show this help
    #|
  println(usage.trim_end().to_owned())
}

///|
fn cli_args_without_program() -> Array[String] {
  let all_args = @env.args()
  if all_args.length() <= 1 {
    []
  } else {
    let args : Array[String] = []
    for i in 1.. String? {
  if args.length() == 0 {
    return Some("all")
  }
  if args.length() == 1 {
    let arg = args[0]
    if arg == "--help" {
      return Some("help")
    }
    if arg is [.. "--case=", .. raw_case] {
      let case_name = raw_case.trim().to_lower()
      if case_name == "" {
        return None
      }
      return Some(case_name.to_owned())
    }
    return None
  }
  if args.length() == 2 && args[0] == "--case" {
    let case_name = args[1].trim().to_lower()
    if case_name == "" {
      return None
    }
    return Some(case_name.to_owned())
  }
  None
}

///|
fn parse_source_arg(args : Array[String]) -> String? {
  if args.length() == 1 {
    let arg = args[0]
    if arg is [.. "--source=", .. raw_source] {
      let source = raw_source.trim()
      if source == "" {
        return None
      }
      return Some(source.to_owned())
    }
  }
  if args.length() == 2 && args[0] == "--source" {
    let source = args[1].trim()
    if source == "" {
      return None
    }
    return Some(source.to_owned())
  }
  None
}

///|
fn direction_tag(direction : @model.Direction) -> String {
  match direction {
    TD => "TD"
    TB => "TB"
    LR => "LR"
    BT => "BT"
    RL => "RL"
  }
}

///|
fn flow_graph(
  node_ids : Array[String],
  edges : Array[(String, String)],
  direction : @model.Direction,
) -> @model.MermaidGraph {
  let nodes : Map[String, @model.MermaidNode] = Map([])
  for node_id in node_ids {
    nodes[node_id] = { id: node_id, label: node_id, shape: Rectangle, }
  }
  let graph_edges : Array[@model.MermaidEdge] = []
  for edge in edges {
    let (source, target) = edge
    graph_edges.push({
      source,
      target,
      label: None,
      style: Solid,
      has_arrow_start: false,
      has_arrow_end: true,
      relation_operator: Some("-->"),
    })
  }
  let class_defs : Map[String, Map[String, String]] = Map([])
  let class_assignments : Map[String, String] = Map([])
  let node_styles : Map[String, Map[String, String]] = Map([])
  let sequence_actor_kinds : Map[String, @model.SequenceParticipantKind] = Map([])
  let sequence_blocks : Array[@model.SequenceBlock] = []
  let sequence_notes : Array[@model.SequenceNote] = []
  let sequence_activation_commands : Array[@model.SequenceActivationCommand] = []
  {
    diagram_kind: Flowchart,
    direction,
    nodes,
    node_definition_order: node_ids.copy(),
    edges: graph_edges,
    subgraphs: [],
    class_defs,
    class_assignments,
    node_styles,
    sequence_actor_order: [],
    sequence_actor_kinds,
    sequence_blocks,
    sequence_notes,
    sequence_activation_commands,
  }
}

///|
fn case1_kernel() -> DagrePipelineKernel {
  {
    graph: flow_graph(
      ["A", "B", "M", "X", "Y"],
      [("A", "M"), ("B", "M"), ("M", "X"), ("M", "Y"), ("A", "Y"), ("B", "X")],
      LR,
    ),
  }
}

///|
fn case2_kernel() -> DagrePipelineKernel {
  {
    graph: flow_graph(
      ["A", "B", "C", "D", "E", "F"],
      [
        ("A", "D"),
        ("B", "C"),
        ("A", "E"),
        ("B", "F"),
        ("C", "F"),
        ("D", "E"),
        ("A", "F"),
        ("B", "E"),
      ],
      LR,
    ),
  }
}

///|
fn case3_kernel() -> DagrePipelineKernel {
  {
    graph: flow_graph(
      ["A", "B", "C", "D", "E", "F", "G", "H"],
      [
        ("A", "C"),
        ("A", "D"),
        ("B", "C"),
        ("B", "D"),
        ("C", "E"),
        ("D", "F"),
        ("C", "F"),
        ("D", "E"),
        ("E", "G"),
        ("F", "H"),
        ("E", "H"),
        ("F", "G"),
      ],
      LR,
    ),
  }
}

///|
fn sorted_graph_node_ids(graph : @model.MermaidGraph) -> Array[String] {
  let ids : Array[String] = []
  for node_id, _ in graph.nodes {
    ids.push(node_id)
  }
  ids.sort()
  ids
}

///|
fn rank_layers(
  setup_state : @layout_engine_graph_legacy_setup_core.LegacyLayoutSetupState,
) -> Array[Array[String]] {
  let max_rank = setup_state.lane_state.flow_max_rank
  let layers : Array[Array[(Int32, String)]] = []
  for _ in 0..<=max_rank {
    layers.push([])
  }
  for node in setup_state.nodes_in_order {
    let rank = match setup_state.lane_state.flow_rank_by_node_id.get(node.id) {
      Some(value) => value
      None => 0
    }
    let lane = match setup_state.lane_state.flow_lane_by_node_id.get(node.id) {
      Some(value) => value
      None => 0
    }
    if rank < 0 {
      continue
    }
    if rank >= layers.length() {
      for _ in layers.length()..<=rank {
        layers.push([])
      }
    }
    layers[rank].push((lane, node.id))
  }
  let normalized_layers : Array[Array[String]] = []
  for layer in layers {
    layer.sort_by((left, right) => {
      let (left_lane, left_id) = left
      let (right_lane, right_id) = right
      if left_lane != right_lane {
        left_lane.compare(right_lane)
      } else {
        left_id.compare(right_id)
      }
    })
    let ids : Array[String] = []
    for node in layer {
      let (_, node_id) = node
      ids.push(node_id)
    }
    normalized_layers.push(ids)
  }
  normalized_layers
}

///|
fn dump_case_trace(case_name : String) -> Bool {
  let kernel = match case_name {
    "case1" => Some(case1_kernel())
    "case2" => Some(case2_kernel())
    "case3" => Some(case3_kernel())
    _ => None
  }
  match kernel {
    Some(found) => {
      dump_graph_trace(case_name, found.graph)
      true
    }
    None => false
  }
}

///|
fn dump_graph_trace(case_name : String, graph : @model.MermaidGraph) -> Unit {
  let options : @model.RenderOptions = {
    ..@model.RenderOptions::default(),
    layout_engine: Some(DagreParity),
  }
  let setup_state = @layout_engine_graph_legacy_setup_core.build_legacy_layout_setup(
    graph, options, true, false,
  )
  let placement_state = @layout_engine_graph_placement_core.run_legacy_placement_phase(
    graph, setup_state, true, false,
  )

  println("CASE\t\{case_name}")
  println("DIRECTION\t\{direction_tag(graph.direction)}")
  for edge in graph.edges {
    println("EDGE\t\{edge.source}\t\{edge.target}")
  }

  let sorted_node_ids = sorted_graph_node_ids(graph)
  for node in setup_state.nodes_in_order {
    println("NODE_ORDER\t\{node.id}")
  }
  for node_id in sorted_node_ids {
    match placement_state.positioned_by_id.get(node_id) {
      Some(node) =>
        println("NODE_SIZE\t\{node_id}\t\{node.width}\t\{node.height}")
      None => ()
    }
  }
  for node_id in sorted_node_ids {
    let rank = match setup_state.lane_state.flow_rank_by_node_id.get(node_id) {
      Some(value) => value
      None => 0
    }
    println("RANK\t\{node_id}\t\{rank}")
  }
  let layers = rank_layers(setup_state)
  for rank, layer in layers {
    let joined_layer = layer.join(",")
    println("LAYER\t\{rank}\t\{joined_layer}")
  }
  for node_id in sorted_node_ids {
    match placement_state.positioned_by_id.get(node_id) {
      Some(node) => println("POS\t\{node_id}\t\{node.x}\t\{node.y}")
      None => ()
    }
  }
}

///|
fn dump_source_trace(source : String) -> Bool {
  let graph = @parser_header_core.parse_mermaid(source) catch {
    err => {
      println("Error: failed to parse source: \{err}")
      return false
    }
  }
  dump_graph_trace("source", graph)
  true
}

///|
fn run() -> Unit {
  let args = cli_args_without_program()
  match parse_source_arg(args) {
    Some(source) => {
      let ok = dump_source_trace(source)
      if !ok {
        print_usage()
      }
      return
    }
    None => ()
  }
  match parse_case_arg(args) {
    Some(case_name) =>
      if case_name == "help" {
        print_usage()
      } else if case_name == "all" {
        for case_name in ["case1", "case2", "case3"] {
          let _ = dump_case_trace(case_name)
        }
      } else if !dump_case_trace(case_name) {
        println("Error: unknown case '\{case_name}'")
        print_usage()
      }
    None => {
      println("Error: invalid arguments")
      print_usage()
    }
  }
}