// Moonlight - トポロジー解析
// 要素間の接続グラフ構築・パス探索・接続距離計算

// ============================================================
// グラフ構造
// ============================================================

///|
/// グラフのエッジ(接続)
pub(all) struct Edge {
  from_id : String // 接続元要素 ID
  to_id : String // 接続先要素 ID
  line_id : String // この接続を表す Line 要素の ID
  from_anchor : Anchor // 接続元のアンカー
  to_anchor : Anchor // 接続先のアンカー
} derive(Show, Eq)

///|
/// 接続グラフ(隣接リスト形式)
pub(all) struct ConnectionGraph {
  // element_id -> 接続しているエッジのリスト
  adjacency : Map[String, Array[Edge]]
  // 全エッジリスト
  edges : Array[Edge]
  // Line 要素 ID -> その Line が表すエッジ
  line_to_edge : Map[String, Edge]
} derive(Show)

///|
/// 空のグラフを作成
pub fn ConnectionGraph::new() -> ConnectionGraph {
  { adjacency: {}, edges: [], line_to_edge: {} }
}

///|
/// 要素配列からグラフを構築
pub fn build_connection_graph(elements : Array[Element]) -> ConnectionGraph {
  let graph = ConnectionGraph::new()
  for el in elements {
    // Line または Path 要素のみ処理
    let is_connectable = match el.shape {
      Line(_, _) => true
      Path(_, _, _, _, _) => true
      _ => false
    }
    guard is_connectable else { continue }
    guard el.connections is Some(conns) else { continue }

    // 始点と終点の両方に接続がある場合、エッジを作成
    match (conns.start, conns.end) {
      (Some(start_conn), Some(end_conn)) => {
        // 双方向のエッジを作成
        let edge1 : Edge = {
          from_id: start_conn.element_id,
          to_id: end_conn.element_id,
          line_id: el.id,
          from_anchor: start_conn.anchor,
          to_anchor: end_conn.anchor,
        }
        let edge2 : Edge = {
          from_id: end_conn.element_id,
          to_id: start_conn.element_id,
          line_id: el.id,
          from_anchor: end_conn.anchor,
          to_anchor: start_conn.anchor,
        }

        // 隣接リストに追加
        add_edge_to_adjacency(graph.adjacency, edge1)
        add_edge_to_adjacency(graph.adjacency, edge2)

        // エッジリストに追加(一方向のみ)
        graph.edges.push(edge1)

        // Line ID -> Edge マッピング
        graph.line_to_edge[el.id] = edge1
      }
      _ => ()
    }
  }
  graph
}

///|
fn add_edge_to_adjacency(
  adjacency : Map[String, Array[Edge]],
  edge : Edge,
) -> Unit {
  match adjacency.get(edge.from_id) {
    Some(edges) => edges.push(edge)
    None => adjacency[edge.from_id] = [edge]
  }
}

// ============================================================
// 接続検出
// ============================================================

///|
/// 指定要素に直接接続している要素の ID リストを取得
pub fn ConnectionGraph::get_neighbors(
  self : ConnectionGraph,
  element_id : String,
) -> Array[String] {
  match self.adjacency.get(element_id) {
    Some(edges) => {
      let ids : Array[String] = []
      for edge in edges {
        if not(ids.contains(edge.to_id)) {
          ids.push(edge.to_id)
        }
      }
      ids
    }
    None => []
  }
}

///|
/// 2つの要素が直接接続されているか判定
pub fn ConnectionGraph::are_directly_connected(
  self : ConnectionGraph,
  id1 : String,
  id2 : String,
) -> Bool {
  self.get_neighbors(id1).contains(id2)
}

///|
/// 指定要素に接続している全ての Line ID を取得
pub fn ConnectionGraph::get_connected_lines(
  self : ConnectionGraph,
  element_id : String,
) -> Array[String] {
  match self.adjacency.get(element_id) {
    Some(edges) => {
      let line_ids : Array[String] = []
      for edge in edges {
        if not(line_ids.contains(edge.line_id)) {
          line_ids.push(edge.line_id)
        }
      }
      line_ids
    }
    None => []
  }
}

///|
/// 連結成分を検出(同じグループに属する要素を取得)
pub fn ConnectionGraph::get_connected_component(
  self : ConnectionGraph,
  start_id : String,
) -> Array[String] {
  let visited : Array[String] = []
  let queue : Array[String] = [start_id]
  while queue.length() > 0 {
    let current = queue.remove(0)
    if visited.contains(current) {
      continue
    }
    visited.push(current)
    for neighbor in self.get_neighbors(current) {
      if not(visited.contains(neighbor)) {
        queue.push(neighbor)
      }
    }
  }
  visited
}

