// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Nested diagram orchestration for non-root nested diagram containers.
///
/// Upstream reference:
/// - `LayoutNested`
/// - the nested diagram types fixture
///
/// The core layout engines in diago only special-case a subset of root-level
/// diagram kinds. This helper mirrors the upstream nested-layout flow by
/// extracting nested diagram subgraphs, recursively laying them out, using the
/// laid out bounds as outer placeholders, and then restoring descendants and
/// extracted edges in original source order.

///|
priv struct NestedSequenceContext {
  container_order : Array[String]
  graphs_by_container_id : Map[String, @graph.GraphInput]
  constant_near_order : Array[String]
  constant_near_graphs_by_container_id : Map[String, @graph.GraphInput]
  descendant_container_by_id : Map[String, String]
  internal_edge_container_by_index : Map[Int, String]
  external_edge_indices : Map[Int, Bool]
}

///|
fn empty_nested_sequence_context() -> NestedSequenceContext {
  {
    container_order: [],
    graphs_by_container_id: Map([]),
    constant_near_order: [],
    constant_near_graphs_by_container_id: Map([]),
    descendant_container_by_id: Map([]),
    internal_edge_container_by_index: Map([]),
    external_edge_indices: Map([]),
  }
}

///|
fn[Engine : LayoutEngine] prepare_nested_sequence_layouts(
  engine : Engine,
  graph : @graph.GraphInput,
  config : LayoutConfig,
) -> (@graph.GraphInput, NestedSequenceContext) raise LayoutError {
  let candidate_ids = collect_nested_sequence_candidate_ids(graph)
  if candidate_ids.is_empty() {
    return (graph, empty_nested_sequence_context())
  }

  let context = empty_nested_sequence_context()
  let mut prepared_graph = graph
  for container_id in candidate_ids {
    let prepared_object_by_id = build_object_by_abs_id_syntax(prepared_graph)
    let container = match prepared_object_by_id.get(container_id) {
      Some(obj) => obj
      None => continue
    }
    let is_constant_near = is_constant_near_candidate_like_d2(
      prepared_graph, container,
    )
    let is_root_grid_cell_container = is_root_grid_cell_container_like_d2(
        prepared_graph, container,
      ) &&
      !is_constant_near
    if is_constant_near {
      let original_near = container.near
      let nested_direction = nested_layout_direction_like_d2(container)
      let (prepared_without_nested, constant_near_graph, external_edge_indices) = extract_constant_near_graph_like_d2(
        prepared_graph, container_id,
      )
      let laid_out_constant_near = layout_with_engine(
        engine,
        clear_constant_near_for_nested_layout_like_d2(
          constant_near_graph, container_id,
        ),
        config,
        nested_direction,
      )
      let laid_out_constant_near = restore_constant_near_after_layout_like_d2(
        laid_out_constant_near, container_id, original_near,
      )
      prepared_graph = prepared_without_nested
      context.constant_near_order.push(container_id)
      context.constant_near_graphs_by_container_id[container_id] = laid_out_constant_near
      for edge_index, _ in external_edge_indices {
        context.external_edge_indices[edge_index] = true
      }
      continue
    }
    let (
      prepared_without_nested,
      descendant_graph,
      descendant_ids,
      internal_edge_indices,
      external_edge_indices,
    ) = extract_nested_sequence_graph(prepared_graph, container_id)
    if is_root_grid_cell_container {
      let nested_direction = nested_layout_direction_like_d2(container)
      let include_self_graph = extract_nested_include_self_graph_like_d2(
        prepared_graph, container_id,
      )
      let laid_out_include_self = layout_with_engine(
        engine, include_self_graph, config, nested_direction,
      )
      let (placeholder_root, finalized_nested) = finalize_grid_cell_container_like_d2(
        prepared_graph, laid_out_include_self, descendant_graph, container_id, internal_edge_indices,
      )
      prepared_graph = install_nested_sequence_placeholder_object(
        prepared_without_nested, placeholder_root,
      )
      context.container_order.push(container_id)
      context.graphs_by_container_id[container_id] = finalized_nested
      for descendant_id, _ in descendant_ids {
        context.descendant_container_by_id[descendant_id] = container_id
      }
      for edge_index, _ in internal_edge_indices {
        context.internal_edge_container_by_index[edge_index] = container_id
      }
      for edge_index, _ in external_edge_indices {
        context.external_edge_indices[edge_index] = true
      }
      continue
    }
    let nested_graph = descendant_graph
    let nested_direction = nested_layout_direction_like_d2(container)
    let laid_out_nested = layout_with_engine(
      engine, nested_graph, config, nested_direction,
    )
    let finalized_nested = finalize_nested_sequence_graph(laid_out_nested)
    let placeholder_box = match finalized_nested.root.box {
      Some(box) => box
      None => @graph.Box::zero()
    }
    prepared_graph = install_nested_sequence_placeholder(
      prepared_without_nested, container_id, placeholder_box,
    )
    context.container_order.push(container_id)
    context.graphs_by_container_id[container_id] = finalized_nested
    for descendant_id, _ in descendant_ids {
      context.descendant_container_by_id[descendant_id] = container_id
    }
    for edge_index, _ in internal_edge_indices {
      context.internal_edge_container_by_index[edge_index] = container_id
    }
    for edge_index, _ in external_edge_indices {
      context.external_edge_indices[edge_index] = true
    }
  }
  (prepared_graph, context)
}

///|
fn nested_layout_direction_like_d2(container : @graph.ObjectInput) -> Direction {
  match container.direction {
    Some(raw) => direction_from_keyword(raw).unwrap_or(Down)
    None => Down
  }
}

