// 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 edge_property_map(
  path : @ast.KeyPath,
  primary : Scalar?,
  value_map : Map?,
) -> Map {
  let root = Map::new()
  let mut current = root
  for i, segment in path.path {
    let child = if i == path.path.length() - 1 {
      value_map
    } else {
      Some(Map::new())
    }
    current.add_field(
      Field::new(
        segment.content(),
        if i == path.path.length() - 1 {
          primary
        } else {
          None
        },
        match child {
          Some(m) => Some(Map(m))
          None => None
        },
        [],
        name_syntax=Some(segment),
      ),
    )
    if i < path.path.length() - 1 {
      current = child.unwrap()
    }
  }
  root
}

///|
fn merge_edge_maps(base : Map?, patch : Map?, patch_overrides : Bool) -> Map? {
  match (base, patch) {
    (Some(base), Some(patch)) =>
      if patch_overrides {
        Some(base.overlay(patch))
      } else {
        let merged = base.overlay(Map::new())
        merge_missing_edge_properties(merged, patch)
        Some(merged)
      }
    (Some(base), None) => Some(base)
    (None, Some(patch)) => Some(patch)
    (None, None) => None
  }
}

///|
fn merge_missing_edge_properties(target : Map, patch : Map) -> Unit {
  for patch_field in patch.fields {
    match target.get_field(patch_field.name) {
      Some(existing) =>
        match (existing.composite, patch_field.composite) {
          (Some(Map(existing_map)), Some(Map(patch_map))) =>
            merge_missing_edge_properties(existing_map, patch_map)
          _ => ()
        }
      None => target.add_field(clone_field(patch_field))
    }
  }
}

///|
fn glob_edge_patch_follows_existing(
  existing : Edge?,
  glob_offset : Int,
) -> Bool {
  guard existing is Some(edge) else { return true }
  for i = edge.references.length() - 1; i >= 0; i = i - 1 {
    let reference = edge.references[i]
    if !reference.due_to_glob {
      return glob_offset >= reference.range.start.offset
    }
  }
  true
}

///|
fn apply_edge_value(
  existing : Edge?,
  edge_key : @ast.KeyPath?,
  patch_primary : Scalar?,
  patch_map : Map?,
  patch_overrides? : Bool = true,
) -> (Scalar?, Map?) {
  let existing_primary = match existing {
    Some(edge) => edge.primary
    None => None
  }
  let existing_map = match existing {
    Some(edge) => edge.map
    None => None
  }
  match edge_key {
    Some(path) => {
      let property_map = edge_property_map(path, patch_primary, patch_map)
      (
        existing_primary,
        merge_edge_maps(existing_map, Some(property_map), patch_overrides),
      )
    }
    None =>
      (
        match patch_primary {
          Some(primary) => Some(primary)
          None => existing_primary
        },
        merge_edge_maps(existing_map, patch_map, patch_overrides),
      )
  }
}