use {
    crate::{
        backend::ButtonState,
        cursor::KnownCursor,
        cursor_user::CursorUser,
        fixed::Fixed,
        ifs::wl_seat::{
            BTN_LEFT, BTN_RIGHT, NodeSeatState, SeatId, WlSeatGlobal,
            tablet::{TabletTool, TabletToolChanges, TabletToolId},
        },
        rect::Rect,
        renderer::Renderer,
        state::State,
        tree::{
            ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
            NodeLayerLink, NodeLocation, OutputNode, PinnedNode, StackedNode, TileDragDestination,
            ToplevelNode, WorkspaceNode, toplevel_set_floating, walker::NodeVisitor,
        },
        utils::{
            clonecell::CloneCell, double_click_state::DoubleClickState,
            event_listener::LazyEventSource, linkedlist::LinkedNode,
        },
    },
    ahash::AHashMap,
    std::{
        cell::{Cell, RefCell},
        fmt::{Debug, Formatter},
        mem,
        rc::Rc,
    },
};

tree_id!(FloatNodeId);
pub struct FloatNode {
    pub id: FloatNodeId,
    pub state: Rc<State>,
    pub visible: Cell<bool>,
    pub position: Cell<Rect>,
    pub display_link: RefCell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
    pub workspace_link: Cell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
    pub pinned_link: RefCell<Option<LinkedNode<Rc<dyn PinnedNode>>>>,
    pub workspace: CloneCell<Rc<WorkspaceNode>>,
    pub location: Cell<NodeLocation>,
    pub child: CloneCell<Option<Rc<dyn ToplevelNode>>>,
    pub active: Cell<bool>,
    pub seat_state: NodeSeatState,
    pub layout_scheduled: Cell<bool>,
    cursors: RefCell<AHashMap<CursorType, CursorState>>,
    pub attention_requested: Cell<bool>,
    pub layout_complete: Rc<LazyEventSource>,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum CursorType {
    Seat(SeatId),
    TabletTool(TabletToolId),
}

struct CursorState {
    cursor: KnownCursor,
    target: bool,
    x: i32,
    y: i32,
    op_type: OpType,
    op_active: bool,
    dist_hor: i32,
    dist_ver: i32,
    double_click_state: DoubleClickState,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum OpType {
    Move,
    ResizeLeft,
    ResizeTop,
    ResizeRight,
    ResizeBottom,
    ResizeTopLeft,
    ResizeTopRight,
    ResizeBottomLeft,
    ResizeBottomRight,
}

pub async fn float_layout(state: Rc<State>) {
    loop {
        let node = state.pending_float_layout.pop().await;
        if node.layout_scheduled.get() {
            node.perform_layout();
        }
    }
}

impl FloatNode {
    pub fn new(
        state: &Rc<State>,
        ws: &Rc<WorkspaceNode>,
        position: Rect,
        child: Rc<dyn ToplevelNode>,
    ) -> Rc<Self> {
        let floater = Rc::new(FloatNode {
            id: state.node_ids.next(),
            state: state.clone(),
            visible: Cell::new(ws.float_visible()),
            position: Cell::new(position),
            display_link: RefCell::new(None),
            workspace_link: Cell::new(None),
            pinned_link: RefCell::new(None),
            workspace: CloneCell::new(ws.clone()),
            location: Cell::new(ws.location()),
            child: CloneCell::new(Some(child.clone())),
            active: Cell::new(false),
            seat_state: Default::default(),
            layout_scheduled: Cell::new(false),
            cursors: Default::default(),
            attention_requested: Cell::new(false),
            layout_complete: state.post_layout_event_sources.create_source(),
        });
        floater.pull_child_properties();
        *floater.display_link.borrow_mut() = Some(state.root.stacked.add_last(floater.clone()));
        floater
            .workspace_link
            .set(Some(ws.stacked.add_last(floater.clone())));
        child.tl_set_parent(floater.clone());
        child.tl_set_visible(floater.visible.get());
        child.tl_restack_popups();
        floater.schedule_layout();
        if floater.visible.get() {
            state.damage(position);
        }
        if child.tl_data().pinned.get() {
            floater.toggle_pinned();
        }
        floater
    }

