use smithay::desktop::utils::bbox_from_surface_tree;
use smithay::wayland::compositor::with_states;
use smithay::wayland::shell::xdg::SurfaceCachedState;

use super::*;
use crate::compositor::monitor::camera::camera_controller;

impl<T: DerefMut<Target = Halley>> ClusterSystemController<T> {
    fn restore_cluster_workspace_monitor(&mut self, monitor: &str) {
        let Some(vp) = self
            .model
            .cluster_state
            .workspace_prev_viewports
            .remove(monitor)
        else {
            return;
        };
        if self.model.monitor_state.current_monitor == monitor {
            self.model.viewport = vp;
            self.model.zoom_ref_size = self.model.viewport.size;
            camera_controller(&mut **self).snap_targets_to_live();
            self.runtime.tuning.viewport_center = self.model.viewport.center;
            self.runtime.tuning.viewport_size = self.model.viewport.size;
        }
        if let Some(space) = self.model.monitor_state.monitors.get_mut(monitor) {
            space.viewport = vp;
            space.zoom_ref_size = vp.size;
            space.camera_target_center = vp.center;
            space.camera_target_view_size = vp.size;
        }
    }

    pub(super) fn clear_cluster_shell_state(&mut self, cid: ClusterId) {
        let active_monitors = self
            .model
            .cluster_state
            .active_cluster_workspaces
            .iter()
            .filter_map(|(monitor, active_cid)| (*active_cid == cid).then(|| monitor.clone()))
            .collect::<Vec<_>>();
        for monitor in &active_monitors {
            for id in self
                .model
                .cluster_state
                .workspace_hidden_nodes
                .remove(monitor.as_str())
                .unwrap_or_default()
            {
                if self.model.field.node(id).is_some() {
                    let _ = self.model.field.set_detached(id, false);
                }
            }
            self.model
                .cluster_state
                .workspace_core_positions
                .remove(monitor.as_str());
            self.clear_cluster_overflow_for_monitor(monitor.as_str());
            if self
                .input
                .interaction_state
                .cluster_overflow_drag_preview
                .as_ref()
                .is_some_and(|preview| preview.monitor == *monitor)
            {
                self.input.interaction_state.cluster_overflow_drag_preview = None;
                crate::compositor::interaction::pointer::set_cursor_override_icon(self, None);
            }
            self.restore_cluster_workspace_monitor(monitor.as_str());
        }
        self.model
            .cluster_state
            .active_cluster_workspaces
            .retain(|_, active_cid| *active_cid != cid);
        self.model
            .cluster_state
            .cluster_bloom_open
            .retain(|_, open_cid| *open_cid != cid);
        if self
            .input
            .interaction_state
            .cluster_join_candidate
            .as_ref()
            .is_some_and(|candidate| candidate.cluster_id == cid)
        {
            self.input.interaction_state.cluster_join_candidate = None;
        }
    }

    pub fn collapse_active_cluster_workspace(&mut self, now: Instant) -> bool {
        let monitor = self.model.monitor_state.current_monitor.clone();
        self.exit_cluster_workspace_for_monitor(monitor.as_str(), now)
    }

    pub fn toggle_cluster_workspace_by_core(&mut self, core_id: NodeId, now: Instant) -> bool {
        let monitor = self.model.monitor_state.current_monitor.clone();
        if let Some(cid) = self.active_cluster_workspace_for_monitor(monitor.as_str())
            && self.model.field.cluster_id_for_core_public(core_id) == Some(cid)
        {
            return self.exit_cluster_workspace_for_monitor(monitor.as_str(), now);
        }
        self.enter_cluster_workspace_by_core(core_id, monitor.as_str(), now)
    }