///|
fn restore_nested_sequence_layouts(
  original_graph : @graph.GraphInput,
  laid_out_graph : @graph.GraphInput,
  context : NestedSequenceContext,
) -> @graph.GraphInput {
  if context.container_order.is_empty() &&
    context.constant_near_order.is_empty() {
    return laid_out_graph
  }

  let laid_out_graph = apply_constant_near_graphs_like_d2(
    laid_out_graph, context,
  )

  let container_ids : Map[String, Bool] = Map([])
  let shifted_nested_object_by_id : Map[String, @graph.ObjectInput] = Map([])
  let shifted_nested_edge_by_index : Map[Int, @graph.EdgeInput] = Map([])
  let shifted_activation_boxes : Array[(String, @graph.Box)] = []
  let shifted_sequence_notes : Array[(String, String, @graph.Box)] = []
  let shifted_sequence_fragments : Array[@graph.SequenceFragmentLayout] = []
  let laid_out_object_by_id = build_object_by_abs_id_syntax(laid_out_graph)
  for container_id in context.container_order {
    container_ids[container_id] = true
    let nested_graph = match context.graphs_by_container_id.get(container_id) {
      Some(graph) => graph
      None => continue
    }
    let container = match laid_out_object_by_id.get(container_id) {
      Some(obj) => obj
      None => continue
    }
    let (dx, dy) = match container.box {
      Some(box) => (box.x, box.y)
      None => (0.0, 0.0)
    }
    let shifted_graph = shift_nested_sequence_graph(nested_graph, dx, dy)
    for obj in shifted_graph.objects {
      shifted_nested_object_by_id[obj.abs_id_syntax] = obj
    }
    for edge in shifted_graph.edges {
      shifted_nested_edge_by_index[edge.index] = edge
    }
    for activation in shifted_graph.activation_boxes {
      shifted_activation_boxes.push(activation)
    }
    for note in shifted_graph.sequence_notes {
      shifted_sequence_notes.push(note)
    }
    for fragment in shifted_graph.sequence_fragments_layout {
      shifted_sequence_fragments.push(fragment)
    }
  }

  let objects : Array[@graph.ObjectInput] = []
  let final_object_by_id : Map[String, @graph.ObjectInput] = Map([])
  final_object_by_id[original_graph.root.abs_id_syntax] = clone_object_with_parts(
    laid_out_graph.root,
    laid_out_graph.root.box,
    original_graph.root.child_ids,
  )
  for original_obj in original_graph.objects {
    let final_obj = match
      context.descendant_container_by_id.get(original_obj.abs_id_syntax) {
      Some(_) =>
        match shifted_nested_object_by_id.get(original_obj.abs_id_syntax) {
          Some(obj) => obj
          None => original_obj
        }
      None =>
        match laid_out_object_by_id.get(original_obj.abs_id_syntax) {
          Some(laid_out_obj) =>
            if container_ids.contains(original_obj.abs_id_syntax) {
              clone_object_with_parts_and_label(
                original_obj,
                laid_out_obj.box,
                None,
                original_obj.child_ids,
              )
            } else {
              laid_out_obj
            }
          None => original_obj
        }
    }
    final_object_by_id[final_obj.abs_id_syntax] = final_obj
    objects.push(final_obj)
  }

  let laid_out_edge_by_index : Map[Int, @graph.EdgeInput] = Map([])
  for edge in laid_out_graph.edges {
    laid_out_edge_by_index[edge.index] = edge
  }
  let edges : Array[@graph.EdgeInput] = []
  for original_edge in original_graph.edges {
    let edge = if context.external_edge_indices.contains(original_edge.index) {
      route_nested_external_edge(original_edge, final_object_by_id)
    } else {
      match context.internal_edge_container_by_index.get(original_edge.index) {
        Some(_) =>
          match shifted_nested_edge_by_index.get(original_edge.index) {
            Some(edge) => edge
            None => original_edge
          }
        None =>
          match laid_out_edge_by_index.get(original_edge.index) {
            Some(edge) => edge
            None => original_edge
          }
      }
    }
    edges.push(edge)
  }

  let activation_boxes : Array[(String, @graph.Box)] = []
  for activation in laid_out_graph.activation_boxes {
    activation_boxes.push(activation)
  }
  for activation in shifted_activation_boxes {
    activation_boxes.push(activation)
  }

  let sequence_notes : Array[(String, String, @graph.Box)] = []
  for note in laid_out_graph.sequence_notes {
    sequence_notes.push(note)
  }
  for note in shifted_sequence_notes {
    sequence_notes.push(note)
  }

  let sequence_fragments_layout : Array[@graph.SequenceFragmentLayout] = []
  for fragment in laid_out_graph.sequence_fragments_layout {
    sequence_fragments_layout.push(fragment)
  }
  for fragment in shifted_sequence_fragments {
    sequence_fragments_layout.push(fragment)
  }

  let root = final_object_by_id.get_or_default(
    original_graph.root.abs_id_syntax,
    laid_out_graph.root,
  )
  clone_graph_with_sequence_parts(
    laid_out_graph, root, objects, edges, activation_boxes, sequence_notes, sequence_fragments_layout,
  )
}

///|
fn collect_nested_sequence_candidate_ids(
  graph : @graph.GraphInput,
) -> Array[String] {
  let object_by_id = build_object_by_abs_id_syntax(graph)
  let queue : Array[String] = []
  for child_id in graph.root.child_ids {
    if object_by_id.contains(child_id) {
      queue.push(child_id)
    }
  }
  let candidate_ids : Array[String] = []
  let mut index = 0
  while index < queue.length() {
    let current_id = queue[index]
    index += 1
    match object_by_id.get(current_id) {
      Some(current) => {
        let is_constant_near = is_constant_near_candidate_like_d2(
          graph, current,
        )
        let is_root_grid_cell_container = is_root_grid_cell_container_like_d2(
            graph, current,
          ) &&
          !is_constant_near
        if is_root_grid_cell_container || is_constant_near {
          candidate_ids.push(current.abs_id_syntax)
        } else if is_nested_diagram_candidate_like_d2(current) &&
          !current.child_ids.is_empty() {
          candidate_ids.push(current.abs_id_syntax)
        } else {
          for child_id in current.child_ids {
            if object_by_id.contains(child_id) {
              queue.push(child_id)
            }
          }
        }
      }
      None => ()
    }
  }
  candidate_ids
}

///|
fn is_root_grid_cell_container_like_d2(
  graph : @graph.GraphInput,
  obj : @graph.ObjectInput,
) -> Bool {
  (graph.root.grid_rows is Some(_) || graph.root.grid_columns is Some(_)) &&
  graph.root.child_ids.contains(obj.abs_id_syntax) &&
  !obj.child_ids.is_empty() &&
  !is_nested_diagram_candidate_like_d2(obj)
}

///|
fn graph_root_level_like_d2(graph : @graph.GraphInput) -> Int {
  if graph.root.child_ids.is_empty() {
    return 0
  }
  let mut min_depth = 1000000000
  for child_id in graph.root.child_ids {
    let depth = @graph.syntax_path_depth(child_id)
    if depth < min_depth {
      min_depth = depth
    }
  }
  if min_depth <= 0 {
    0
  } else {
    min_depth - 1
  }
}

///|
fn is_constant_near_candidate_like_d2(
  graph : @graph.GraphInput,
  obj : @graph.ObjectInput,
) -> Bool {
  if graph_root_level_like_d2(graph) != 0 {
    return false
  }
  match obj.near {
    Some(raw) => is_constant_near_key_like_d2(raw.to_lower())
    None => false
  }
}

///|
fn is_nested_diagram_candidate_like_d2(obj : @graph.ObjectInput) -> Bool {
  obj.shape_type == SequenceDiagram ||
  obj.grid_rows is Some(_) ||
  obj.grid_columns is Some(_)
}

///|
fn extract_constant_near_graph_like_d2(
  graph : @graph.GraphInput,
  container_id : String,
) -> (@graph.GraphInput, @graph.GraphInput, Map[Int, Bool]) raise LayoutError {
  let object_by_id = build_object_by_abs_id_syntax(graph)
  guard object_by_id.get(container_id) is Some(_) else {
    raise invalid_graph("constant near container not found: \{container_id}")
  }
  let subtree_ids = collect_descendant_ids(object_by_id, [container_id])
  let parent_id_by_child_id = build_parent_id_by_child_abs_id_syntax(graph)
  let parent_id = parent_id_by_child_id.get(container_id)
  let subtree_semantic_ids : Map[String, Bool] = Map([])
  let nested_objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if subtree_ids.contains(obj.abs_id_syntax) {
      subtree_semantic_ids[obj.id] = true
      nested_objects.push(obj)
    }
  }
  let nested_edges : Array[@graph.EdgeInput] = []
  let outer_edges : Array[@graph.EdgeInput] = []
  let external_edge_indices : Map[Int, Bool] = Map([])
  for edge in graph.edges {
    let src_nested = subtree_ids.contains(edge.src_id_syntax)
    let dst_nested = subtree_ids.contains(edge.dst_id_syntax)
    if src_nested && dst_nested {
      nested_edges.push(edge)
    } else if src_nested || dst_nested {
      external_edge_indices[edge.index] = true
    } else {
      outer_edges.push(edge)
    }
  }
  let outer_root_child_ids = match parent_id {
    Some(parent) if parent == graph.root.abs_id_syntax =>
      remove_child_id_like_d2(graph.root.child_ids, container_id)
    _ => graph.root.child_ids
  }
  let outer_root = clone_object_with_parts(
    graph.root,
    graph.root.box,
    outer_root_child_ids,
  )
  let outer_objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if subtree_ids.contains(obj.abs_id_syntax) {
      continue
    }
    let child_ids = match parent_id {
      Some(parent) if parent == obj.abs_id_syntax =>
        remove_child_id_like_d2(obj.child_ids, container_id)
      _ => obj.child_ids
    }
    if child_ids == obj.child_ids {
      outer_objects.push(obj)
    } else {
      outer_objects.push(clone_object_with_parts(obj, obj.box, child_ids))
    }
  }
  let nested_activation_boxes : Array[(String, @graph.Box)] = []
  let outer_activation_boxes : Array[(String, @graph.Box)] = []
  for activation in graph.activation_boxes {
    let (id, box) = activation
    if subtree_semantic_ids.contains(id) {
      nested_activation_boxes.push((id, box))
    } else {
      outer_activation_boxes.push((id, box))
    }
  }
  let nested_sequence_notes : Array[(String, String, @graph.Box)] = []
  let outer_sequence_notes : Array[(String, String, @graph.Box)] = []
  for note_entry in graph.sequence_notes {
    let (id, note, box) = note_entry
    if subtree_semantic_ids.contains(id) {
      nested_sequence_notes.push((id, note, box))
    } else {
      outer_sequence_notes.push((id, note, box))
    }
  }
  let outer_graph = @graph.GraphInput::from_parts(
    graph.name,
    outer_root,
    outer_objects,
    outer_edges,
    graph.sequence_fragments,
    outer_activation_boxes,
    outer_sequence_notes,
    graph.sequence_fragments_layout,
    graph.layers,
    graph.scenarios,
    graph.steps,
    graph.legend,
    is_folder_only=graph.is_folder_only,
    data=graph.data,
  )
  let nested_graph = extract_nested_include_self_graph_like_d2(
    graph, container_id,
  )
  let nested_graph = clone_graph_with_sequence_parts(
    nested_graph,
    nested_graph.root,
    nested_graph.objects,
    nested_edges,
    nested_activation_boxes,
    nested_sequence_notes,
    [],
  )
  (outer_graph, nested_graph, external_edge_indices)
}