///|
/// グラフ内の全ての連結成分を取得
pub fn ConnectionGraph::get_all_components(
  self : ConnectionGraph,
) -> Array[Array[String]] {
  let components : Array[Array[String]] = []
  let visited : Array[String] = []
  for node_id in self.adjacency.keys() {
    if not(visited.contains(node_id)) {
      let component = self.get_connected_component(node_id)
      for id in component {
        if not(visited.contains(id)) {
          visited.push(id)
        }
      }
      components.push(component)
    }
  }
  components
}

// ============================================================
// パス探索(BFS)
// ============================================================

///|
/// パス(経路)情報
pub(all) struct Path {
  nodes : Array[String] // 経路上のノード ID(開始から終了まで)
  edges : Array[Edge] // 経路上のエッジ
  total_distance : Double // 合計距離
} derive(Show, Eq)

///|
/// 2点間の最短パスを探索(BFS)
/// 見つからない場合は None
pub fn ConnectionGraph::find_shortest_path(
  self : ConnectionGraph,
  from_id : String,
  to_id : String,
  elements : Array[Element],
) -> Path? {
  if from_id == to_id {
    return Some({ nodes: [from_id], edges: [], total_distance: 0.0 })
  }

  // BFS で探索
  // 各ノードへの親情報を記録
  let parent : Map[String, (String, Edge)] = {} // node_id -> (parent_id, edge)
  let visited : Array[String] = []
  let queue : Array[String] = [from_id]
  visited.push(from_id)
  while queue.length() > 0 {
    let current = queue.remove(0)
    if current == to_id {
      // パスが見つかった - 経路を再構築
      return Some(reconstruct_path(from_id, to_id, parent, elements))
    }
    match self.adjacency.get(current) {
      Some(edges) =>
        for edge in edges {
          if not(visited.contains(edge.to_id)) {
            visited.push(edge.to_id)
            parent[edge.to_id] = (current, edge)
            queue.push(edge.to_id)
          }
        }
      None => ()
    }
  }
  None // パスが見つからない
}

///|
fn reconstruct_path(
  from_id : String,
  to_id : String,
  parent : Map[String, (String, Edge)],
  elements : Array[Element],
) -> Path {
  let nodes : Array[String] = []
  let edges : Array[Edge] = []
  let mut current = to_id

  // to_id から from_id まで逆順にたどる
  while current != from_id {
    nodes.push(current)
    match parent.get(current) {
      Some((prev, edge)) => {
        edges.push(edge)
        current = prev
      }
      None => break
    }
  }
  nodes.push(from_id)

  // 逆順にする
  nodes.rev_in_place()
  edges.rev_in_place()

  // 距離を計算
  let total_distance = calc_path_distance(nodes, elements)
  { nodes, edges, total_distance }
}

///|
/// パスの合計距離を計算(ユークリッド距離)
fn calc_path_distance(
  node_ids : Array[String],
  elements : Array[Element],
) -> Double {
  if node_ids.length() < 2 {
    return 0.0
  }
  let mut total = 0.0
  for i = 0; i < node_ids.length() - 1; i = i + 1 {
    let p1 = get_element_center(elements, node_ids[i])
    let p2 = get_element_center(elements, node_ids[i + 1])
    match (p1, p2) {
      (Some(c1), Some(c2)) => {
        let dx = c2.x - c1.x
        let dy = c2.y - c1.y
        total = total + (dx * dx + dy * dy).sqrt()
      }
      _ => ()
    }
  }
  total
}

///|
fn get_element_center(elements : Array[Element], id : String) -> Point? {
  for el in elements {
    if el.id == id {
      let bbox = el.bounding_box()
      return Some({
        x: bbox.x + bbox.width / 2.0,
        y: bbox.y + bbox.height / 2.0,
      })
    }
  }
  None
}

// ============================================================
// 接続距離
// ============================================================

///|
/// 2点間の接続距離(エッジ数)
/// 接続されていない場合は -1
pub fn ConnectionGraph::connection_distance(
  self : ConnectionGraph,
  from_id : String,
  to_id : String,
) -> Int {
  if from_id == to_id {
    return 0
  }

  // BFS でホップ数をカウント
  let distance : Map[String, Int] = {}
  distance[from_id] = 0
  let queue : Array[String] = [from_id]
  while queue.length() > 0 {
    let current = queue.remove(0)
    let current_dist = distance.get(current).unwrap_or(0)
    if current == to_id {
      return current_dist
    }
    for neighbor in self.get_neighbors(current) {
      if distance.get(neighbor) is None {
        distance[neighbor] = current_dist + 1
        queue.push(neighbor)
      }
    }
  }
  -1 // 接続されていない
}