    pub(crate) fn enter_cluster_workspace_by_core(
        &mut self,
        core_id: NodeId,
        monitor: &str,
        now: Instant,
    ) -> bool {
        let Some(cid) = self.model.field.cluster_id_for_core_public(core_id) else {
            return false;
        };
        if self.active_cluster_workspace_for_monitor(monitor) == Some(cid) {
            return true;
        }
        if self.active_cluster_workspace_for_monitor(monitor).is_some() {
            let _ = self.exit_cluster_workspace_for_monitor(monitor, now);
        }
        let Some(plan) = self
            .cluster_read_controller()
            .plan_enter_cluster_workspace(core_id, monitor)
        else {
            return false;
        };
        let _ = self.sync_cluster_monitor(cid, Some(monitor));
        let previous_full_viewport = if self.model.monitor_state.current_monitor == monitor {
            self.model.viewport
        } else {
            self.model
                .monitor_state
                .monitors
                .get(monitor)
                .map(|space| space.viewport)
                .unwrap_or(plan.current_viewport)
        };
        self.model
            .cluster_state
            .workspace_prev_viewports
            .insert(monitor.to_string(), previous_full_viewport);
        self.model
            .cluster_state
            .workspace_core_positions
            .insert(monitor.to_string(), plan.core_pos);
        if self.model.monitor_state.current_monitor == monitor {
            let live_viewport = self
                .model
                .monitor_state
                .monitors
                .get(monitor)
                .map(|space| space.viewport)
                .unwrap_or(plan.current_viewport);
            self.input.interaction_state.viewport_pan_anim = None;
            self.model.viewport = live_viewport;
            self.model.zoom_ref_size = live_viewport.size;
            self.model.camera_target_center = live_viewport.center;
            self.model.camera_target_view_size = live_viewport.size;
            self.runtime.tuning.viewport_center = live_viewport.center;
            self.runtime.tuning.viewport_size = live_viewport.size;
        }
        self.model.spawn_state.pending_spawn_pan_queue.clear();
        self.model.spawn_state.active_spawn_pan = None;
        self.input.interaction_state.viewport_pan_anim = None;
        self.model.spawn_state.pending_spawn_monitor = None;
        let spawn = self.spawn_monitor_state_mut(monitor);
        spawn.spawn_pan_start_center = None;
        for id in &plan.hidden_ids {
            let _ = self.model.field.set_detached(*id, true);
        }
        let _ = self.model.field.set_detached(plan.core_id, true);
        let _ = self.model.field.activate_cluster_workspace(plan.cid);

        self.model
            .cluster_state
            .workspace_hidden_nodes
            .insert(monitor.to_string(), plan.hidden_ids);
        self.model
            .cluster_state
            .active_cluster_workspaces
            .retain(|name, active_cid| *active_cid != cid || name == monitor);
        self.model
            .cluster_state
            .active_cluster_workspaces
            .insert(monitor.to_string(), cid);
        self.model.cluster_state.cluster_bloom_open.remove(monitor);
        self.set_interaction_focus(None, 0, now);
        let now_ms = self.now_ms(now);
        self.layout_active_cluster_workspace_for_monitor(monitor, now_ms);
        if matches!(
            self.active_cluster_layout_kind(),
            ClusterWorkspaceLayoutKind::Stacking
        ) && let Some(front) = self
            .model
            .field
            .cluster(plan.cid)
            .and_then(|cluster| cluster.members().first().copied())
        {
            self.set_recent_top_node(front, now + std::time::Duration::from_millis(1200));
            self.set_interaction_focus(Some(front), 30_000, now);
            self.update_focus_tracking_for_surface(front, now_ms);
        } else if matches!(
            self.active_cluster_layout_kind(),
            ClusterWorkspaceLayoutKind::Tiling
        ) {
            let _ = self.focus_active_tiled_cluster_member_for_monitor(monitor, Some(0), now);
        }
        self.refresh_cluster_overflow_for_monitor(monitor, now_ms, false);
        true
    }

    pub(crate) fn exit_cluster_workspace_for_monitor(
        &mut self,
        monitor: &str,
        now: Instant,
    ) -> bool {
        let Some(plan) = self
            .cluster_read_controller()
            .plan_exit_cluster_workspace(monitor)
        else {
            return false;
        };

        for id in &plan.hidden_ids {
            let _ = self.model.field.set_detached(*id, false);
        }

        let _ = self.model.field.deactivate_cluster_workspace(plan.cid);
        let core = self.model.field.collapse_cluster(plan.cid).or(plan.core_id);
        if let Some(core_id) = core {
            let _ = self.ensure_cluster_name_record_for_monitor(plan.cid, monitor);
            let _ = self.relabel_cluster_core(plan.cid);
            let preserved_core_pos = self
                .model
                .cluster_state
                .workspace_core_positions
                .remove(monitor)
                .or(plan.core_pos);
            if let Some(core_pos) = preserved_core_pos {
                let _ = self.model.field.carry(core_id, core_pos);
            }
            let _ = self.model.field.set_detached(core_id, false);
            self.assign_node_to_monitor(core_id, monitor);
            let now_ms = self.now_ms(now);
            let _ = self.model.field.touch(core_id, now_ms);
        }

        self.restore_cluster_workspace_monitor(monitor);
        self.model
            .cluster_state
            .active_cluster_workspaces
            .remove(monitor);
        self.model
            .cluster_state
            .workspace_hidden_nodes
            .remove(monitor);
        self.clear_cluster_overflow_for_monitor(monitor);
        if self
            .input
            .interaction_state
            .cluster_overflow_drag_preview
            .as_ref()
            .is_some_and(|preview| preview.monitor == monitor)
        {
            self.input.interaction_state.cluster_overflow_drag_preview = None;
            crate::compositor::interaction::pointer::set_cursor_override_icon(self, None);
        }
        if let Some(core_id) = core {
            self.set_recent_top_node(core_id, now + std::time::Duration::from_millis(1200));
            self.set_interaction_focus(Some(core_id), 30_000, now);
        }
        true
    }

