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

///|
/// Visibility propagation systems (Bevy-aligned).
///
/// We follow Bevy's semantics from `bevy_camera::visibility`:
/// - Visibility::Hidden   => inherited=false
/// - Visibility::Visible  => inherited=true (even if parent is hidden)
/// - Visibility::Inherited => inherited=parent_inherited (or true if parent is missing the required components)
///
/// Current stage-1 behavior: ViewVisibility is set equal to InheritedVisibility
/// (no frustum culling yet).

///|
pub fn[
  W : Has_Visibility + Has_InheritedVisibility + Has_ViewVisibility + @hierarchy.Has_Parent + @hierarchy.Has_Children + @core.ChangeTickWorld,
] visibility_propagate_system(
  world : W,
) -> Unit {
  let visibility_store = Has_Visibility::get_visibility_store(world)
  let inherited_store = Has_InheritedVisibility::get_inherited_visibility_store(
    world,
  )
  let view_store = Has_ViewVisibility::get_view_visibility_store(world)
  let parent_store = @hierarchy.Has_Parent::get_parent_store(world)
  let children_store = @hierarchy.Has_Children::get_children_store(world)
  let change_tick = @core.ChangeTickWorld::read_change_tick(world)

  // Identify roots: entities that either have no parent, or whose parent lacks
  // the required visibility components (Bevy fallback).
  let roots : Array[@core.Entity] = []
  visibility_store.for_each(fn(entity, _v) {
    match parent_store.get(entity) {
      None => roots.push(entity)
      Some(parent) => {
        let p = parent.entity()
        if !visibility_store.contains(p) || !inherited_store.contains(p) {
          roots.push(entity)
        }
      }
    }
  })

  // DFS traversal from roots, propagating inherited visibility.
  let stack : Array[(@core.Entity, Bool)] = []
  for root in roots {
    stack.push((root, true))
  }
  while stack.length() > 0 {
    let (entity, parent_visible) = stack.pop().unwrap()
    guard visibility_store.get(entity) is Some(visibility) else { continue }
    let is_visible = match visibility {
      Visibility::Hidden => false
      Visibility::Visible => true
      Visibility::Inherited => parent_visible
    }

    // Update InheritedVisibility (insert if missing).
    let needs_inherited_update = match inherited_store.get(entity) {
      Some(v) => v.is_visible() != is_visible
      None => true
    }
    if needs_inherited_update {
      let v = InheritedVisibility::new(is_visible)
      if inherited_store.contains(entity) {
        inherited_store.set(entity, v, change_tick) |> ignore
      } else {
        inherited_store.insert(entity, v, change_tick)
      }
    }

    // Stage-1: ViewVisibility == InheritedVisibility (no culling yet).
    let needs_view_update = match view_store.get(entity) {
      Some(v) => v.is_visible() != is_visible
      None => true
    }
    if needs_view_update {
      let v = ViewVisibility::new(is_visible)
      if view_store.contains(entity) {
        view_store.set(entity, v, change_tick) |> ignore
      } else {
        view_store.insert(entity, v, change_tick)
      }
    }

    // Recurse to children that participate in visibility (same as Bevy's query
    // constraints: With, With).
    if children_store.get(entity) is Some(children) {
      for child in children.entities() {
        if visibility_store.contains(child) {
          stack.push((child, is_visible))
        }
      }
    }
  }
}

///|
pub fn[
  W : Has_Visibility + Has_InheritedVisibility + Has_ViewVisibility + @hierarchy.Has_Parent + @hierarchy.Has_Children + @core.ChangeTickWorld,
] visibility_plugin(
  app : @app.App[W],
) -> @app.App[W] {
  // Must run after `transform.update_children_system` so `Children` is in sync.
  app.add_pre_update_system(visibility_propagate_system)
}