// 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.

///|
fn field_suspension(field : Field) -> Bool? {
  let mut state : Bool? = None
  for reference in field.references {
    match reference.context() {
      Some(context) =>
        match context.key {
          Some(key) =>
            match key_suspension(key) {
              Some(value) => state = Some(value)
              None => ()
            }
          None => ()
        }
      None => ()
    }
  }
  state
}

///|
fn edge_suspension(edge : Edge) -> Bool? {
  let mut state : Bool? = None
  for reference in edge.references {
    match reference.context() {
      Some(context) =>
        match context.key {
          Some(key) =>
            match key_suspension(key) {
              Some(value) => state = Some(value)
              None => ()
            }
          None => ()
        }
      None => ()
    }
  }
  state
}

///|
fn suspension_path_key(path : Array[String]) -> String {
  let out = StringBuilder::new()
  for segment in path {
    out.write_string(segment.length().to_string())
    out.write_char(':')
    out.write_string(segment)
    out.write_char(';')
  }
  out.to_string()
}

///|
fn append_suspension_path(
  prefix : Array[String],
  suffix : Array[String],
) -> Array[String] {
  let path = prefix.copy()
  for segment in suffix {
    path.push(segment)
  }
  path
}

///|
fn force_path_and_ancestors(
  forced : Array[String],
  path : Array[String],
) -> Unit {
  let prefix : Array[String] = []
  for segment in path {
    prefix.push(segment)
    let key = suspension_path_key(prefix)
    if !forced.contains(key) {
      forced.push(key)
    }
  }
}

///|
fn collect_unsuspended_edge_endpoints(
  ir : Map,
  prefix : Array[String],
  forced : Array[String],
) -> Unit {
  for edge in ir.edges {
    if edge_suspension(edge) == Some(false) {
      force_path_and_ancestors(
        forced,
        append_suspension_path(prefix, edge.id.src_path),
      )
      force_path_and_ancestors(
        forced,
        append_suspension_path(prefix, edge.id.dst_path),
      )
    }
  }
  for field in ir.fields {
    match field.composite {
      Some(Map(child)) => {
        let child_prefix = prefix.copy()
        child_prefix.push(field.name)
        collect_unsuspended_edge_endpoints(child, child_prefix, forced)
      }
      _ => ()
    }
  }
}

///|
fn remove_suspended_nodes(
  ir : Map,
  prefix : Array[String],
  forced : Array[String],
) -> Unit {
  for i = ir.fields.length() - 1; i >= 0; i = i - 1 {
    let field = ir.fields[i]
    let field_path = prefix.copy()
    field_path.push(field.name)
    if field_suspension(field) == Some(true) &&
      !forced.contains(suspension_path_key(field_path)) {
      let _ = ir.fields.remove(i)
      continue
    }
    match field.composite {
      Some(Map(child)) => remove_suspended_nodes(child, field_path, forced)
      _ => ()
    }
  }
  for i = ir.edges.length() - 1; i >= 0; i = i - 1 {
    let edge = ir.edges[i]
    if edge_suspension(edge) == Some(true) ||
      !suspension_endpoint_exists(ir, edge.id.src_path_syntax()) ||
      !suspension_endpoint_exists(ir, edge.id.dst_path_syntax()) {
      let _ = ir.edges.remove(i)
    }
  }
}

///|
fn suspension_endpoint_exists(ir : Map, path : Array[@ast.StringValue]) -> Bool {
  if path.is_empty() {
    return false
  }
  let mut current = ir
  for i, syntax in path {
    match current.get_field_by_syntax(syntax) {
      Some(field) =>
        if i == path.length() - 1 {
          return true
        } else {
          match field.composite {
            Some(Map(child)) => current = child
            _ => return false
          }
        }
      None => return false
    }
  }
  false
}

///|
fn apply_suspensions(ir : Map) -> Unit {
  let forced : Array[String] = []
  collect_unsuspended_edge_endpoints(ir, [], forced)
  remove_suspended_nodes(ir, [], forced)
}