    pub fn on_spaces_changed(self: &Rc<Self>) {
        self.schedule_layout();
    }

    pub fn on_colors_changed(self: &Rc<Self>) {
        // No title rendering needed
    }

    pub fn schedule_layout(self: &Rc<Self>) {
        if !self.layout_scheduled.replace(true) {
            self.state.pending_float_layout.push(self.clone());
        }
    }

    fn perform_layout(self: &Rc<Self>) {
        let child = match self.child.get() {
            Some(c) => c,
            _ => return,
        };
        let pos = self.position.get();
        let theme = &self.state.theme;
        let bw = theme.sizes.border_width.get();
        let cpos = Rect::new_sized_saturating(
            pos.x1() + bw,
            pos.y1() + bw,
            pos.width() - 2 * bw,
            pos.height() - 2 * bw,
        );
        child.clone().tl_change_extents(&cpos);
        self.layout_scheduled.set(false);
        self.layout_complete.trigger();
    }

    fn pointer_move(
        self: &Rc<Self>,
        id: CursorType,
        cursor: &CursorUser,
        x: Fixed,
        y: Fixed,
        target: bool,
    ) {
        let x = x.round_down();
        let y = y.round_down();
        let theme = &self.state.theme;
        let bw = theme.sizes.border_width.get();
        let mut seats = self.cursors.borrow_mut();
        let seat_state = seats.entry(id).or_insert_with(|| CursorState {
            cursor: KnownCursor::Default,
            target,
            x,
            y,
            op_type: OpType::Move,
            op_active: false,
            dist_hor: 0,
            dist_ver: 0,
            double_click_state: Default::default(),
        });
        seat_state.x = x;
        seat_state.y = y;
        let pos = self.position.get();
        if seat_state.op_active {
            let mut x1 = pos.x1();
            let mut y1 = pos.y1();
            let mut x2 = pos.x2();
            let mut y2 = pos.y2();
            match seat_state.op_type {
                OpType::Move => {
                    let dx = x - seat_state.dist_hor;
                    let dy = y - seat_state.dist_ver;
                    x1 += dx;
                    y1 += dy;
                    x2 += dx;
                    y2 += dy;
                }
                OpType::ResizeLeft => {
                    x1 += x - seat_state.dist_hor;
                    x1 = x1.min(x2 - 2 * bw);
                }
                OpType::ResizeTop => {
                    y1 += y - seat_state.dist_ver;
                    y1 = y1.min(y2 - 2 * bw);
                }
                OpType::ResizeRight => {
                    x2 += x - pos.width() + seat_state.dist_hor;
                    x2 = x2.max(x1 + 2 * bw);
                }
                OpType::ResizeBottom => {
                    y2 += y - pos.height() + seat_state.dist_ver;
                    y2 = y2.max(y1 + 2 * bw);
                }
                OpType::ResizeTopLeft => {
                    x1 += x - seat_state.dist_hor;
                    y1 += y - seat_state.dist_ver;
                    x1 = x1.min(x2 - 2 * bw);
                    y1 = y1.min(y2 - 2 * bw);
                }
                OpType::ResizeTopRight => {
                    x2 += x - pos.width() + seat_state.dist_hor;
                    y1 += y - seat_state.dist_ver;
                    x2 = x2.max(x1 + 2 * bw);
                    y1 = y1.min(y2 - 2 * bw);
                }
                OpType::ResizeBottomLeft => {
                    x1 += x - seat_state.dist_hor;
                    y2 += y - pos.height() + seat_state.dist_ver;
                    x1 = x1.min(x2 - 2 * bw);
                    y2 = y2.max(y1 + 2 * bw);
                }
                OpType::ResizeBottomRight => {
                    x2 += x - pos.width() + seat_state.dist_hor;
                    y2 += y - pos.height() + seat_state.dist_ver;
                    x2 = x2.max(x1 + 2 * bw);
                    y2 = y2.max(y1 + 2 * bw);
                }
            }
            let new_pos = Rect::new_saturating(x1, y1, x2, y2);
            self.position.set(new_pos);
            if self.visible.get() {
                self.state.damage(pos);
                self.state.damage(new_pos);
            }
            self.schedule_layout();
            return;
        }
        let resize_left = x < bw;
        let resize_right = x >= pos.width() - bw;
        let resize_top = y < bw;
        let resize_bottom = y >= pos.height() - bw;
        let id = 0
            | ((resize_left as usize) << 0)
            | ((resize_right as usize) << 1)
            | ((resize_top as usize) << 2)
            | ((resize_bottom as usize) << 3);
        const OP_TYPES: [OpType; 16] = [
            OpType::Move,              // 0000
            OpType::ResizeLeft,        // 0001
            OpType::ResizeRight,       // 0010
            OpType::Move,              // 0011
            OpType::ResizeTop,         // 0100
            OpType::ResizeTopLeft,     // 0101
            OpType::ResizeTopRight,    // 0110
            OpType::Move,              // 0111
            OpType::ResizeBottom,      // 1000
            OpType::ResizeBottomLeft,  // 1001
            OpType::ResizeBottomRight, // 1010
            OpType::Move,              // 1011
            OpType::Move,              // 1100
            OpType::Move,              // 1101
            OpType::Move,              // 1110
            OpType::Move,              // 1111
        ];
        let op_type = OP_TYPES[id];
        let new_cursor = match op_type {
            OpType::Move => KnownCursor::Default,
            OpType::ResizeLeft => KnownCursor::EwResize,
            OpType::ResizeTop => KnownCursor::NsResize,
            OpType::ResizeRight => KnownCursor::EwResize,
            OpType::ResizeBottom => KnownCursor::NsResize,
            OpType::ResizeTopLeft => KnownCursor::NwResize,
            OpType::ResizeTopRight => KnownCursor::NeResize,
            OpType::ResizeBottomLeft => KnownCursor::SwResize,
            OpType::ResizeBottomRight => KnownCursor::SeResize,
        };
        seat_state.op_type = op_type;
        if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) {
            if seat_state.target {
                cursor.set_known(new_cursor);
            }
        }
    }