    fn clear_cluster_tile_animation_for_node(&mut self, node_id: NodeId) {
        self.ui.render_state.cluster_tile_tracks.remove(&node_id);
        self.ui
            .render_state
            .cluster_tile_entry_pending
            .remove(&node_id);
        self.ui
            .render_state
            .cluster_tile_frozen_geometry
            .remove(&node_id);
    }

    fn update_tiled_cluster_animation_targets(
        &mut self,
        plan: &ClusterLayoutPlan,
        dragged_member: Option<NodeId>,
        now: Instant,
    ) {
        for placement in &plan.tiles {
            if self
                .model
                .spawn_state
                .pending_tiled_insert_reveal_at_ms
                .contains_key(&placement.node_id)
                || Some(placement.node_id) == dragged_member
            {
                self.clear_cluster_tile_animation_for_node(placement.node_id);
                continue;
            }

            let current_rect = if self
                .ui
                .render_state
                .cluster_tile_entry_pending
                .remove(&placement.node_id)
            {
                None
            } else {
                crate::animation::cluster_tile_rect_from_field(&self.model.field, placement.node_id)
            };
            let frozen_geo = self
                .ui
                .render_state
                .cache
                .window_geometry
                .get(&placement.node_id)
                .copied();
            if current_rect.is_some_and(|rect| rect.alpha > 0.01)
                && let Some(geo) = frozen_geo
            {
                self.ui
                    .render_state
                    .cluster_tile_frozen_geometry
                    .entry(placement.node_id)
                    .or_insert(geo);
            }
            let duration_ms = self.runtime.tuning.tile_animation_duration_ms();
            if self.runtime.tuning.tile_animation_enabled() {
                crate::animation::set_cluster_tile_target(
                    &mut self.ui.render_state.cluster_tile_tracks,
                    current_rect,
                    placement.node_id,
                    placement.rect,
                    now,
                    duration_ms,
                );
            } else {
                self.ui
                    .render_state
                    .cluster_tile_tracks
                    .remove(&placement.node_id);
            }
        }
    }

    fn current_surface_size_map_for_members(
        &self,
        members: &HashSet<NodeId>,
    ) -> HashMap<NodeId, Vec2> {
        let mut sizes = HashMap::with_capacity(members.len());
        for (&node_id, &(_, _, w, h)) in &self.ui.render_state.cache.window_geometry {
            if members.contains(&node_id) {
                sizes.insert(
                    node_id,
                    Vec2 {
                        x: w.max(1.0),
                        y: h.max(1.0),
                    },
                );
            }
        }

        for top in self.platform.xdg_shell_state.toplevel_surfaces() {
            let wl = top.wl_surface();
            let key = wl.id();
            let Some(node_id) = self.model.surface_to_node.get(&key).copied() else {
                continue;
            };
            if sizes.contains_key(&node_id) || !members.contains(&node_id) {
                continue;
            }

            let size = with_states(wl, |states| {
                states
                    .cached_state
                    .get::<SurfaceCachedState>()
                    .current()
                    .geometry
            })
            .map(|g| Vec2 {
                x: g.size.w.max(1) as f32,
                y: g.size.h.max(1) as f32,
            })
            .or_else(|| {
                top.current_state().size.map(|sz| Vec2 {
                    x: sz.w.max(1) as f32,
                    y: sz.h.max(1) as f32,
                })
            })
            .unwrap_or_else(|| {
                let bbox = bbox_from_surface_tree(wl, (0, 0));
                Vec2 {
                    x: bbox.size.w.max(1) as f32,
                    y: bbox.size.h.max(1) as f32,
                }
            });
            sizes.insert(node_id, size);
        }

        for &node_id in members {
            sizes.entry(node_id).or_insert_with(|| {
                self.model
                    .field
                    .node(node_id)
                    .map_or(Vec2 { x: 1.0, y: 1.0 }, |node| Vec2 {
                        x: node.intrinsic_size.x.max(1.0),
                        y: node.intrinsic_size.y.max(1.0),
                    })
            });
        }

        sizes
    }

