use {
    crate::{
        client::{Client, ClientError},
        ifs::{wl_seat::SeatFocusGrab, wl_surface::WlSurface},
        leaks::Tracker,
        object::{Object, Version},
        tree::NodeId,
        wire::{HyprlandFocusGrabV1Id, hyprland_focus_grab_v1::*},
    },
    ahash::AHashMap,
    std::{cell::Cell, rc::Rc},
    thiserror::Error,
};

pub struct HyprlandFocusGrabV1 {
    pub id: HyprlandFocusGrabV1Id,
    pub client: Rc<Client>,
    pub tracker: Tracker<Self>,
    pub version: Version,
    pending: std::cell::RefCell<Option<AHashMap<NodeId, Rc<WlSurface>>>>,
    committed: std::cell::RefCell<AHashMap<NodeId, Rc<WlSurface>>>,
    active: Cell<bool>,
}

impl HyprlandFocusGrabV1 {
    pub fn new(id: HyprlandFocusGrabV1Id, client: &Rc<Client>, version: Version) -> Self {
        Self {
            id,
            client: client.clone(),
            tracker: Default::default(),
            version,
            pending: Default::default(),
            committed: Default::default(),
            active: Cell::new(false),
        }
    }

    fn send_cleared(&self) {
        self.client.event(Cleared { self_id: self.id });
    }

    fn activate(self: &Rc<Self>) {
        let state = &self.client.state;
        let seats = state.globals.seats.lock();
        let focus_node = self
            .committed
            .borrow()
            .values()
            .find_map(|s| s.get_focus_node());

        for seat in seats.values() {
            if let Some(old) = seat.focus_grab.get() {
                old.clear();
            }
            seat.focus_grab
                .set(Some(self.clone() as Rc<dyn SeatFocusGrab>));
            if let Some(node) = focus_node.clone() {
                seat.grab(node);
            }
        }
        self.active.set(true);
    }

    fn deactivate(&self) {
        if !self.active.get() {
            return;
        }
        self.active.set(false);
        let state = &self.client.state;
        for seat in state.globals.seats.lock().values() {
            seat.clear_focus_grab();
        }
    }
}

impl SeatFocusGrab for HyprlandFocusGrabV1 {
    fn contains_node(&self, node_id: NodeId) -> bool {
        self.committed.borrow().contains_key(&node_id)
    }

    fn clear(self: Rc<Self>) {
        if !self.active.get() {
            return;
        }
        self.active.set(false);
        self.send_cleared();
        let state = &self.client.state;
        for seat in state.globals.seats.lock().values() {
            seat.clear_focus_grab();
        }
    }
}

impl HyprlandFocusGrabV1RequestHandler for HyprlandFocusGrabV1 {
    type Error = HyprlandFocusGrabV1Error;

    fn add_surface(&self, req: AddSurface, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        let surface = self.client.lookup(req.surface)?;
        let node_id: NodeId = surface.node_id.into();
        let mut pending = self.pending.borrow_mut();
        let pending = pending.get_or_insert_with(|| self.committed.borrow().clone());
        pending.insert(node_id, surface);
        Ok(())
    }

    fn remove_surface(&self, req: RemoveSurface, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        let surface = self.client.lookup(req.surface)?;
        let node_id: NodeId = surface.node_id.into();
        let mut pending = self.pending.borrow_mut();
        let pending = pending.get_or_insert_with(|| self.committed.borrow().clone());
        pending.remove(&node_id);
        Ok(())
    }

    fn commit(&self, _req: Commit, slf: &Rc<Self>) -> Result<(), Self::Error> {
        let was_empty = self.committed.borrow().is_empty();
        if let Some(new_committed) = self.pending.borrow_mut().take() {
            *self.committed.borrow_mut() = new_committed;
        }
        let is_empty = self.committed.borrow().is_empty();
        match (was_empty, is_empty) {
            (true, false) => slf.activate(),
            (false, true) => self.deactivate(),
            _ => {}
        }
        Ok(())
    }

    fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.deactivate();
        self.client.remove_obj(self)?;
        Ok(())
    }
}

object_base! {
    self = HyprlandFocusGrabV1;
    version = self.version;
}

impl Object for HyprlandFocusGrabV1 {}

simple_add_obj!(HyprlandFocusGrabV1);

#[derive(Debug, Error)]
pub enum HyprlandFocusGrabV1Error {
    #[error(transparent)]
    ClientError(Box<ClientError>),
}

efrom!(HyprlandFocusGrabV1Error, ClientError);