///|
fn remove_child_id_like_d2(
  child_ids : Array[String],
  child_id : String,
) -> Array[String] {
  let filtered : Array[String] = []
  for current in child_ids {
    if current != child_id {
      filtered.push(current)
    }
  }
  filtered
}

///|
fn clear_constant_near_for_nested_layout_like_d2(
  graph : @graph.GraphInput,
  container_id : String,
) -> @graph.GraphInput {
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if obj.abs_id_syntax == container_id {
      objects.push(clone_object_with_near_like_d2(obj, None))
    } else {
      objects.push(obj)
    }
  }
  clone_graph_with_sequence_parts(
    graph,
    graph.root,
    objects,
    graph.edges,
    graph.activation_boxes,
    graph.sequence_notes,
    graph.sequence_fragments_layout,
  )
}

///|
fn clone_object_with_near_like_d2(
  obj : @graph.ObjectInput,
  near : String?,
) -> @graph.ObjectInput {
  @graph.ObjectInput::from_parts(
    obj.id,
    obj.label,
    obj.shape_type,
    obj.style,
    obj.box,
    obj.label_box,
    obj.child_ids,
    obj.z_index,
    obj.icon,
    obj.tooltip,
    obj.link,
    obj.classes,
    obj.grid_rows,
    obj.grid_columns,
    obj.grid_gap,
    obj.horizontal_gap,
    obj.vertical_gap,
    obj.grid_column_span,
    obj.grid_row_span,
    near,
    obj.top,
    obj.left,
    direction=obj.direction,
    language=obj.language,
    sql_constraints=obj.sql_constraints,
    id_val=obj.id_val,
    id_syntax=obj.id_syntax,
    abs_id_syntax=obj.abs_id_syntax,
    references=obj.references,
    icon_position=obj.icon_position,
    tooltip_position=obj.tooltip_position,
    label_position=obj.label_position,
    grid_row_directed=obj.grid_row_directed,
  )
}

///|
fn restore_constant_near_after_layout_like_d2(
  graph : @graph.GraphInput,
  container_id : String,
  near : String?,
) -> @graph.GraphInput {
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if obj.abs_id_syntax == container_id {
      let effective_label_position = match obj.label_position {
        Some(current) => Some(current)
        None =>
          if obj.child_ids.is_empty() {
            None
          } else {
            inferred_label_position_from_layout_like_d2(obj)
          }
      }
      objects.push(
        clone_object_with_parts_and_effective_positions(
          clone_object_with_near_like_d2(obj, near),
          obj.box,
          obj.label_box,
          obj.child_ids,
          obj.classes,
          effective_label_position,
          obj.icon_position,
        ),
      )
    } else {
      objects.push(obj)
    }
  }
  clone_graph_with_sequence_parts(
    graph,
    graph.root,
    objects,
    graph.edges,
    graph.activation_boxes,
    graph.sequence_notes,
    graph.sequence_fragments_layout,
  )
}

///|
fn extract_nested_sequence_graph(
  graph : @graph.GraphInput,
  container_id : String,
) -> (
  @graph.GraphInput,
  @graph.GraphInput,
  Map[String, Bool],
  Map[Int, Bool],
  Map[Int, Bool],
) raise LayoutError {
  let object_by_id = build_object_by_abs_id_syntax(graph)
  let container = match object_by_id.get(container_id) {
    Some(obj) => obj
    None =>
      raise invalid_graph(
        "nested sequence container not found: \{container_id}",
      )
  }
  let descendant_ids = collect_descendant_ids(object_by_id, container.child_ids)
  let descendant_semantic_ids : Map[String, Bool] = Map([])
  let nested_root = clone_nested_sequence_root(container)
  let nested_objects : Array[@graph.ObjectInput] = []
  let outer_objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if descendant_ids.contains(obj.abs_id_syntax) {
      descendant_semantic_ids[obj.id] = true
      nested_objects.push(obj)
    } else if obj.abs_id_syntax == container_id {
      outer_objects.push(clone_object_with_parts(obj, obj.box, []))
    } else {
      outer_objects.push(obj)
    }
  }

  let nested_edges : Array[@graph.EdgeInput] = []
  let outer_edges : Array[@graph.EdgeInput] = []
  let internal_edge_indices : Map[Int, Bool] = Map([])
  let external_edge_indices : Map[Int, Bool] = Map([])
  for edge in graph.edges {
    let src_nested = descendant_ids.contains(edge.src_id_syntax)
    let dst_nested = descendant_ids.contains(edge.dst_id_syntax)
    if src_nested && dst_nested {
      internal_edge_indices[edge.index] = true
      nested_edges.push(edge)
    } else if src_nested || dst_nested {
      external_edge_indices[edge.index] = true
    } else {
      outer_edges.push(edge)
    }
  }

  let nested_activation_boxes : Array[(String, @graph.Box)] = []
  let outer_activation_boxes : Array[(String, @graph.Box)] = []
  for activation in graph.activation_boxes {
    let (id, box) = activation
    if descendant_semantic_ids.contains(id) {
      nested_activation_boxes.push((id, box))
    } else {
      outer_activation_boxes.push((id, box))
    }
  }

  let nested_sequence_notes : Array[(String, String, @graph.Box)] = []
  let outer_sequence_notes : Array[(String, String, @graph.Box)] = []
  for note_entry in graph.sequence_notes {
    let (id, note, box) = note_entry
    if descendant_semantic_ids.contains(id) {
      nested_sequence_notes.push((id, note, box))
    } else {
      outer_sequence_notes.push((id, note, box))
    }
  }

  let outer_graph = @graph.GraphInput::from_parts(
    graph.name,
    graph.root,
    outer_objects,
    outer_edges,
    graph.sequence_fragments,
    outer_activation_boxes,
    outer_sequence_notes,
    graph.sequence_fragments_layout,
    graph.layers,
    graph.scenarios,
    graph.steps,
    graph.legend,
    is_folder_only=graph.is_folder_only,
    data=graph.data,
  )
  let nested_graph = @graph.GraphInput::from_parts(
    graph.name,
    nested_root,
    nested_objects,
    nested_edges,
    [],
    nested_activation_boxes,
    nested_sequence_notes,
    [],
    [],
    [],
    [],
    None,
    is_folder_only=false,
    data=Map([]),
  )
  (
    outer_graph, nested_graph, descendant_ids, internal_edge_indices, external_edge_indices,
  )
}