    fn set_workspace_(
        self: &Rc<Self>,
        ws: &Rc<WorkspaceNode>,
        update_pinned: bool,
        update_visible: bool,
    ) {
        if let Some(c) = self.child.get() {
            c.tl_set_workspace(ws);
        }
        self.workspace_link
            .set(Some(ws.stacked.add_last(self.clone())));
        self.workspace.set(ws.clone());
        self.location.set(ws.location());
        if update_visible {
            self.stacked_set_visible(ws.float_visible());
        }
        if update_pinned && let Some(pl) = &*self.pinned_link.borrow_mut() {
            ws.output.get().pinned.add_last_existing(pl);
        }
    }

    pub fn after_ws_move(self: &Rc<Self>, output: &Rc<OutputNode>) {
        if let Some(pinned) = &*self.pinned_link.borrow() {
            output.pinned.add_last_existing(pinned);
        }
        if output.is_dummy {
            return;
        }
        let pos = self.position.get();
        let opos = output.global.pos.get();
        if pos.intersects(&opos) {
            return;
        }
        let bw = self.state.theme.sizes.border_width.get();
        let th = self.state.theme.title_height();
        let mut x1 = pos.x1();
        let mut x2 = pos.x2();
        let mut y1 = pos.y1();
        let mut y2 = pos.y2();
        const DELTA: i32 = 100;
        let delta = bw + DELTA;
        macro_rules! adjust {
            ($z1:ident, $z2:ident) => {
                if $z1 > opos.$z2() - delta {
                    $z1 = (opos.$z2() - delta).max(opos.$z1());
                    $z2 += $z1 - pos.$z1();
                } else if $z2 < opos.$z1() + delta {
                    $z2 = (opos.$z1() + delta).min(opos.$z2());
                    $z1 += $z2 - pos.$z2();
                }
            };
        }
        adjust!(x1, x2);
        adjust!(y1, y2);
        if y1 + bw + th <= opos.y1() {
            y1 = opos.y1();
            y2 += y1 - pos.y1();
        }
        let new_pos = Rect::new_saturating(x1, y1, x2, y2);
        self.position.set(new_pos);
        if self.visible.get() {
            self.state.damage(pos);
            self.state.damage(new_pos);
        }
        self.schedule_layout();
    }