///|
/// 指定要素から各要素への接続距離マップを取得
pub fn ConnectionGraph::distance_map_from(
  self : ConnectionGraph,
  start_id : String,
) -> Map[String, Int] {
  let distances : Map[String, Int] = {}
  distances[start_id] = 0
  let queue : Array[String] = [start_id]
  while queue.length() > 0 {
    let current = queue.remove(0)
    let current_dist = distances.get(current).unwrap_or(0)
    for neighbor in self.get_neighbors(current) {
      if distances.get(neighbor) is None {
        distances[neighbor] = current_dist + 1
        queue.push(neighbor)
      }
    }
  }
  distances
}

// ============================================================
// 部分移動時の再計算
// ============================================================

///|
/// 移動対象の情報
pub(all) struct MoveTarget {
  id : String
  new_x : Double
  new_y : Double
} derive(Show, Eq)

///|
/// 再計算結果
pub(all) struct RecalculationResult {
  // 更新が必要な要素の新座標
  element_updates : Array[MoveTarget]
  // 更新が必要な Line の新座標
  line_updates : Array[LineCoords]
} derive(Show, Eq)

///|
/// 部分移動時の全体再計算
/// moved_targets: 移動した要素のリスト
/// elements: 全要素
/// 戻り値: 接続されている他の要素・ラインの更新情報
pub fn recalculate_on_partial_move(
  moved_targets : Array[MoveTarget],
  elements : Array[Element],
) -> RecalculationResult {
  let element_updates : Array[MoveTarget] = []
  let line_updates : Array[LineCoords] = []

  // 移動した要素の ID セット
  let moved_ids : Array[String] = moved_targets.map(fn(t) { t.id })

  // 移動後の座標マップを作成
  let new_positions : Map[String, (Double, Double)] = {}
  for target in moved_targets {
    new_positions[target.id] = (target.new_x, target.new_y)
  }

  // 仮想的な更新後要素を作成
  let updated_elements = elements.map(fn(el) {
    match new_positions.get(el.id) {
      Some((nx, ny)) => { ..el, x: nx, y: ny }
      None => el
    }
  })

  // 各 Line 要素を確認
  for el in elements {
    guard el.shape is Line(x2, y2) else { continue }
    guard el.connections is Some(conns) else { continue }

    // この Line 自体が移動対象の場合はスキップ
    if moved_ids.contains(el.id) {
      continue
    }
    let mut new_x = el.x
    let mut new_y = el.y
    let mut new_x2 = x2
    let mut new_y2 = y2
    let mut needs_update = false

    // 始点の接続先が移動した場合
    match conns.start {
      Some(conn) if moved_ids.contains(conn.element_id) =>
        // 更新後の要素からアンカー位置を取得
        for updated_el in updated_elements {
          if updated_el.id == conn.element_id {
            let p = updated_el.get_anchor_point(conn.anchor)
            new_x = p.x
            new_y = p.y
            needs_update = true
            break
          }
        }
      _ => ()
    }

    // 終点の接続先が移動した場合
    match conns.end {
      Some(conn) if moved_ids.contains(conn.element_id) =>
        for updated_el in updated_elements {
          if updated_el.id == conn.element_id {
            let p = updated_el.get_anchor_point(conn.anchor)
            new_x2 = p.x
            new_y2 = p.y
            needs_update = true
            break
          }
        }
      _ => ()
    }
    if needs_update {
      line_updates.push({
        id: el.id,
        x: new_x,
        y: new_y,
        x2: new_x2,
        y2: new_y2,
      })
    }
  }

  // 子要素の更新(parent_id が移動対象の場合)
  for el in elements {
    guard el.parent_id is Some(parent_id) else { continue }

    // 親が移動対象の場合
    if moved_ids.contains(parent_id) {
      // 親の移動差分を計算
      let mut dx = 0.0
      let mut dy = 0.0
      for old_el in elements {
        if old_el.id == parent_id {
          match new_positions.get(parent_id) {
            Some((nx, ny)) => {
              dx = nx - old_el.x
              dy = ny - old_el.y
            }
            None => ()
          }
          break
        }
      }

      // 子要素自体が移動対象でない場合のみ更新
      if not(moved_ids.contains(el.id)) {
        element_updates.push({ id: el.id, new_x: el.x + dx, new_y: el.y + dy })
      }
    }
  }
  { element_updates, line_updates }
}

// ============================================================
// トポロジー解析ユーティリティ
// ============================================================