///|
fn collect_descendant_ids(
  object_by_id : Map[String, @graph.ObjectInput],
  child_ids : Array[String],
) -> Map[String, Bool] {
  let descendant_ids : Map[String, Bool] = Map([])
  let queue : Array[String] = []
  for child_id in child_ids {
    if object_by_id.contains(child_id) {
      queue.push(child_id)
    }
  }
  let mut index = 0
  while index < queue.length() {
    let current_id = queue[index]
    index += 1
    if descendant_ids.contains(current_id) {
      continue
    }
    descendant_ids[current_id] = true
    match object_by_id.get(current_id) {
      Some(obj) =>
        for child_id in obj.child_ids {
          if object_by_id.contains(child_id) {
            queue.push(child_id)
          }
        }
      None => ()
    }
  }
  descendant_ids
}

///|
fn extract_nested_include_self_graph_like_d2(
  graph : @graph.GraphInput,
  container_id : String,
) -> @graph.GraphInput raise LayoutError {
  let object_by_id = build_object_by_abs_id_syntax(graph)
  let container = match object_by_id.get(container_id) {
    Some(obj) => obj
    None =>
      raise invalid_graph(
        "nested include-self container not found: \{container_id}",
      )
  }
  let subtree_ids = collect_descendant_ids(object_by_id, [container_id])
  let subtree_semantic_ids : Map[String, Bool] = Map([])
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if subtree_ids.contains(obj.abs_id_syntax) {
      subtree_semantic_ids[obj.id] = true
      objects.push(obj)
    }
  }
  let edges : Array[@graph.EdgeInput] = []
  for edge in graph.edges {
    if subtree_ids.contains(edge.src_id_syntax) &&
      subtree_ids.contains(edge.dst_id_syntax) {
      edges.push(edge)
    }
  }
  let activation_boxes : Array[(String, @graph.Box)] = []
  for activation in graph.activation_boxes {
    let (id, box) = activation
    if subtree_semantic_ids.contains(id) {
      activation_boxes.push((id, box))
    }
  }
  let sequence_notes : Array[(String, String, @graph.Box)] = []
  for note_entry in graph.sequence_notes {
    let (id, note, box) = note_entry
    if subtree_semantic_ids.contains(id) {
      sequence_notes.push((id, note, box))
    }
  }
  let root = @graph.ObjectInput::from_parts(
    "root",
    "",
    Rectangle,
    @graph.StyleInput::new(),
    None,
    None,
    [container.abs_id_syntax],
    0,
    None,
    None,
    None,
    [],
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    None,
    direction=container.direction,
  )
  @graph.GraphInput::from_parts(
    graph.name,
    root,
    objects,
    edges,
    [],
    activation_boxes,
    sequence_notes,
    [],
    [],
    [],
    [],
    None,
    is_folder_only=false,
    data=Map([]),
  )
}

///|
fn finalize_grid_cell_container_like_d2(
  original_graph : @graph.GraphInput,
  laid_out_graph : @graph.GraphInput,
  descendant_graph : @graph.GraphInput,
  container_id : String,
  internal_edge_indices : Map[Int, Bool],
) -> (@graph.ObjectInput, @graph.GraphInput) raise LayoutError {
  let object_by_id = build_object_by_abs_id_syntax(laid_out_graph)
  let container = match object_by_id.get(container_id) {
    Some(obj) => obj
    None =>
      raise invalid_graph(
        "laid out grid cell container not found: \{container_id}",
      )
  }
  let container_box = match container.box {
    Some(box) => box
    None => @graph.Box::zero()
  }
  let dx = -container_box.x
  let dy = -container_box.y
  let placeholder_box = @graph.Box::new(
    0.0,
    0.0,
    container_box.width,
    container_box.height,
  )
  let placeholder_label_box = shift_box_opt(container.label_box, dx, dy)
  let (default_label_position, default_icon_position) = grid_child_effective_positions_like_d2(
    container,
  )
  let effective_label_position = match container.label_position {
    Some(position) => Some(position)
    None => default_label_position
  }
  let effective_icon_position = match container.icon_position {
    Some(position) => Some(position)
    None => default_icon_position
  }
  let placeholder_root = clone_object_with_parts_and_effective_positions(
    container,
    Some(placeholder_box),
    placeholder_label_box,
    [],
    nested_layout_placeholder_classes(container.classes),
    effective_label_position,
    effective_icon_position,
  )
  let nested_objects : Array[@graph.ObjectInput] = []
  for obj in laid_out_graph.objects {
    if obj.abs_id_syntax == container_id {
      continue
    }
    nested_objects.push(
      clone_object_with_parts_and_label(
        obj,
        shift_box_opt(obj.box, dx, dy),
        shift_box_opt(obj.label_box, dx, dy),
        obj.child_ids,
      ),
    )
  }
  let nested_edges : Array[@graph.EdgeInput] = []
  for edge in laid_out_graph.edges {
    if !internal_edge_indices.contains(edge.index) {
      continue
    }
    nested_edges.push(
      clone_edge_with_layout(
        edge,
        shift_route(edge.route, dx, dy),
        shift_points_opt(edge.bend_points, dx, dy),
        shift_box_opt(edge.label_box, dx, dy),
      ),
    )
  }
  let subtree_object_by_id = build_object_by_abs_id_syntax(original_graph)
  let descendant_ids = collect_descendant_ids(
    subtree_object_by_id,
    container.child_ids,
  )
  let descendant_semantic_ids : Map[String, Bool] = Map([])
  for descendant_id, _ in descendant_ids {
    match subtree_object_by_id.get(descendant_id) {
      Some(obj) => descendant_semantic_ids[obj.id] = true
      None => ()
    }
  }
  let activation_boxes : Array[(String, @graph.Box)] = []
  for activation in laid_out_graph.activation_boxes {
    let (id, box) = activation
    if descendant_semantic_ids.contains(id) {
      activation_boxes.push((id, shift_box(box, dx, dy)))
    }
  }
  let sequence_notes : Array[(String, String, @graph.Box)] = []
  for note_entry in laid_out_graph.sequence_notes {
    let (id, note, box) = note_entry
    if descendant_semantic_ids.contains(id) {
      sequence_notes.push((id, note, shift_box(box, dx, dy)))
    }
  }
  let sequence_fragments_layout : Array[@graph.SequenceFragmentLayout] = []
  for fragment in laid_out_graph.sequence_fragments_layout {
    sequence_fragments_layout.push(shift_fragment_layout(fragment, dx, dy))
  }
  let nested_root = clone_object_with_parts_and_effective_positions(
    placeholder_root,
    Some(placeholder_box),
    placeholder_label_box,
    descendant_graph.root.child_ids,
    container.classes,
    effective_label_position,
    effective_icon_position,
  )
  let nested_graph = clone_graph_with_sequence_parts(
    descendant_graph, nested_root, nested_objects, nested_edges, activation_boxes,
    sequence_notes, sequence_fragments_layout,
  )
  (placeholder_root, nested_graph)
}