    fn update_child_title(self: &Rc<Self>, _title: &str) {
        // No title rendering
    }

    fn update_child_active(self: &Rc<Self>, active: bool) {
        if self.active.replace(active) != active {
            if active {
                self.restack();
            }
        }
    }

    fn pull_child_properties(self: &Rc<Self>) {
        let child = match self.child.get() {
            None => return,
            Some(c) => c,
        };
        let data = child.tl_data();
        let activation_requested = data.wants_attention.get();
        self.attention_requested.set(activation_requested);
        if activation_requested {
            self.workspace
                .get()
                .cnode_child_attention_request_changed(&**self, true);
        }
        self.update_child_title(&data.title.borrow());
        self.update_child_active(data.active());
    }

    fn discard_child_properties(&self) {
        if self.attention_requested.get() {
            self.workspace
                .get()
                .cnode_child_attention_request_changed(self, false);
        }
    }

    fn restack(&self) {
        if let Some(dl) = &*self.display_link.borrow() {
            if dl.next().is_none() {
                return;
            }
            self.state.damage(self.position.get());
            self.state.root.stacked.add_last_existing(&dl);
            if let Some(tl) = self.child.get() {
                tl.tl_restack_popups();
            }
            self.state.tree_changed();
        }
    }

    fn toggle_pinned(self: &Rc<Self>) {
        let pl = &mut *self.pinned_link.borrow_mut();
        *pl = if pl.is_some() {
            None
        } else {
            let output = self.workspace.get().output.get();
            Some(output.pinned.add_last(self.clone()))
        };
        if let Some(tl) = self.child.get() {
            tl.tl_data().pinned.set(pl.is_some());
        }
    }

    fn button(
        self: Rc<Self>,
        id: CursorType,
        cursor: &CursorUser,
        seat: &Rc<WlSeatGlobal>,
        time_usec: u64,
        pressed: bool,
    ) {
        let mut cursors = self.cursors.borrow_mut();
        let cursor_data = match cursors.get_mut(&id) {
            Some(s) => s,
            _ => return,
        };
        if !cursor_data.op_active {
            if !pressed {
                return;
            }
            if cursor_data.op_type == OpType::Move
                && let Some(tl) = self.child.get()
            {
                tl.node_do_focus(seat, Direction::Unspecified);
            }
            if cursor_data.double_click_state.click(
                &self.state,
                time_usec,
                cursor_data.x,
                cursor_data.y,
            ) && cursor_data.op_type == OpType::Move
                && let Some(tl) = self.child.get()
            {
                drop(cursors);
                toplevel_set_floating(&self.state, tl, false);
                return;
            }
            cursor_data.op_active = true;
            let pos = self.position.get();
            match cursor_data.op_type {
                OpType::Move => {
                    self.restack();
                    cursor_data.dist_hor = cursor_data.x;
                    cursor_data.dist_ver = cursor_data.y;
                }
                OpType::ResizeLeft => cursor_data.dist_hor = cursor_data.x,
                OpType::ResizeTop => cursor_data.dist_ver = cursor_data.y,
                OpType::ResizeRight => cursor_data.dist_hor = pos.width() - cursor_data.x,
                OpType::ResizeBottom => cursor_data.dist_ver = pos.height() - cursor_data.y,
                OpType::ResizeTopLeft => {
                    cursor_data.dist_hor = cursor_data.x;
                    cursor_data.dist_ver = cursor_data.y;
                }
                OpType::ResizeTopRight => {
                    cursor_data.dist_hor = pos.width() - cursor_data.x;
                    cursor_data.dist_ver = cursor_data.y;
                }
                OpType::ResizeBottomLeft => {
                    cursor_data.dist_hor = cursor_data.x;
                    cursor_data.dist_ver = pos.height() - cursor_data.y;
                }
                OpType::ResizeBottomRight => {
                    cursor_data.dist_hor = pos.width() - cursor_data.x;
                    cursor_data.dist_ver = pos.height() - cursor_data.y;
                }
            }
        } else if !pressed {
            cursor_data.op_active = false;
            let ws = cursor.output().ensure_workspace();
            self.set_workspace_(&ws, true, true);
        }
    }

