hyprswitch 3.3.2

A CLI/GUI that allows switching between windows in Hyprland
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};

use anyhow::Context;
use hyprland::data::{Client, Monitor, Monitors};
use hyprland::prelude::{HyprData, HyprDataActiveOptional, HyprDataVec};
use hyprland::shared::Address;
use tracing::info;

pub use data::collect_data;
pub use exec::switch_to_active;

use crate::handle::next::{find_next_client, find_next_monitor, find_next_workspace};
use crate::{Active, DispatchConfig, HyprlandData, SwitchType};

mod data;
mod exec;
mod next;
mod run;
mod sort;

pub use run::run_program;

pub fn find_next(
    switch_type: &SwitchType,
    dispatch_config: &DispatchConfig,
    clients_data: &HyprlandData,
    active: Option<&Active>,
) -> anyhow::Result<Active> {
    match switch_type {
        SwitchType::Client => {
            let (addr, _) = find_next_client(
                dispatch_config,
                &clients_data.clients,
                if let Some(Active::Client(addr)) = &active {
                    Some(addr)
                } else {
                    None
                },
            )
            .with_context(|| format!("Failed to find next client with dispatch_config {dispatch_config:?}"))?;
            info!("Next client: {:?}", addr);
            Ok(Active::Client(addr.clone()))
        }
        SwitchType::Workspace => {
            let (workspace_id, _) = find_next_workspace(
                dispatch_config,
                &clients_data.workspaces,
                if let Some(Active::Workspace(ws)) = &active {
                    Some(ws)
                } else {
                    None
                },
            )
            .with_context(|| format!("Failed to find next workspace with dispatch_config {dispatch_config:?}"))?;
            info!("Next workspace: {:?}", workspace_id);
            Ok(Active::Workspace(*workspace_id))
        }
        SwitchType::Monitor => {
            let (monitor_id, _) = find_next_monitor(
                dispatch_config,
                &clients_data.monitors,
                if let Some(Active::Monitor(monitor)) = &active {
                    Some(monitor)
                } else {
                    None
                },
            )
            .with_context(|| format!("Failed to find next monitor with dispatch_config {dispatch_config:?}"))?;
            info!("Next monitor: {:?}", monitor_id);
            Ok(Active::Monitor(*monitor_id))
        }
    }
}

fn get_recent_clients_map() -> &'static Mutex<HashMap<Address, i8>> {
    static MAP_LOCK: OnceLock<Mutex<HashMap<Address, i8>>> = OnceLock::new();
    MAP_LOCK.get_or_init(|| Mutex::new(HashMap::new()))
}

pub fn clear_recent_clients() {
    get_recent_clients_map()
        .lock()
        .expect("Failed to lock focus_map")
        .clear();
}

pub fn get_monitors() -> Vec<Monitor> {
    Monitors::get().map_or(vec![], |monitors| monitors.to_vec())
}

pub fn get_active_monitor() -> Option<String> {
    match Client::get_active().map(|c| {
        c.map(|c| {
            Monitors::get().map(|monitors| {
                monitors
                    .iter()
                    .find(|m| m.id == c.monitor)
                    .map(|mm| mm.name.clone())
            })
        })
    }) {
        Ok(Some(Ok(Some(monitor)))) => Some(monitor),
        _ => None,
    }
}