///|
fn clone_object_with_parts_and_effective_positions(
  obj : @graph.ObjectInput,
  box : @graph.Box?,
  label_box : @graph.Box?,
  child_ids : Array[String],
  classes : Array[String],
  label_position : String?,
  icon_position : String?,
) -> @graph.ObjectInput {
  @graph.ObjectInput::from_parts(
    obj.id,
    obj.label,
    obj.shape_type,
    obj.style,
    box,
    label_box,
    child_ids,
    obj.z_index,
    obj.icon,
    obj.tooltip,
    obj.link,
    classes,
    obj.grid_rows,
    obj.grid_columns,
    obj.grid_gap,
    obj.horizontal_gap,
    obj.vertical_gap,
    obj.grid_column_span,
    obj.grid_row_span,
    obj.near,
    obj.top,
    obj.left,
    direction=obj.direction,
    language=obj.language,
    sql_constraints=obj.sql_constraints,
    id_val=obj.id_val,
    id_syntax=obj.id_syntax,
    abs_id_syntax=obj.abs_id_syntax,
    references=obj.references,
    icon_position~,
    tooltip_position=obj.tooltip_position,
    label_position~,
    grid_row_directed=obj.grid_row_directed,
  )
}

///|
fn finalize_nested_sequence_graph(
  graph : @graph.GraphInput,
) -> @graph.GraphInput {
  let (min_x, min_y, max_x, max_y, saw_content) = nested_sequence_content_bounds(
    graph,
  )
  let dx = if min_x < 0.0 { -min_x } else { 0.0 }
  let dy = if min_y < 0.0 { -min_y } else { 0.0 }
  let shifted = if dx == 0.0 && dy == 0.0 {
    graph
  } else {
    shift_nested_sequence_graph(graph, dx, dy)
  }
  let placeholder_box = if shifted.root.shape_type == SequenceDiagram {
    match nested_sequence_lifeline_end_y(shifted) {
      Some(end_y) => {
        let mut has_person_participant = false
        for child_id in shifted.root.child_ids {
          match shifted.find_object(child_id) {
            Some(obj) =>
              if obj.shape_type == Person {
                has_person_participant = true
              }
            None => ()
          }
        }
        let horizontal_padding = if !shifted.activation_boxes.is_empty() ||
          has_person_participant {
          D2_NESTED_SEQUENCE_ACTIVATION_HORIZONTAL_PADDING
        } else {
          D2_NESTED_SEQUENCE_HORIZONTAL_PADDING
        }
        @graph.Box::new(
          0.0,
          0.0,
          max_x + dx + horizontal_padding,
          end_y + D2_NESTED_SEQUENCE_VERTICAL_PADDING,
        )
      }
      None =>
        nested_sequence_placeholder_box(saw_content, max_x + dx, max_y + dy)
    }
  } else {
    match shifted.root.box {
      Some(box) =>
        if box.width > 0.0 && box.height > 0.0 {
          @graph.Box::new(0.0, 0.0, box.width, box.height)
        } else {
          nested_non_sequence_placeholder_box_like_d2(
            shifted,
            saw_content,
            max_x + dx,
            max_y + dy,
          )
        }
      None =>
        nested_non_sequence_placeholder_box_like_d2(
          shifted,
          saw_content,
          max_x + dx,
          max_y + dy,
        )
    }
  }
  let root = clone_object_with_parts(
    shifted.root,
    Some(placeholder_box),
    shifted.root.child_ids,
  )
  clone_graph_with_sequence_parts(
    shifted,
    root,
    shifted.objects,
    shifted.edges,
    shifted.activation_boxes,
    shifted.sequence_notes,
    shifted.sequence_fragments_layout,
  )
}

///|
fn nested_non_sequence_placeholder_box_like_d2(
  graph : @graph.GraphInput,
  saw_content : Bool,
  max_x : Double,
  max_y : Double,
) -> @graph.Box {
  if graph.root.grid_rows is Some(_) || graph.root.grid_columns is Some(_) {
    let (hgap, vgap) = grid_semantic_gaps(graph.root)
    let padding = grid_semantic_padding_like_d2(
      graph.root,
      max_x,
      max_y,
      hgap,
      vgap,
    )
    @graph.Box::new(
      0.0,
      0.0,
      if saw_content {
        max_x + padding.right
      } else {
        0.0
      },
      if saw_content {
        max_y + padding.bottom
      } else {
        0.0
      },
    )
  } else {
    nested_sequence_placeholder_box(saw_content, max_x, max_y)
  }
}

///|
const D2_NESTED_SEQUENCE_HORIZONTAL_PADDING : Double = 10.0

///|
const D2_NESTED_SEQUENCE_ACTIVATION_HORIZONTAL_PADDING : Double = 12.0

///|
const D2_NESTED_SEQUENCE_VERTICAL_PADDING : Double = 12.0

///|
const D2_NESTED_SEQUENCE_LIFELINE_STEP : Double = 70.0

///|
fn nested_sequence_lifeline_end_y(graph : @graph.GraphInput) -> Double? {
  if graph.root.child_ids.is_empty() {
    return None
  }
  let participant_ids : Map[String, Bool] = Map([])
  for id in graph.root.child_ids {
    match graph.find_object(id) {
      Some(obj) =>
        if !obj.classes.contains("__diago_sequence_group") {
          participant_ids[id] = true
        }
      None => ()
    }
  }
  let mut end_y = 0.0
  let mut saw_participant = false
  for obj in graph.objects {
    if !participant_ids.contains(obj.abs_id_syntax) {
      continue
    }
    match obj.box {
      Some(box) => {
        saw_participant = true
        if box.y + box.height > end_y {
          end_y = box.y + box.height
        }
      }
      None => ()
    }
  }
  if !saw_participant {
    return None
  }
  for edge in graph.edges {
    for point in edge.route {
      if point.y > end_y {
        end_y = point.y
      }
    }
  }
  for note in graph.sequence_notes {
    let box = note.2
    if box.y + box.height > end_y {
      end_y = box.y + box.height
    }
  }
  Some(end_y + D2_NESTED_SEQUENCE_LIFELINE_STEP)
}

///|
fn nested_sequence_placeholder_box(
  saw_content : Bool,
  max_x : Double,
  max_y : Double,
) -> @graph.Box {
  let width = if saw_content { max_x } else { 0.0 }
  let height = if saw_content { max_y } else { 0.0 }
  @graph.Box::new(0.0, 0.0, width, height)
}

///|
fn nested_sequence_content_bounds(
  graph : @graph.GraphInput,
) -> (Double, Double, Double, Double, Bool) {
  let mut min_x = 0.0
  let mut min_y = 0.0
  let mut max_x = 0.0
  let mut max_y = 0.0
  let mut saw_content = false

  let update_point = fn(point : @graph.Point) -> Unit {
    saw_content = true
    if point.x < min_x {
      min_x = point.x
    }
    if point.y < min_y {
      min_y = point.y
    }
    if point.x > max_x {
      max_x = point.x
    }
    if point.y > max_y {
      max_y = point.y
    }
  }
  let update_box = fn(box : @graph.Box) -> Unit {
    saw_content = true
    if box.x < min_x {
      min_x = box.x
    }
    if box.y < min_y {
      min_y = box.y
    }
    if box.x + box.width > max_x {
      max_x = box.x + box.width
    }
    if box.y + box.height > max_y {
      max_y = box.y + box.height
    }
  }

  for obj in graph.objects {
    match obj.box {
      Some(box) => update_box(box)
      None => ()
    }
    match obj.label_box {
      Some(box) => update_box(box)
      None => ()
    }
  }
  for edge in graph.edges {
    for point in edge.route {
      update_point(point)
    }
    match edge.bend_points {
      Some(points) =>
        for point in points {
          update_point(point)
        }
      None => ()
    }
    match edge.label_box {
      Some(box) => update_box(box)
      None => ()
    }
  }
  for activation in graph.activation_boxes {
    let (_, box) = activation
    update_box(box)
  }
  for note_entry in graph.sequence_notes {
    let (_, _, box) = note_entry
    update_box(box)
  }
  for fragment in graph.sequence_fragments_layout {
    match fragment.bbox {
      Some(box) => update_box(box)
      None => ()
    }
    for operand in fragment.operands {
      update_point(@graph.Point::new(0.0, operand.separator_y))
    }
  }

  (min_x, min_y, max_x, max_y, saw_content)
}

