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

///|
priv enum BoardCompiler {
  PlainCompiler
  ImportCompiler(ImportResolver)
}

///|
fn compile_board_patch(
  compiler : BoardCompiler,
  target : Map,
  patch : Map,
) -> Bool raise CompileError {
  guard patch.ast is Some(Map(ast)) else { return false }
  match compiler {
    PlainCompiler =>
      compile_map_nodes(target, ast, ast.nodes, source_path=patch.source_path)
    ImportCompiler(resolver) =>
      compile_map_nodes_with_imports(
        target,
        ast,
        ast.nodes,
        [],
        resolver,
        [],
        source_path=patch.source_path,
      )
  }
  true
}

///|
fn is_unresolved_indexed_edge(edge : Edge) -> Bool {
  guard edge.references.get(0) is Some(reference) else { return false }
  guard reference.context is Some(context) else { return false }
  guard context.key is Some(key) else { return false }
  match key.edge_index {
    Some(index) => !index.glob
    None => false
  }
}

///|
fn inheritable_board_map(map : Map) -> Map {
  let inherited = map.copy()
  let _ = inherited.delete_field("label")
  let _ = inherited.delete_field("layers")
  let _ = inherited.delete_field("scenarios")
  let _ = inherited.delete_field("steps")
  inherited
}

///|
fn field_with_map(field : Field, map : Map) -> Field {
  {
    name: field.name,
    name_syntax: field.name_syntax(),
    primary: field.primary,
    composite: Some(Map(map)),
    references: field.references,
    import_ast: field.import_ast,
    path: field.path,
  }
}

///|
fn materialize_board(
  compiler : BoardCompiler,
  base : Map,
  field : Field,
  inherits_base : Bool,
) -> Field raise CompileError {
  let patch = match field.composite {
    Some(Map(map)) => map
    _ => Map::new()
  }
  let materialized = if inherits_base {
    inheritable_board_map(base)
  } else {
    Map::new()
  }
  let materialized = if compile_board_patch(compiler, materialized, patch) {
    materialized
  } else {
    materialized.overlay(patch)
  }
  resolve_boards_in_map(compiler, materialized)
  field_with_map(field, materialized)
}

///|
fn materialize_board_field(
  compiler : BoardCompiler,
  board : Map,
  field_name : String,
  inherits_base : Bool,
  sequential : Bool,
) -> Unit raise CompileError {
  guard board.get_field(field_name) is Some(container_field) else { return }
  guard container_field.composite is Some(Map(container)) else { return }
  let resolved_container = clone_map(container)
  let mut previous = board
  for i, field in container.fields {
    let base = if sequential { previous } else { board }
    let resolved = materialize_board(compiler, base, field, inherits_base)
    resolved_container.fields[i] = resolved
    if sequential {
      previous = resolved.map().unwrap()
    }
  }
  board.set_field(field_with_map(container_field, resolved_container))
}

///|
fn resolve_boards_in_map(
  compiler : BoardCompiler,
  board : Map,
) -> Unit raise CompileError {
  materialize_board_field(compiler, board, "layers", false, false)
  materialize_board_field(compiler, board, "scenarios", true, false)
  materialize_board_field(compiler, board, "steps", true, true)
}

///|
fn validate_indexed_edges(map : Map) -> Unit raise CompileError {
  for edge in map.edges {
    if is_unresolved_indexed_edge(edge) {
      raise InvalidKey("indexed edge does not exist")
    }
    match edge.map {
      Some(child) => validate_indexed_edges(child)
      None => ()
    }
  }
  for field in map.fields {
    match field.composite {
      Some(Map(child)) => validate_indexed_edges(child)
      _ => ()
    }
  }
}

///|
fn resolve_boards(map : Map) -> Unit raise CompileError {
  resolve_boards_in_map(PlainCompiler, map)
  validate_indexed_edges(map)
}

///|
fn resolve_boards_with_imports(
  map : Map,
  resolver : ImportResolver,
) -> Unit raise CompileError {
  resolve_boards_in_map(ImportCompiler(resolver), map)
  validate_indexed_edges(map)
}