    pub(crate) fn layout_active_cluster_workspace_for_monitor(
        &mut self,
        monitor: &str,
        now_ms: u64,
    ) {
        let Some(cid) = self.active_cluster_workspace_for_monitor(monitor) else {
            return;
        };
        let Some(cluster) = self.model.field.cluster(cid) else {
            self.model
                .cluster_state
                .active_cluster_workspaces
                .remove(monitor);
            return;
        };
        let members = cluster.members().to_vec();
        let member_set = members.iter().copied().collect::<HashSet<_>>();
        let dragged_member = self
            .input
            .interaction_state
            .drag_authority_node
            .filter(|id| member_set.contains(id));
        if self
            .model
            .fullscreen_state
            .fullscreen_active_node
            .get(monitor)
            .is_some_and(|fullscreen_id| member_set.contains(fullscreen_id))
        {
            return;
        }
        let Some(plan) = self
            .cluster_read_controller()
            .plan_active_cluster_layout(monitor)
        else {
            return;
        };
        let now = Instant::now();
        if matches!(plan.kind, ClusterWorkspaceLayoutKind::Tiling) {
            self.update_tiled_cluster_animation_targets(&plan, dragged_member, now);
        }
        let visible_members = plan
            .tiles
            .iter()
            .map(|tile| tile.node_id)
            .filter(|id| {
                !self
                    .model
                    .spawn_state
                    .pending_tiled_insert_reveal_at_ms
                    .contains_key(id)
            })
            .collect::<HashSet<_>>();
        let visible_surface_sizes = self.current_surface_size_map_for_members(&visible_members);
        if let Some(cluster) = self.model.field.cluster_mut(cid) {
            for member_id in &members {
                if let Some(node) = cluster.workspace_member_mut(*member_id) {
                    let visible = visible_members.contains(member_id);
                    node.visibility.set(Visibility::DETACHED, !visible);
                    node.visibility.set(Visibility::HIDDEN_BY_CLUSTER, !visible);
                }
            }
        }
        for placement in plan.tiles {
            let nid = placement.node_id;
            if Some(nid) == dragged_member
                || self
                    .model
                    .spawn_state
                    .pending_tiled_insert_reveal_at_ms
                    .contains_key(&nid)
            {
                continue;
            }
            let rect = placement.rect;
            let target_size = Vec2 {
                x: rect.w.max(64.0),
                y: rect.h.max(64.0),
            };
            let target_pos = Vec2 {
                x: rect.x + rect.w * 0.5,
                y: rect.y + rect.h * 0.5,
            };
            let layout_changed = self.model.field.node(nid).is_none_or(|node| {
                (node.intrinsic_size.x - target_size.x).abs() > 0.5
                    || (node.intrinsic_size.y - target_size.y).abs() > 0.5
                    || (node.pos.x - target_pos.x).abs() > 0.5
                    || (node.pos.y - target_pos.y).abs() > 0.5
                    || node.state != halley_core::field::NodeState::Active
                    || node.visibility.has(Visibility::DETACHED)
                    || node.visibility.has(Visibility::HIDDEN_BY_CLUSTER)
            });
            if let Some(cluster) = self.model.field.cluster_mut(cid)
                && let Some(node) = cluster.workspace_member_mut(nid)
                && layout_changed
            {
                node.visibility.set(Visibility::DETACHED, false);
                node.visibility.set(Visibility::HIDDEN_BY_CLUSTER, false);
                node.intrinsic_size = target_size;
                node.state = halley_core::field::NodeState::Active;
                node.footprint = node.resize_footprint.unwrap_or(node.intrinsic_size);
                node.pos = target_pos;
            }
            if layout_changed {
                self.set_last_active_size_now(nid, target_size);
            }
            let surface_size_changed = visible_surface_sizes.get(&nid).is_none_or(|size| {
                (size.x - target_size.x).abs() > 0.5 || (size.y - target_size.y).abs() > 0.5
            });
            if surface_size_changed {
                self.request_toplevel_resize(nid, rect.w.round() as i32, rect.h.round() as i32);
            }
        }
        self.refresh_cluster_overflow_for_monitor(monitor, now_ms, false);
    }
}