///|
fn shift_nested_sequence_graph(
  graph : @graph.GraphInput,
  dx : Double,
  dy : Double,
) -> @graph.GraphInput {
  if dx == 0.0 && dy == 0.0 {
    return graph
  }
  let root = clone_object_with_parts_and_label(
    graph.root,
    shift_box_opt(graph.root.box, dx, dy),
    shift_box_opt(graph.root.label_box, dx, dy),
    graph.root.child_ids,
  )
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    objects.push(
      clone_object_with_parts_and_label(
        obj,
        shift_box_opt(obj.box, dx, dy),
        shift_box_opt(obj.label_box, dx, dy),
        obj.child_ids,
      ),
    )
  }
  let edges : Array[@graph.EdgeInput] = []
  for edge in graph.edges {
    edges.push(
      clone_edge_with_layout(
        edge,
        shift_route(edge.route, dx, dy),
        shift_points_opt(edge.bend_points, dx, dy),
        shift_box_opt(edge.label_box, dx, dy),
      ),
    )
  }
  let activation_boxes : Array[(String, @graph.Box)] = []
  for activation in graph.activation_boxes {
    let (id, box) = activation
    activation_boxes.push((id, shift_box(box, dx, dy)))
  }
  let sequence_notes : Array[(String, String, @graph.Box)] = []
  for note_entry in graph.sequence_notes {
    let (id, note, box) = note_entry
    sequence_notes.push((id, note, shift_box(box, dx, dy)))
  }
  let sequence_fragments_layout : Array[@graph.SequenceFragmentLayout] = []
  for fragment in graph.sequence_fragments_layout {
    sequence_fragments_layout.push(shift_fragment_layout(fragment, dx, dy))
  }
  clone_graph_with_sequence_parts(
    graph, root, objects, edges, activation_boxes, sequence_notes, sequence_fragments_layout,
  )
}

///|
fn install_nested_sequence_placeholder(
  graph : @graph.GraphInput,
  container_id : String,
  placeholder_box : @graph.Box,
) -> @graph.GraphInput {
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if obj.abs_id_syntax == container_id {
      objects.push(
        clone_object_with_parts_and_effective_positions(
          obj,
          Some(placeholder_box),
          None,
          [],
          nested_layout_placeholder_classes(obj.classes),
          obj.label_position,
          obj.icon_position,
        ),
      )
    } else {
      objects.push(obj)
    }
  }
  clone_graph_with_sequence_parts(
    graph,
    graph.root,
    objects,
    graph.edges,
    graph.activation_boxes,
    graph.sequence_notes,
    graph.sequence_fragments_layout,
  )
}

///|
fn nested_layout_placeholder_classes(classes : Array[String]) -> Array[String] {
  let result : Array[String] = []
  for class_name in classes {
    result.push(class_name)
  }
  result.push("__diago_nested_layout_placeholder")
  result
}

///|
fn install_nested_sequence_placeholder_object(
  graph : @graph.GraphInput,
  placeholder : @graph.ObjectInput,
) -> @graph.GraphInput {
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    if obj.abs_id_syntax == placeholder.abs_id_syntax {
      objects.push(placeholder)
    } else {
      objects.push(obj)
    }
  }
  clone_graph_with_sequence_parts(
    graph,
    graph.root,
    objects,
    graph.edges,
    graph.activation_boxes,
    graph.sequence_notes,
    graph.sequence_fragments_layout,
  )
}

///|
fn clone_graph_with_sequence_parts(
  graph : @graph.GraphInput,
  root : @graph.ObjectInput,
  objects : Array[@graph.ObjectInput],
  edges : Array[@graph.EdgeInput],
  activation_boxes : Array[(String, @graph.Box)],
  sequence_notes : Array[(String, String, @graph.Box)],
  sequence_fragments_layout : Array[@graph.SequenceFragmentLayout],
) -> @graph.GraphInput {
  @graph.GraphInput::from_parts(
    graph.name,
    root,
    objects,
    edges,
    graph.sequence_fragments,
    activation_boxes,
    sequence_notes,
    sequence_fragments_layout,
    graph.layers,
    graph.scenarios,
    graph.steps,
    graph.legend,
    is_folder_only=graph.is_folder_only,
    data=graph.data,
  )
}

///|
const D2_NEAR_PAD_LIKE_D2 : Double = 20.0

///|
fn apply_constant_near_graphs_like_d2(
  graph : @graph.GraphInput,
  context : NestedSequenceContext,
) -> @graph.GraphInput {
  if context.constant_near_order.is_empty() {
    return graph
  }

  let mut placed_graph = graph
  let placed_near_ids : Map[String, Bool] = Map([])
  for key_set in [0, 1, 2] {
    let current_set_graphs : Array[(String, @graph.GraphInput)] = []
    for container_id in context.constant_near_order {
      let nested_graph = match
        context.constant_near_graphs_by_container_id.get(container_id) {
        Some(current) => current
        None => continue
      }
      let obj = match constant_near_root_object_like_d2(nested_graph) {
        Some(current) => current
        None => continue
      }
      let near_key = match obj.near {
        Some(raw) => raw.to_lower()
        None => continue
      }
      if !near_key_matches_set_like_d2(near_key, key_set) {
        continue
      }
      let box = match obj.box {
        Some(current) => current
        None => continue
      }
      let (min_x, min_y, max_x, max_y) = constant_near_bounding_box_like_d2(
        placed_graph, placed_near_ids,
      )
      let (x, y) = place_constant_near_box_like_d2(
        obj,
        near_key,
        min_x,
        min_y,
        max_x - min_x,
        max_y - min_y,
        box.width,
        box.height,
      )
      let shifted = shift_nested_sequence_graph(
        nested_graph,
        x - box.x,
        y - box.y,
      )
      current_set_graphs.push((container_id, shifted))
    }
    for entry in current_set_graphs {
      let (container_id, shifted) = entry
      placed_graph = inject_constant_near_graph_like_d2(placed_graph, shifted)
      placed_near_ids[container_id] = true
    }
  }
  placed_graph
}

///|
fn constant_near_root_object_like_d2(
  graph : @graph.GraphInput,
) -> @graph.ObjectInput? {
  if graph.root.child_ids.is_empty() {
    return None
  }
  build_object_by_abs_id_syntax(graph).get(graph.root.child_ids[0])
}

///|
fn inject_constant_near_graph_like_d2(
  graph : @graph.GraphInput,
  nested_graph : @graph.GraphInput,
) -> @graph.GraphInput {
  let objects : Array[@graph.ObjectInput] = []
  for obj in graph.objects {
    objects.push(obj)
  }
  for obj in nested_graph.objects {
    objects.push(obj)
  }
  let edges : Array[@graph.EdgeInput] = []
  for edge in graph.edges {
    edges.push(edge)
  }
  for edge in nested_graph.edges {
    edges.push(edge)
  }
  let activation_boxes : Array[(String, @graph.Box)] = []
  for activation in graph.activation_boxes {
    activation_boxes.push(activation)
  }
  for activation in nested_graph.activation_boxes {
    activation_boxes.push(activation)
  }
  let sequence_notes : Array[(String, String, @graph.Box)] = []
  for note in graph.sequence_notes {
    sequence_notes.push(note)
  }
  for note in nested_graph.sequence_notes {
    sequence_notes.push(note)
  }
  let sequence_fragments_layout : Array[@graph.SequenceFragmentLayout] = []
  for fragment in graph.sequence_fragments_layout {
    sequence_fragments_layout.push(fragment)
  }
  for fragment in nested_graph.sequence_fragments_layout {
    sequence_fragments_layout.push(fragment)
  }
  clone_graph_with_sequence_parts(
    graph,
    graph.root,
    objects,
    edges,
    activation_boxes,
    sequence_notes,
    sequence_fragments_layout,
  )
}