    pub fn tile_drag_destination(
        self: &Rc<Self>,
        source: NodeId,
        abs_x: i32,
        abs_y: i32,
    ) -> Option<TileDragDestination> {
        let child = self.child.get()?;
        let theme = &self.state.theme.sizes;
        let bw = theme.border_width.get();
        let pos = self.position.get();
        let body = Rect::new(pos.x1() + bw, pos.y1() + bw, pos.x2() - bw, pos.y2() - bw)?;
        child.tl_tile_drag_destination(source, None, body, abs_x, abs_y)
    }
}

impl Debug for FloatNode {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FloatNode").finish_non_exhaustive()
    }
}

impl Node for FloatNode {
    fn node_id(&self) -> NodeId {
        self.id.into()
    }

    fn node_seat_state(&self) -> &NodeSeatState {
        &self.seat_state
    }

    fn node_visit(self: Rc<Self>, visitor: &mut dyn NodeVisitor) {
        visitor.visit_float(&self);
    }

    fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) {
        if let Some(c) = self.child.get() {
            c.node_visit(visitor);
        }
    }

    fn node_visible(&self) -> bool {
        self.visible.get()
    }

    fn node_absolute_position(&self) -> Rect {
        self.position.get()
    }

    fn node_output(&self) -> Option<Rc<OutputNode>> {
        Some(self.workspace.get().output.get())
    }

    fn node_location(&self) -> Option<NodeLocation> {
        Some(self.location.get())
    }

    fn node_layer(&self) -> NodeLayerLink {
        let Some(l) = self.display_link.borrow().as_ref().map(|l| l.to_ref()) else {
            return NodeLayerLink::Display;
        };
        NodeLayerLink::Stacked(l)
    }

    fn node_child_title_changed(self: Rc<Self>, _child: &dyn Node, _title: &str) {
        // No title rendering
    }

    fn node_accepts_focus(&self) -> bool {
        if let Some(c) = self.child.get() {
            return c.tl_accepts_keyboard_focus();
        }
        false
    }

    fn node_do_focus(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, direction: Direction) {
        if let Some(c) = self.child.get() {
            c.node_do_focus(seat, direction);
        }
    }

    fn node_find_tree_at(
        &self,
        x: i32,
        y: i32,
        tree: &mut Vec<FoundNode>,
        usecase: FindTreeUsecase,
    ) -> FindTreeResult {
        let theme = &self.state.theme;
        let bw = theme.sizes.border_width.get();
        let pos = self.position.get();
        if x < bw || x >= pos.width() - bw {
            return FindTreeResult::AcceptsInput;
        }
        if y < bw || y >= pos.height() - bw {
            return FindTreeResult::AcceptsInput;
        }
        let child = match self.child.get() {
            Some(c) => c,
            _ => return FindTreeResult::Other,
        };
        let x = x - bw;
        let y = y - bw;
        tree.push(FoundNode {
            node: child.clone(),
            x,
            y,
        });
        child.node_find_tree_at(x, y, tree, usecase)
    }

    fn node_child_active_changed(self: Rc<Self>, _child: &dyn Node, active: bool, _depth: u32) {
        self.update_child_active(active);
    }

    fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) {
        renderer.render_floating(self, x, y)
    }

    fn node_make_visible(self: Rc<Self>) {
        if self.visible.get() {
            return;
        }
        self.workspace.get().cnode_make_visible(&*self);
    }

    fn node_on_button(
        self: Rc<Self>,
        seat: &Rc<WlSeatGlobal>,
        time_usec: u64,
        button: u32,
        state: ButtonState,
        _serial: u64,
    ) {
        if button == BTN_RIGHT && state == ButtonState::Pressed {
            self.toggle_pinned();
        }
        if button != BTN_LEFT {
            return;
        }
        self.button(
            CursorType::Seat(seat.id()),
            seat.pointer_cursor(),
            seat,
            time_usec,
            state == ButtonState::Pressed,
        );
    }

    fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
        self.pointer_move(
            CursorType::Seat(seat.id()),
            seat.pointer_cursor(),
            x,
            y,
            false,
        );
    }

    fn node_on_pointer_unfocus(&self, seat: &Rc<WlSeatGlobal>) {
        let mut cursors = self.cursors.borrow_mut();
        let id = CursorType::Seat(seat.id());
        if let Some(seat_state) = cursors.get_mut(&id) {
            seat_state.target = false;
        }
    }

    fn node_on_pointer_focus(&self, seat: &Rc<WlSeatGlobal>) {
        // log::info!("float focus");
        let mut cursors = self.cursors.borrow_mut();
        let id = CursorType::Seat(seat.id());
        if let Some(seat_state) = cursors.get_mut(&id) {
            seat_state.target = true;
            seat.pointer_cursor().set_known(seat_state.cursor);
        }
    }

    fn node_on_pointer_motion(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, x: Fixed, y: Fixed) {
        self.pointer_move(
            CursorType::Seat(seat.id()),
            seat.pointer_cursor(),
            x,
            y,
            false,
        );
    }

    fn node_on_tablet_tool_leave(&self, tool: &Rc<TabletTool>, _time_usec: u64) {
        let id = CursorType::TabletTool(tool.id);
        self.cursors.borrow_mut().remove(&id);
    }

    fn node_on_tablet_tool_enter(
        self: Rc<Self>,
        tool: &Rc<TabletTool>,
        _time_usec: u64,
        x: Fixed,
        y: Fixed,
    ) {
        tool.cursor().set_known(KnownCursor::Default);
        self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, true);
    }

    fn node_on_tablet_tool_apply_changes(
        self: Rc<Self>,
        tool: &Rc<TabletTool>,
        time_usec: u64,
        changes: Option<&TabletToolChanges>,
        x: Fixed,
        y: Fixed,
    ) {
        self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, false);
        if let Some(changes) = changes
            && let Some(pressed) = changes.down
        {
            self.button(
                CursorType::TabletTool(tool.id),
                tool.cursor(),
                tool.seat(),
                time_usec,
                pressed,
            );
        }
    }

    fn node_into_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
        Some(self.clone())
    }

    fn node_into_containing_node(self: Rc<Self>) -> Option<Rc<dyn ContainingNode>> {
        Some(self)
    }

    fn node_is_float(&self) -> bool {
        true
    }
}