///|
/// 要素が「ハブ」(3つ以上の接続を持つ)かどうか判定
pub fn ConnectionGraph::is_hub(
  self : ConnectionGraph,
  element_id : String,
) -> Bool {
  self.get_neighbors(element_id).length() >= 3
}

///|
/// 要素が「リーフ」(1つの接続のみ持つ)かどうか判定
pub fn ConnectionGraph::is_leaf(
  self : ConnectionGraph,
  element_id : String,
) -> Bool {
  self.get_neighbors(element_id).length() == 1
}

///|
/// 要素が「孤立」(接続なし)かどうか判定
pub fn ConnectionGraph::is_isolated(
  self : ConnectionGraph,
  element_id : String,
) -> Bool {
  self.get_neighbors(element_id).length() == 0
}

///|
/// グラフの統計情報
pub(all) struct GraphStats {
  node_count : Int // ノード数
  edge_count : Int // エッジ数(Line の数)
  component_count : Int // 連結成分数
  hub_count : Int // ハブノード数
  leaf_count : Int // リーフノード数
  isolated_count : Int // 孤立ノード数
} derive(Show, Eq)

///|
/// グラフの統計を取得
pub fn ConnectionGraph::get_stats(
  self : ConnectionGraph,
  all_element_ids : Array[String],
) -> GraphStats {
  let mut hub_count = 0
  let mut leaf_count = 0
  let mut isolated_count = 0
  for id in all_element_ids {
    let neighbor_count = self.get_neighbors(id).length()
    if neighbor_count >= 3 {
      hub_count = hub_count + 1
    } else if neighbor_count == 1 {
      leaf_count = leaf_count + 1
    } else if neighbor_count == 0 {
      isolated_count = isolated_count + 1
    }
  }
  {
    node_count: self.adjacency.length(),
    edge_count: self.edges.length(),
    component_count: self.get_all_components().length(),
    hub_count,
    leaf_count,
    isolated_count,
  }
}

///|
/// 循環(サイクル)検出
/// グラフに循環がある場合 true
pub fn ConnectionGraph::has_cycle(self : ConnectionGraph) -> Bool {
  let visited : Array[String] = []
  for start_id in self.adjacency.keys() {
    if visited.contains(start_id) {
      continue
    }

    // DFS でサイクル検出
    let stack : Array[(String, String?)] = [(start_id, None)] // (node, parent)
    while stack.length() > 0 {
      let (current, parent) = stack.unsafe_pop()
      if visited.contains(current) {
        return true // サイクル検出
      }
      visited.push(current)
      for neighbor in self.get_neighbors(current) {
        // 親ノードへの戻りは無視
        match parent {
          Some(p) if p == neighbor => continue
          _ => ()
        }
        if not(visited.contains(neighbor)) {
          stack.push((neighbor, Some(current)))
        } else {
          return true // サイクル検出
        }
      }
    }
  }
  false
}

///|
/// 2点間の全パスを探索(制限付き)
/// max_paths: 最大パス数
pub fn ConnectionGraph::find_all_paths(
  self : ConnectionGraph,
  from_id : String,
  to_id : String,
  elements : Array[Element],
  max_paths : Int,
) -> Array[Path] {
  let paths : Array[Path] = []
  if from_id == to_id {
    paths.push({ nodes: [from_id], edges: [], total_distance: 0.0 })
    return paths
  }

  // DFS で全パス探索
  fn dfs(
    current : String,
    target : String,
    visited : Array[String],
    current_nodes : Array[String],
    current_edges : Array[Edge],
    graph : ConnectionGraph,
    elements : Array[Element],
    paths : Array[Path],
    max_paths : Int,
  ) -> Unit {
    if paths.length() >= max_paths {
      return
    }
    if current == target {
      let total_distance = calc_path_distance(current_nodes, elements)
      paths.push({
        nodes: current_nodes.copy(),
        edges: current_edges.copy(),
        total_distance,
      })
      return
    }
    match graph.adjacency.get(current) {
      Some(edges) =>
        for edge in edges {
          if not(visited.contains(edge.to_id)) {
            visited.push(edge.to_id)
            current_nodes.push(edge.to_id)
            current_edges.push(edge)
            dfs(
              edge.to_id,
              target,
              visited,
              current_nodes,
              current_edges,
              graph,
              elements,
              paths,
              max_paths,
            )
            ignore(visited.pop())
            ignore(current_nodes.pop())
            ignore(current_edges.pop())
          }
        }
      None => ()
    }
  }

  let visited : Array[String] = [from_id]
  let current_nodes : Array[String] = [from_id]
  let current_edges : Array[Edge] = []
  dfs(
    from_id, to_id, visited, current_nodes, current_edges, self, elements, paths,
    max_paths,
  )
  paths
}