use super::*;
use crate::compositor::clusters::state::PendingClusterSlotTransition;

impl<T: DerefMut<Target = Halley>> ClusterSystemController<T> {
    pub(crate) fn activate_cluster_slot_on_current_monitor(
        &mut self,
        slot: u8,
        now: Instant,
    ) -> bool {
        let monitor = self.model.monitor_state.current_monitor.clone();
        self.model
            .cluster_state
            .pending_cluster_slot_transition
            .remove(monitor.as_str());
        let Some(cid) = self.cluster_slot_cluster_for_monitor(monitor.as_str(), slot) else {
            return false;
        };

        if self.active_cluster_workspace_for_monitor(monitor.as_str()) == Some(cid) {
            return self.exit_cluster_workspace_for_monitor(monitor.as_str(), now);
        }

        if self
            .active_cluster_workspace_for_monitor(monitor.as_str())
            .is_some()
        {
            let _ = self.exit_cluster_workspace_for_monitor(monitor.as_str(), now);
        }

        let Some(core_id) = self
            .model
            .field
            .cluster(cid)
            .and_then(|cluster| cluster.core)
        else {
            return false;
        };
        let Some(target_center) = self.model.field.node(core_id).map(|node| node.pos) else {
            return false;
        };

        if self.animate_viewport_center_to(target_center, now) {
            self.model
                .cluster_state
                .pending_cluster_slot_transition
                .insert(monitor, PendingClusterSlotTransition { slot, cid, core_id });
            self.request_maintenance();
            true
        } else {
            self.enter_cluster_workspace_by_core(core_id, monitor.as_str(), now)
        }
    }

    pub(crate) fn process_pending_cluster_slot_transition_for_current_monitor(
        &mut self,
        now: Instant,
    ) -> bool {
        if self.input.interaction_state.viewport_pan_anim.is_some() {
            return false;
        }
        let monitor = self.model.monitor_state.current_monitor.clone();
        let Some(pending) = self
            .model
            .cluster_state
            .pending_cluster_slot_transition
            .remove(monitor.as_str())
        else {
            return false;
        };
        if self.cluster_slot_cluster_for_monitor(monitor.as_str(), pending.slot)
            != Some(pending.cid)
        {
            return false;
        }
        self.enter_cluster_workspace_by_core(pending.core_id, monitor.as_str(), now)
    }

    fn visible_tiled_cluster_tiles_for_monitor(
        &self,
        monitor: &str,
    ) -> Option<(ClusterId, Vec<ClusterTilePlacement>)> {
        let cid = self.active_cluster_workspace_for_monitor(monitor)?;
        let plan = self
            .cluster_read_controller()
            .plan_active_cluster_layout(monitor)?;
        if !matches!(plan.kind, ClusterWorkspaceLayoutKind::Tiling) {
            return None;
        }

        let visible_tiles = plan
            .tiles
            .into_iter()
            .filter(|tile| {
                !self
                    .model
                    .spawn_state
                    .pending_tiled_insert_reveal_at_ms
                    .contains_key(&tile.node_id)
            })
            .collect::<Vec<_>>();
        Some((cid, visible_tiles))
    }

    fn directional_target_member(
        &self,
        visible_tiles: &[ClusterTilePlacement],
        direction: DirectionalAction,
    ) -> Option<(NodeId, NodeId)> {
        if visible_tiles.is_empty() {
            return None;
        }

        let current_member = self
            .model
            .focus_state
            .primary_interaction_focus
            .filter(|id| visible_tiles.iter().any(|tile| tile.node_id == *id))
            .unwrap_or(visible_tiles[0].node_id);
        let current_rect = visible_tiles
            .iter()
            .find(|tile| tile.node_id == current_member)
            .map(|tile| tile.rect)?;
        let target = visible_tiles
            .iter()
            .filter(|tile| tile.node_id != current_member)
            .filter_map(|tile| {
                directional_candidate_score(current_rect, tile.rect, direction)
                    .map(|score| (score, tile.node_id))
            })
            .min_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
            .map(|(_, node_id)| node_id)?;
        Some((current_member, target))
    }

    pub(crate) fn move_active_cluster_member_to_drop_tile(
        &mut self,
        monitor: &str,
        member: NodeId,
        world_pos: Vec2,
        now_ms: u64,
    ) -> bool {
        let Some((cid, visible_tiles)) = self.visible_tiled_cluster_tiles_for_monitor(monitor)
        else {
            return false;
        };
        if !visible_tiles.iter().any(|tile| tile.node_id == member) {
            return false;
        }

        let Some(target_member) = visible_tiles
            .iter()
            .find(|tile| {
                world_pos.x >= tile.rect.x
                    && world_pos.x <= tile.rect.x + tile.rect.w
                    && world_pos.y >= tile.rect.y
                    && world_pos.y <= tile.rect.y + tile.rect.h
            })
            .map(|tile| tile.node_id)
        else {
            return false;
        };
        if target_member == member {
            return false;
        }

        let Some(members) = self
            .model
            .field
            .cluster(cid)
            .map(|cluster| cluster.members().to_vec())
        else {
            return false;
        };
        let Some(from_index) = members.iter().position(|&id| id == member) else {
            return false;
        };
        let Some(target_index) = members.iter().position(|&id| id == target_member) else {
            return false;
        };

        let mut reordered = members;
        let moved = reordered.remove(from_index);
        reordered.insert(target_index.min(reordered.len()), moved);
        if self
            .model
            .field
            .reorder_cluster_members(cid, reordered)
            .is_err()
        {
            return false;
        }
        self.layout_active_cluster_workspace_for_monitor(monitor, now_ms);
        true
    }