impl ContainingNode for FloatNode {
    fn cnode_replace_child(self: Rc<Self>, _old: &dyn Node, new: Rc<dyn ToplevelNode>) {
        self.discard_child_properties();
        self.child.set(Some(new.clone()));
        new.tl_set_parent(self.clone());
        self.pull_child_properties();
        new.tl_set_visible(self.visible.get());
        self.schedule_layout();
        if self.visible.get() {
            self.state.damage(self.position.get());
        }
    }

    fn cnode_remove_child2(self: Rc<Self>, _child: &dyn Node, _preserve_focus: bool) {
        self.discard_child_properties();
        self.child.set(None);
        self.display_link.borrow_mut().take();
        self.workspace_link.set(None);
        self.pinned_link.take();
        if self.visible.get() {
            self.state.damage(self.position.get());
        }
    }

    fn cnode_accepts_child(&self, _node: &dyn Node) -> bool {
        true
    }

    fn cnode_child_attention_request_changed(self: Rc<Self>, _node: &dyn Node, set: bool) {
        if self.attention_requested.replace(set) != set {
            self.workspace
                .get()
                .cnode_child_attention_request_changed(&*self, set);
        }
    }

    fn cnode_workspace(self: Rc<Self>) -> Rc<WorkspaceNode> {
        self.workspace.get()
    }