///|
fn is_constant_near_key_like_d2(key : String) -> Bool {
  match key {
    "top-left"
    | "top-center"
    | "top-right"
    | "center-left"
    | "center-right"
    | "bottom-left"
    | "bottom-center"
    | "bottom-right" => true
    _ => false
  }
}

///|
fn near_key_matches_set_like_d2(key : String, key_set : Int) -> Bool {
  match key_set {
    0 => key == "top-center" || key == "bottom-center"
    1 => key == "center-left" || key == "center-right"
    _ =>
      key == "top-left" ||
      key == "top-right" ||
      key == "bottom-left" ||
      key == "bottom-right"
  }
}

///|
fn constant_near_bounding_box_like_d2(
  graph : @graph.GraphInput,
  placed_near_ids : Map[String, Bool],
) -> (Double, Double, Double, Double) {
  let object_by_id = build_object_by_abs_id_syntax(graph)
  let parent_id_by_child_id = build_parent_id_by_child_abs_id_syntax(graph)
  let mut min_x = 1000000000000.0
  let mut min_y = 1000000000000.0
  let mut max_x = -1000000000000.0
  let mut max_y = -1000000000000.0
  let mut has_bounds = false
  let update_box = fn(box : @graph.Box) -> Unit {
    if box.x < min_x {
      min_x = box.x
    }
    if box.y < min_y {
      min_y = box.y
    }
    if box.x + box.width > max_x {
      max_x = box.x + box.width
    }
    if box.y + box.height > max_y {
      max_y = box.y + box.height
    }
    has_bounds = true
  }
  for obj in graph.objects {
    match parent_id_by_child_id.get(obj.abs_id_syntax) {
      Some(parent_id) =>
        match object_by_id.get(parent_id) {
          Some(parent) if parent.shape_type == SqlTable ||
            parent.shape_type == Class => continue
          _ => ()
        }
      None => ()
    }
    let box = match obj.box {
      Some(current) => current
      None => continue
    }
    if placed_near_ids.contains(obj.abs_id_syntax) {
      let near_key = match obj.near {
        Some(raw) => raw.to_lower()
        None => continue
      }
      match near_key {
        "top-center" | "bottom-center" => {
          if box.x < min_x {
            min_x = box.x
          }
          if box.x + box.width > max_x {
            max_x = box.x + box.width
          }
          has_bounds = true
        }
        "center-left" | "center-right" => {
          if box.y < min_y {
            min_y = box.y
          }
          if box.y + box.height > max_y {
            max_y = box.y + box.height
          }
          has_bounds = true
        }
        _ => ()
      }
      continue
    }
    if has_outer_near_container_like_d2(obj.abs_id_syntax, placed_near_ids) {
      continue
    }
    update_box(box)
    match obj.label_box {
      Some(label_box) => {
        let label_position = match obj.label_position {
          Some(current) => current
          None => ""
        }
        if !label_position.contains("INSIDE") {
          update_box(label_box)
        }
      }
      None => ()
    }
  }
  for edge in graph.edges {
    if has_outer_near_container_like_d2(edge.src_id_syntax, placed_near_ids) ||
      has_outer_near_container_like_d2(edge.dst_id_syntax, placed_near_ids) {
      continue
    }
    for point in edge.route {
      if point.x < min_x {
        min_x = point.x
      }
      if point.y < min_y {
        min_y = point.y
      }
      if point.x > max_x {
        max_x = point.x
      }
      if point.y > max_y {
        max_y = point.y
      }
      has_bounds = true
    }
  }
  if !has_bounds {
    return (0.0, 0.0, 0.0, 0.0)
  }
  if min_x > max_x {
    min_x = 0.0
    max_x = 0.0
  }
  if min_y > max_y {
    min_y = 0.0
    max_y = 0.0
  }
  (min_x, min_y, max_x, max_y)
}

///|
fn has_outer_near_container_like_d2(
  obj_id : String,
  placed_near_ids : Map[String, Bool],
) -> Bool {
  for near_id, _ in placed_near_ids {
    if obj_id != near_id && obj_id.has_prefix(near_id + ".") {
      return true
    }
  }
  false
}

///|
fn place_constant_near_box_like_d2(
  obj : @graph.ObjectInput,
  near_key : String,
  min_x : Double,
  min_y : Double,
  width : Double,
  height : Double,
  box_width : Double,
  box_height : Double,
) -> (Double, Double) {
  let (x, y) = match near_key {
    "top-left" =>
      (
        min_x - box_width - D2_NEAR_PAD_LIKE_D2,
        min_y - box_height - D2_NEAR_PAD_LIKE_D2,
      )
    "top-center" =>
      (
        min_x + width / 2.0 - box_width / 2.0,
        min_y - box_height - D2_NEAR_PAD_LIKE_D2,
      )
    "top-right" =>
      (
        min_x + width + D2_NEAR_PAD_LIKE_D2,
        min_y - box_height - D2_NEAR_PAD_LIKE_D2,
      )
    "center-left" =>
      (
        min_x - box_width - D2_NEAR_PAD_LIKE_D2,
        min_y + height / 2.0 - box_height / 2.0,
      )
    "center-right" =>
      (
        min_x + width + D2_NEAR_PAD_LIKE_D2,
        min_y + height / 2.0 - box_height / 2.0,
      )
    "bottom-left" =>
      (
        min_x - box_width - D2_NEAR_PAD_LIKE_D2,
        min_y + height + D2_NEAR_PAD_LIKE_D2,
      )
    "bottom-center" =>
      (
        min_x + width / 2.0 - box_width / 2.0,
        min_y + height + D2_NEAR_PAD_LIKE_D2,
      )
    "bottom-right" =>
      (
        min_x + width + D2_NEAR_PAD_LIKE_D2,
        min_y + height + D2_NEAR_PAD_LIKE_D2,
      )
    _ => (min_x, min_y)
  }
  adjust_constant_near_for_label_like_d2(obj, near_key, x, y)
}

///|
fn adjust_constant_near_for_label_like_d2(
  obj : @graph.ObjectInput,
  near_key : String,
  x : Double,
  y : Double,
) -> (Double, Double) {
  let label_position = match obj.label_position {
    Some(current) => current
    None =>
      match inferred_label_position_from_layout_like_d2(obj) {
        Some(current) => current
        None => return (x, y)
      }
  }
  if label_position.contains("INSIDE") {
    return (x, y)
  }
  let (label_width, label_height) = match obj.label_box {
    Some(box) => (box.width, box.height)
    None => object_label_dimensions_like_d2(obj, 0.0)
  }
  if label_width <= 0.0 || label_height <= 0.0 {
    return (x, y)
  }
  if label_position.contains("_TOP_") {
    if near_key.contains("bottom") {
      (x, y + label_height)
    } else {
      (x, y)
    }
  } else if label_position.contains("_LEFT_") {
    if near_key.contains("right") {
      (x + label_width, y)
    } else {
      (x, y)
    }
  } else if label_position.contains("_RIGHT_") {
    if near_key.contains("left") {
      (x - label_width, y)
    } else {
      (x, y)
    }
  } else if label_position.contains("_BOTTOM_") {
    if near_key.contains("top") {
      (x, y - label_height)
    } else {
      (x, y)
    }
  } else {
    (x, y)
  }
}