    pub(crate) fn focus_active_tiled_cluster_member_for_monitor(
        &mut self,
        monitor: &str,
        preferred_index: Option<usize>,
        now: Instant,
    ) -> bool {
        let Some((_, visible_tiles)) = self.visible_tiled_cluster_tiles_for_monitor(monitor) else {
            return false;
        };
        let visible_members = visible_tiles
            .into_iter()
            .map(|tile| tile.node_id)
            .collect::<Vec<_>>();
        let Some(target) = visible_members
            .get(
                preferred_index
                    .unwrap_or(0)
                    .min(visible_members.len().saturating_sub(1)),
            )
            .copied()
        else {
            return false;
        };
        let now_ms = self.now_ms(now);
        self.set_interaction_focus(Some(target), 30_000, now);
        self.update_focus_tracking_for_surface(target, now_ms);
        true
    }

    pub(crate) fn tile_focus_active_cluster_member_for_monitor(
        &mut self,
        monitor: &str,
        direction: DirectionalAction,
        now: Instant,
    ) -> bool {
        let Some((_, visible_tiles)) = self.visible_tiled_cluster_tiles_for_monitor(monitor) else {
            return false;
        };
        let Some((_, target)) = self.directional_target_member(&visible_tiles, direction) else {
            return false;
        };
        let now_ms = self.now_ms(now);
        self.set_interaction_focus(Some(target), 30_000, now);
        self.update_focus_tracking_for_surface(target, now_ms);
        true
    }

    pub(crate) fn tile_swap_active_cluster_member_for_monitor(
        &mut self,
        monitor: &str,
        direction: DirectionalAction,
        now: Instant,
    ) -> bool {
        let Some((cid, visible_tiles)) = self.visible_tiled_cluster_tiles_for_monitor(monitor)
        else {
            return false;
        };
        if visible_tiles.len() < 2 {
            return false;
        }
        let Some((current_member, target_member)) =
            self.directional_target_member(&visible_tiles, direction)
        else {
            return false;
        };

        let Some(mut members) = self
            .model
            .field
            .cluster(cid)
            .map(|cluster| cluster.members().to_vec())
        else {
            return false;
        };
        let Some(current_index) = members.iter().position(|id| *id == current_member) else {
            return false;
        };
        let Some(target_index) = members.iter().position(|id| *id == target_member) else {
            return false;
        };
        members.swap(current_index, target_index);
        if self
            .model
            .field
            .reorder_cluster_members(cid, members)
            .is_err()
        {
            return false;
        }
        let now_ms = self.now_ms(now);
        self.layout_active_cluster_workspace_for_monitor(monitor, now_ms);
        self.set_interaction_focus(Some(current_member), 30_000, now);
        self.update_focus_tracking_for_surface(current_member, now_ms);
        true
    }