    fn cnode_make_visible(self: Rc<Self>, _child: &dyn Node) {
        self.node_make_visible();
    }

    fn cnode_set_child_position(self: Rc<Self>, _child: &dyn Node, x: i32, y: i32) {
        let theme = &self.state.theme;
        let bw = theme.sizes.border_width.get();
        let (x, y) = (x - bw, y - bw);
        let pos = self.position.get();
        if pos.position() != (x, y) {
            let new_pos = pos.at_point(x, y);
            self.position.set(new_pos);
            self.state.damage(pos);
            self.state.damage(new_pos);
            self.schedule_layout();
        }
    }

    fn cnode_resize_child(
        self: Rc<Self>,
        _child: &dyn Node,
        new_x1: Option<i32>,
        new_y1: Option<i32>,
        new_x2: Option<i32>,
        new_y2: Option<i32>,
    ) {
        let theme = &self.state.theme;
        let bw = theme.sizes.border_width.get();
        let pos = self.position.get();
        let mut x1 = pos.x1();
        let mut x2 = pos.x2();
        let mut y1 = pos.y1();
        let mut y2 = pos.y2();
        if let Some(v) = new_x1 {
            x1 = (v - bw).min(x2 - bw - bw);
        }
        if let Some(v) = new_x2 {
            x2 = (v + bw).max(x1 + bw + bw);
        }
        if let Some(v) = new_y1 {
            y1 = (v - bw).min(y2 - bw - bw);
        }
        if let Some(v) = new_y2 {
            y2 = (v + bw).max(y1 + bw + bw);
        }
        let new_pos = Rect::new_saturating(x1, y1, x2, y2);
        if new_pos != pos {
            self.position.set(new_pos);
            if self.visible.get() {
                self.state.damage(pos);
                self.state.damage(new_pos);
            }
            self.schedule_layout();
        }
    }

    fn cnode_pinned(&self) -> bool {
        self.pinned_link.borrow().is_some()
    }

    fn cnode_set_pinned(self: Rc<Self>, pinned: bool) {
        if self.pinned_link.borrow().is_some() == pinned {
            return;
        }
        self.toggle_pinned();
    }

    fn cnode_get_float(self: Rc<Self>) -> Option<Rc<FloatNode>> {
        Some(self)
    }
}

impl StackedNode for FloatNode {
    fn stacked_set_visible(&self, visible: bool) {
        if self.visible.replace(visible) != visible {
            self.state.damage(self.position.get());
        }
        if let Some(child) = self.child.get() {
            child.tl_set_visible(visible);
        }
        self.seat_state.set_visible(self, visible);
    }

    fn stacked_has_workspace_link(&self) -> bool {
        true
    }
}

impl PinnedNode for FloatNode {
    fn set_workspace(self: Rc<Self>, workspace: &Rc<WorkspaceNode>, update_visible: bool) {
        self.set_workspace_(workspace, false, update_visible);
    }
}

impl dyn Node {
    pub fn node_restack(self: &Rc<Self>) {
        if let Some(tl) = self.clone().node_toplevel()
            && let Some(float) = tl.tl_data().float.get()
        {
            float.restack();
        }
    }
}
