use std::collections::{HashMap, HashSet, VecDeque};
use std::time::Instant;

use halley_config::{
    InitialWindowClusterParticipation, InitialWindowOverlapPolicy, InitialWindowSpawnPlacement,
};
use halley_core::decay::DecayLevel;
use halley_core::field::{NodeId, Vec2};

use super::Halley;

#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct SpawnFrontierPoint {
    pub pos: Vec2,
    pub score: f32,
    pub dir: Vec2,
}

#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct SpawnPatch {
    pub anchor: Vec2,
    pub focus_node: Option<NodeId>,
    pub focus_pos: Vec2,
    pub growth_dir: Vec2,
    pub placements_in_patch: u32,
    pub frontier: Vec<SpawnFrontierPoint>,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct PendingSpawnPan {
    pub node_id: NodeId,
    pub target_center: Vec2,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct ActiveSpawnPan {
    pub node_id: NodeId,
    pub pan_start_at_ms: u64,
    pub reveal_at_ms: u64,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct AppliedInitialWindowRule {
    pub(crate) overlap_policy: InitialWindowOverlapPolicy,
    pub(crate) spawn_placement: InitialWindowSpawnPlacement,
    pub(crate) cluster_participation: InitialWindowClusterParticipation,
    pub(crate) parent_node: Option<NodeId>,
    pub(crate) suppress_reveal_pan: bool,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SpawnAnchorMode {
    Focus,
    View,
}

#[derive(Clone, Copy, Debug)]
pub(crate) struct SpawnFocusOverride {
    pub(crate) pos: Vec2,
    pub(crate) size: Vec2,
}

#[derive(Clone, Debug)]
pub(crate) struct MonitorSpawnState {
    pub(crate) spawn_cursor: u32,
    pub(crate) spawn_patch: Option<SpawnPatch>,
    pub(crate) spawn_anchor_mode: SpawnAnchorMode,
    pub(crate) spawn_view_anchor: Vec2,
    pub(crate) spawn_focus_override: Option<SpawnFocusOverride>,
    pub(crate) spawn_pan_start_center: Option<Vec2>,
    pub(crate) spawn_last_pan_ms: u64,
}

impl MonitorSpawnState {
    pub(crate) fn new(view_anchor: Vec2) -> Self {
        Self {
            spawn_cursor: 0,
            spawn_patch: None,
            spawn_anchor_mode: SpawnAnchorMode::Focus,
            spawn_view_anchor: view_anchor,
            spawn_focus_override: None,
            spawn_pan_start_center: None,
            spawn_last_pan_ms: 0,
        }
    }
}

pub(crate) struct SpawnState {
    pub pending_spawn_activate_at_ms: HashMap<NodeId, u64>,
    pub(crate) pending_tiled_insert_reveal_at_ms: HashMap<NodeId, u64>,
    pub(crate) pending_tiled_insert_preserve_focus: HashSet<NodeId>,
    pub(crate) pending_spawn_monitor: Option<String>,
    pub(crate) per_monitor: HashMap<String, MonitorSpawnState>,
    pub(crate) pending_spawn_pan_queue: VecDeque<PendingSpawnPan>,
    pub(crate) active_spawn_pan: Option<ActiveSpawnPan>,
    pub(crate) applied_window_rules: HashMap<NodeId, AppliedInitialWindowRule>,
    pub(crate) pending_rule_rechecks: HashSet<NodeId>,
    pub(crate) pending_initial_reveal: HashSet<NodeId>,
}

pub(crate) fn is_persistent_rule_top(st: &Halley, node_id: NodeId) -> bool {
    st.model
        .spawn_state
        .applied_window_rules
        .contains_key(&node_id)
}

pub(crate) fn node_has_overlap_policy(st: &Halley, node_id: NodeId) -> bool {
    if matches!(
        st.runtime.tuning.cluster_layout_kind(),
        halley_core::cluster_layout::ClusterWorkspaceLayoutKind::Stacking
    ) {
        return false;
    }
    st.model
        .spawn_state
        .applied_window_rules
        .get(&node_id)
        .is_some_and(|rule| rule.overlap_policy != InitialWindowOverlapPolicy::None)
}

pub(crate) fn node_draws_above_fullscreen_on_monitor(
    st: &Halley,
    node_id: NodeId,
    monitor: &str,
) -> bool {
    let Some(fullscreen_id) = st
        .model
        .fullscreen_state
        .fullscreen_active_node
        .get(monitor)
        .copied()
    else {
        return false;
    };
    if fullscreen_id == node_id || !node_has_overlap_policy(st, node_id) {
        return false;
    }
    let Some(node) = st.model.field.node(node_id) else {
        return false;
    };
    if node.state != halley_core::field::NodeState::Active || !st.model.field.is_visible(node_id) {
        return false;
    }
    st.model
        .monitor_state
        .node_monitor
        .get(&node_id)
        .map(String::as_str)
        .unwrap_or(st.model.monitor_state.current_monitor.as_str())
        == monitor
}

pub(crate) fn monitor_has_visible_overlap_policy_window(st: &Halley, monitor: &str) -> bool {
    st.model
        .field
        .node_ids_all()
        .into_iter()
        .any(|node_id| node_draws_above_fullscreen_on_monitor(st, node_id, monitor))
}

pub(crate) fn default_spawn_view_anchor_for_monitor(st: &Halley, monitor: &str) -> Vec2 {
    st.model
        .monitor_state
        .monitors
        .get(monitor)
        .map(|space| space.viewport.center)
        .unwrap_or(st.model.viewport.center)
}

pub(crate) fn spawn_monitor_state(st: &Halley, monitor: &str) -> MonitorSpawnState {
    st.model
        .spawn_state
        .per_monitor
        .get(monitor)
        .cloned()
        .unwrap_or_else(|| {
            MonitorSpawnState::new(default_spawn_view_anchor_for_monitor(st, monitor))
        })
}

pub(crate) fn spawn_monitor_state_mut<'a>(
    st: &'a mut Halley,
    monitor: &str,
) -> &'a mut MonitorSpawnState {
    let view_anchor = default_spawn_view_anchor_for_monitor(st, monitor);
    st.model
        .spawn_state
        .per_monitor
        .entry(monitor.to_string())
        .or_insert_with(|| MonitorSpawnState::new(view_anchor))
}

pub(crate) fn process_pending_spawn_activations(st: &mut Halley, now: Instant, now_ms: u64) {
    let due_tiled_reveals: Vec<NodeId> = st
        .model
        .spawn_state
        .pending_tiled_insert_reveal_at_ms
        .iter()
        .filter_map(|(&id, &at)| (now_ms >= at).then_some(id))
        .collect();

    for id in due_tiled_reveals {
        if !st.ui.render_state.cache.window_geometry.contains_key(&id) {
            continue;
        }
        let preserve_focus = st
            .model
            .spawn_state
            .pending_tiled_insert_preserve_focus
            .remove(&id);
        st.model
            .spawn_state
            .pending_tiled_insert_reveal_at_ms
            .remove(&id);
        st.ui.render_state.cluster_tile_entry_pending.insert(id);
        let monitor = st
            .model
            .monitor_state
            .node_monitor
            .get(&id)
            .cloned()
            .unwrap_or_else(|| st.model.monitor_state.current_monitor.clone());
        if st
            .active_cluster_workspace_for_monitor(monitor.as_str())
            .is_some()
        {
            st.layout_active_cluster_workspace_for_monitor(monitor.as_str(), now_ms);
            let _ = st.model.field.set_decay_level(id, DecayLevel::Hot);
            st.set_recent_top_node(id, now + std::time::Duration::from_millis(1200));
            crate::compositor::workspace::state::mark_active_transition(st, id, now, 620);
            if !preserve_focus {
                st.set_interaction_focus(Some(id), 30_000, now);
            }
            st.request_maintenance();
        }
    }

    let due: Vec<NodeId> = st
        .model
        .spawn_state
        .pending_spawn_activate_at_ms
        .iter()
        .filter_map(|(&id, &at)| (now_ms >= at).then_some(id))
        .collect();

    for id in due {
        st.model
            .spawn_state
            .pending_spawn_activate_at_ms
            .remove(&id);
        if !st.model.field.is_visible(id) {
            continue;
        }
        let Some(n) = st.model.field.node(id) else {
            continue;
        };
        if n.kind != halley_core::field::NodeKind::Surface {
            continue;
        }
        if crate::compositor::workspace::state::preserve_collapsed_surface(st, id) {
            continue;
        }
        let node_monitor = st
            .model
            .monitor_state
            .node_monitor
            .get(&id)
            .cloned()
            .unwrap_or_else(|| st.model.monitor_state.current_monitor.clone());
        let cluster_local = st
            .active_cluster_workspace_for_monitor(node_monitor.as_str())
            .is_some();
        let _ = st.model.field.set_decay_level(id, DecayLevel::Hot);
        if let Some((_, _, w, h)) = st.ui.render_state.cache.window_geometry.get(&id) {
            st.model
                .workspace_state
                .last_active_size
                .insert(id, Vec2 { x: *w, y: *h });
        }
        crate::compositor::workspace::state::mark_active_transition(st, id, now, 620);
        if !cluster_local {
            st.record_focus_trail_visit(id);
            st.model.focus_state.suppress_trail_record_once = true;
        }
        st.set_interaction_focus(Some(id), 30_000, now);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use smithay::reexports::wayland_server::Display;

    #[test]
    fn visible_overlap_policy_window_blocks_same_monitor_scanout_eligibility() {
        let mut tuning = halley_config::RuntimeTuning::default();
        tuning.cluster_default_layout = halley_config::ClusterDefaultLayout::Tiling;
        let dh = Display::<Halley>::new().expect("display").handle();
        let mut st = Halley::new_for_test(&dh, tuning);

        let monitor = st.model.monitor_state.current_monitor.clone();
        let fullscreen = st.model.field.spawn_surface(
            "fullscreen",
            Vec2 { x: 400.0, y: 300.0 },
            Vec2 { x: 800.0, y: 600.0 },
        );
        let overlap = st.model.field.spawn_surface(
            "overlap",
            Vec2 { x: 400.0, y: 300.0 },
            Vec2 { x: 240.0, y: 180.0 },
        );
        st.assign_node_to_monitor(fullscreen, monitor.as_str());
        st.assign_node_to_monitor(overlap, monitor.as_str());
        let _ = st
            .model
            .field
            .set_state(fullscreen, halley_core::field::NodeState::Active);
        let _ = st
            .model
            .field
            .set_state(overlap, halley_core::field::NodeState::Active);
        st.model
            .fullscreen_state
            .fullscreen_active_node
            .insert(monitor.clone(), fullscreen);

        assert!(!monitor_has_visible_overlap_policy_window(
            &st,
            monitor.as_str()
        ));

        st.model.spawn_state.applied_window_rules.insert(
            overlap,
            AppliedInitialWindowRule {
                overlap_policy: InitialWindowOverlapPolicy::All,
                spawn_placement: InitialWindowSpawnPlacement::Adjacent,
                cluster_participation: InitialWindowClusterParticipation::Float,
                parent_node: None,
                suppress_reveal_pan: true,
            },
        );

        assert!(monitor_has_visible_overlap_policy_window(
            &st,
            monitor.as_str()
        ));
    }
}