    pub(crate) fn cycle_active_stack_for_monitor(
        &mut self,
        monitor: &str,
        direction: ClusterCycleDirection,
        now: Instant,
    ) -> bool {
        if !matches!(
            self.active_cluster_layout_kind(),
            ClusterWorkspaceLayoutKind::Stacking
        ) {
            return false;
        }
        let Some(cid) = self.active_cluster_workspace_for_monitor(monitor) else {
            return false;
        };
        let old_visible =
            crate::compositor::surface::active_stacking_visible_members_for_monitor(self, monitor);
        let Some(front) = self
            .model
            .field
            .cycle_cluster_stacking_members(cid, direction)
        else {
            return false;
        };
        if self.focused_monitor() != monitor {
            self.focus_monitor_view(monitor, now);
        }
        let now_ms = self.now_ms(now);
        self.layout_active_cluster_workspace_for_monitor(monitor, now_ms);
        let new_visible =
            crate::compositor::surface::active_stacking_visible_members_for_monitor(self, monitor);
        let duration_ms = self.runtime.tuning.stack_animation_duration_ms();
        if self.runtime.tuning.stack_animation_enabled() {
            self.ui.render_state.start_stack_cycle_transition(
                monitor,
                direction,
                old_visible,
                new_visible,
                now,
                duration_ms,
            );
            self.request_maintenance();
        }
        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);
        true
    }

    pub(crate) fn cycle_active_cluster_layout_for_monitor(
        &mut self,
        monitor: &str,
        now: Instant,
    ) -> bool {
        let Some(cid) = self.active_cluster_workspace_for_monitor(monitor) else {
            return false;
        };
        let current_focus = self
            .model
            .focus_state
            .primary_interaction_focus
            .filter(|id| self.model.field.cluster_id_for_member_public(*id) == Some(cid));
        let previous_layout_kind = self.active_cluster_layout_kind();
        let tile_to_stack_transition =
            if matches!(previous_layout_kind, ClusterWorkspaceLayoutKind::Tiling) {
                self.cluster_read_controller()
                    .plan_active_cluster_layout(monitor)
                    .map(|plan| {
                        let source_rects = plan
                            .tiles
                            .iter()
                            .map(|tile| (tile.node_id, tile.rect))
                            .collect::<HashMap<_, _>>();
                        let old_visible = plan
                            .tiles
                            .into_iter()
                            .map(|tile| tile.node_id)
                            .collect::<Vec<_>>();
                        (!source_rects.is_empty()).then_some((old_visible, source_rects))
                    })
                    .flatten()
            } else {
                None
            };

        self.runtime.tuning.cluster_default_layout =
            match self.runtime.tuning.cluster_default_layout {
                ClusterDefaultLayout::Tiling => ClusterDefaultLayout::Stacking,
                ClusterDefaultLayout::Stacking => ClusterDefaultLayout::Tiling,
            };

        let is_tiling = matches!(
            self.runtime.tuning.cluster_layout_kind(),
            ClusterWorkspaceLayoutKind::Tiling
        );

        if is_tiling {
            let members = self
                .model
                .field
                .cluster(cid)
                .map(|c| c.members().iter().copied().collect::<Vec<_>>())
                .unwrap_or_default();
            for member in members {
                let should_float = self
                    .model
                    .spawn_state
                    .applied_window_rules
                    .get(&member)
                    .is_some_and(|rule| {
                        rule.cluster_participation
                            == halley_config::InitialWindowClusterParticipation::Float
                            || rule.overlap_policy
                                != halley_config::InitialWindowOverlapPolicy::None
                    });
                if should_float {
                    let pos = self
                        .model
                        .field
                        .node(member)
                        .map(|n| n.pos)
                        .unwrap_or(halley_core::field::Vec2 { x: 0.0, y: 0.0 });
                    let _ = self.detach_member_from_cluster(cid, member, pos, now);
                }
            }
        } else {
            let monitor_nodes: Vec<_> = self
                .model
                .monitor_state
                .node_monitor
                .iter()
                .filter_map(|(&id, m)| if m == monitor { Some(id) } else { None })
                .collect();
            for node in monitor_nodes {
                if self
                    .model
                    .field
                    .cluster_id_for_member_public(node)
                    .is_none()
                {
                    let should_be_in_cluster = self
                        .model
                        .spawn_state
                        .applied_window_rules
                        .get(&node)
                        .is_some_and(|rule| {
                            rule.cluster_participation
                                == halley_config::InitialWindowClusterParticipation::Float
                                || rule.overlap_policy
                                    != halley_config::InitialWindowOverlapPolicy::None
                        });
                    if should_be_in_cluster {
                        let _ = self.absorb_node_into_cluster(cid, node, now);
                    }
                }
            }
        }

        let now_ms = self.now_ms(now);
        self.layout_active_cluster_workspace_for_monitor(monitor, now_ms);

        match self.runtime.tuning.cluster_layout_kind() {
            ClusterWorkspaceLayoutKind::Tiling => {
                let preferred_index = current_focus.and_then(|id| {
                    self.model.field.cluster(cid).and_then(|cluster| {
                        cluster.members().iter().position(|member| *member == id)
                    })
                });
                let _ = self.focus_active_tiled_cluster_member_for_monitor(
                    monitor,
                    preferred_index,
                    now,
                );
            }
            ClusterWorkspaceLayoutKind::Stacking => {
                let visible =
                    crate::compositor::surface::active_stacking_visible_members_for_monitor(
                        self, monitor,
                    );
                if let Some((old_visible, source_rects)) = tile_to_stack_transition {
                    self.ui
                        .render_state
                        .start_stack_cycle_transition_from_rects(
                            monitor,
                            ClusterCycleDirection::Prev,
                            old_visible,
                            visible.clone(),
                            source_rects,
                            now,
                            240,
                        );
                    self.request_maintenance();
                }
                if let Some(target) =
                    crate::compositor::surface::active_stacking_front_member_for_monitor(
                        self, monitor,
                    )
                    .or_else(|| visible.first().copied())
                {
                    self.set_recent_top_node(target, now + std::time::Duration::from_millis(1200));
                    self.set_interaction_focus(Some(target), 30_000, now);
                    self.update_focus_tracking_for_surface(target, now_ms);
                }
            }
        }
        self.refresh_cluster_overflow_for_monitor(monitor, now_ms, true);
        true
    }
}
