// Copyright 2026 Ling71671
//
// 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.

///|
/// Subsystem managing floating window stacking, dragging, draw batches, and z-ordering.
pub struct WindowManager {
  positions : @hashmap.HashMap[Id, @math.Vec2]
  batches : Array[(Id, @draw.DrawList)]
  drawn : Array[Id]
  focus : Array[Id]
  mut active_z : Int
  rects : Array[(@math.Rect, Int)]
  prev_rects : Array[(@math.Rect, Int)]
} derive(Eq, Debug)

///|
pub fn WindowManager::new() -> WindowManager {
  {
    positions: @hashmap.HashMap([], capacity=32),
    batches: [],
    drawn: [],
    focus: [],
    active_z: -1,
    rects: [],
    prev_rects: [],
  }
}

///|
pub fn WindowManager::begin_frame(self : WindowManager) -> Unit {
  self.drawn.clear()
  self.prev_rects.clear()
  for item in self.rects {
    self.prev_rects.push(item)
  }
  self.rects.clear()
  self.active_z = -1
}

///|
pub fn WindowManager::flush_to(
  self : WindowManager,
  target : @draw.DrawList,
) -> Unit {
  for id in self.focus {
    if self.drawn.contains(id) {
      for batch in self.batches {
        if batch.0 == id {
          target.append(batch.1)
        }
      }
    }
  }
}

///|
pub fn WindowManager::get_pos(
  self : WindowManager,
  id : Id,
  fallback : @math.Vec2,
) -> @math.Vec2 {
  match self.positions.get(id) {
    Some(pos) => pos
    None => fallback
  }
}

///|
pub fn WindowManager::set_pos(
  self : WindowManager,
  id : Id,
  pos : @math.Vec2,
) -> Unit {
  self.positions.set(id, pos)
}

///|
pub fn WindowManager::window_z(self : WindowManager, id : Id) -> Int {
  let mut rank = -1
  for i = 0; i < self.focus.length(); i = i + 1 {
    if self.focus[i] == id {
      rank = i
    }
  }
  if rank < 0 {
    self.focus.push(id)
    rank = self.focus.length() - 1
  }
  rank
}

///|
pub fn WindowManager::raise_window(self : WindowManager, id : Id) -> Unit {
  let kept : Array[Id] = []
  for other in self.focus {
    if other != id {
      kept.push(other)
    }
  }
  self.focus.clear()
  for other in kept {
    self.focus.push(other)
  }
  self.focus.push(id)
}

///|
pub fn WindowManager::batch(self : WindowManager, id : Id) -> @draw.DrawList {
  for b in self.batches {
    if b.0 == id {
      return b.1
    }
  }
  let list = @draw.DrawList::new()
  self.batches.push((id, list))
  list
}

///|
pub fn WindowManager::is_occluded(
  self : WindowManager,
  pt : @math.Vec2,
) -> Bool {
  let mut occluded = false
  for item in self.prev_rects {
    if item.1 > self.active_z && item.0.contains(pt) {
      occluded = true
    }
  }
  occluded
}

///|
pub fn WindowManager::record_rect(
  self : WindowManager,
  rect : @math.Rect,
  z : Int,
) -> Unit {
  self.rects.push((rect, z))
}

///|
pub fn WindowManager::mark_drawn(self : WindowManager, id : Id) -> Unit {
  self.drawn.push(id)
}

///|
pub fn WindowManager::prune_stale(self : WindowManager) -> Unit {
  let stale_ids : Array[Id] = []
  self.positions.each(fn(id, _) {
    if !self.drawn.contains(id) {
      stale_ids.push(id)
    }
  })
  for id in stale_ids {
    self.positions.remove(id)
  }

  let kept_batches : Array[(Id, @draw.DrawList)] = []
  for batch in self.batches {
    if self.drawn.contains(batch.0) {
      kept_batches.push(batch)
    }
  }
  self.batches.clear()
  for b in kept_batches {
    self.batches.push(b)
  }

  let kept_focus : Array[Id] = []
  for id in self.focus {
    if self.drawn.contains(id) {
      kept_focus.push(id)
    }
  }
  self.focus.clear()
  for id in kept_focus {
    self.focus.push(id)
  }
}

///|
pub fn WindowManager::active_z(self : WindowManager) -> Int {
  self.active_z
}

///|
pub fn WindowManager::set_active_z(self : WindowManager, z : Int) -> Unit {
  self.active_z = z
}