///|
fn route_nested_external_edge(
  edge : @graph.EdgeInput,
  object_by_id : Map[String, @graph.ObjectInput],
) -> @graph.EdgeInput {
  let route = match
    (object_by_id.get(edge.src_id_syntax), object_by_id.get(edge.dst_id_syntax)) {
    (Some(src), Some(dst)) =>
      match (src.box, dst.box) {
        (Some(src_box), Some(dst_box)) => {
          let src_layout_box = nested_external_layout_box_like_d2(src, src_box)
          let dst_layout_box = nested_external_layout_box_like_d2(dst, dst_box)
          clip_default_route_like_d2(
            [src_box.center(), dst_box.center()],
            src_layout_box,
            dst_layout_box,
          )
        }
        _ => edge.route
      }
    _ => edge.route
  }
  clone_edge_with_layout(edge, route, None, None)
}

///|
fn nested_external_layout_box_like_d2(
  obj : @graph.ObjectInput,
  box : @graph.Box,
) -> @graph.Box {
  if obj.grid_rows is None && obj.grid_columns is None {
    return box
  }
  let (label_position, icon_position) = grid_child_effective_positions_like_d2(
    obj,
  )
  let margin = grid_child_margin_like_d2(
    obj,
    box.width,
    box.height,
    label_position,
    icon_position,
  )
  @graph.Box::new(
    box.x - margin.left,
    box.y - margin.top,
    box.width + margin.left + margin.right,
    box.height + margin.top + margin.bottom,
  )
}

///|
fn clip_default_route_like_d2(
  route : Array[@graph.Point],
  src_box : @graph.Box,
  dst_box : @graph.Box,
) -> Array[@graph.Point] {
  if route.length() < 2 {
    return route
  }
  let clipped : Array[@graph.Point] = []
  for point in route {
    clipped.push(point)
  }
  clipped[0] = box_border_point_on_segment_like_d2(
    src_box,
    clipped[1],
    clipped[0],
  )
  let last_index = clipped.length() - 1
  clipped[last_index] = box_border_point_on_segment_like_d2(
    dst_box,
    clipped[last_index - 1],
    clipped[last_index],
  )
  clipped
}

///|
fn box_border_point_on_segment_like_d2(
  box : @graph.Box,
  segment_start : @graph.Point,
  segment_end : @graph.Point,
) -> @graph.Point {
  if (segment_start.x - segment_end.x).abs() <= 1.0e-9 &&
    (segment_start.y - segment_end.y).abs() <= 1.0e-9 {
    return segment_end
  }
  let top_left = @graph.Point::new(box.x, box.y)
  let top_right = @graph.Point::new(box.x + box.width, box.y)
  let bottom_right = @graph.Point::new(box.x + box.width, box.y + box.height)
  let bottom_left = @graph.Point::new(box.x, box.y + box.height)
  for
    border in [
      (top_left, top_right),
      (top_right, bottom_right),
      (bottom_right, bottom_left),
      (bottom_left, top_left),
    ] {
    let (a, b) = border
    match segment_intersection_point_like_d2(segment_start, segment_end, a, b) {
      Some(point) => return point
      None => ()
    }
  }
  segment_end
}

///|
fn segment_intersection_point_like_d2(
  u0 : @graph.Point,
  u1 : @graph.Point,
  v0 : @graph.Point,
  v1 : @graph.Point,
) -> @graph.Point? {
  let udx = u1.x - u0.x
  let vdx = v1.x - v0.x
  let uvdx = v0.x - u0.x
  let udy = u1.y - u0.y
  let vdy = v1.y - v0.y
  let uvdy = v0.y - u0.y
  let denominator = udy * vdx - udx * vdy
  if denominator == 0.0 {
    return None
  }
  let s = (vdx * uvdy - vdy * uvdx) / denominator
  let t = (udx * uvdy - udy * uvdx) / denominator
  if s < 0.0 || s > 1.0 || t < 0.0 || t > 1.0 {
    return None
  }
  Some(
    @graph.Point::new(
      u0.x + round_like_go(s * udx),
      u0.y + round_like_go(s * udy),
    ),
  )
}

///|
fn round_like_go(value : Double) -> Double {
  if value >= 0.0 {
    (value + 0.5).to_int().to_double()
  } else {
    (value - 0.5).to_int().to_double()
  }
}

///|
fn shift_fragment_layout(
  fragment : @graph.SequenceFragmentLayout,
  dx : Double,
  dy : Double,
) -> @graph.SequenceFragmentLayout {
  let operands : Array[@graph.FragmentOperandLayout] = []
  for operand in fragment.operands {
    operands.push(
      @graph.FragmentOperandLayout::new(
        operand.condition,
        operand.separator_y + dy,
      ),
    )
  }
  @graph.SequenceFragmentLayout::new(
    fragment.fragment_type,
    fragment.condition,
    shift_box_opt(fragment.bbox, dx, dy),
    fragment.start_index,
    fragment.end_index,
    operands,
  )
}

///|
fn clone_nested_sequence_root(obj : @graph.ObjectInput) -> @graph.ObjectInput {
  @graph.ObjectInput::from_parts(
    obj.id,
    obj.label,
    obj.shape_type,
    obj.style,
    None,
    None,
    obj.child_ids,
    obj.z_index,
    obj.icon,
    obj.tooltip,
    obj.link,
    obj.classes,
    obj.grid_rows,
    obj.grid_columns,
    obj.grid_gap,
    obj.horizontal_gap,
    obj.vertical_gap,
    obj.grid_column_span,
    obj.grid_row_span,
    None,
    None,
    None,
    direction=obj.direction,
    language=obj.language,
    sql_constraints=obj.sql_constraints,
    id_val=obj.id_val,
    id_syntax=obj.id_syntax,
    abs_id_syntax=obj.abs_id_syntax,
    references=obj.references,
    icon_position=obj.icon_position,
    tooltip_position=obj.tooltip_position,
    label_position=obj.label_position,
    grid_row_directed=obj.grid_row_directed,
  )
}

///|
fn clone_edge_with_layout(
  edge : @graph.EdgeInput,
  route : Array[@graph.Point],
  bend_points : Array[@graph.Point]?,
  label_box : @graph.Box?,
) -> @graph.EdgeInput {
  @graph.EdgeInput::from_parts(
    edge.index,
    edge.src_id,
    edge.dst_id,
    edge.src_arrow,
    edge.dst_arrow,
    edge.src_arrowhead,
    edge.dst_arrowhead,
    edge.src_arrowhead_label,
    edge.dst_arrowhead_label,
    edge.src_arrowhead_label_color,
    edge.dst_arrowhead_label_color,
    edge.src_anchor,
    edge.dst_anchor,
    edge.label,
    edge.style,
    route,
    bend_points,
    edge.is_curve,
    edge.z_index,
    edge.reference_count,
    label_box,
    edge.src_column_index,
    edge.dst_column_index,
    src_id_syntax=edge.src_id_syntax,
    dst_id_syntax=edge.dst_id_syntax,
    references=edge.references,
    icon=edge.icon,
    icon_position=edge.icon_position,
    icon_border_radius=edge.icon_border_radius,
    link=edge.link,
    classes=edge.classes,
  )
}

///|
fn shift_points_opt(
  points : Array[@graph.Point]?,
  dx : Double,
  dy : Double,
) -> Array[@graph.Point]? {
  match points {
    Some(values) => Some(shift_route(values, dx, dy))
    None => None
  }
}

///|
fn shift_route(
  route : Array[@graph.Point],
  dx : Double,
  dy : Double,
) -> Array[@graph.Point] {
  let shifted : Array[@graph.Point] = []
  for point in route {
    shifted.push(@graph.Point::new(point.x + dx, point.y + dy))
  }
  shifted
}

///|
fn shift_box_opt(box : @graph.Box?, dx : Double, dy : Double) -> @graph.Box? {
  match box {
    Some(value) => Some(shift_box(value, dx, dy))
    None => None
  }
}

///|
fn shift_box(box : @graph.Box, dx : Double, dy : Double) -> @graph.Box {
  @graph.Box::new(box.x + dx, box.y + dy, box.width, box.height)
}