use serde::{Deserialize, Serialize};
use std::{
    io,
    process::{Command, Stdio},
};
use thiserror::Error;

/// Node in sway's tree of containers/windows.
#[derive(Serialize, Deserialize, Debug)]
struct Node {
    nodes: Vec<Node>,
    floating_nodes: Vec<Node>,
    focused: bool,
    rect: crate::Region,
}

impl Node {
    fn take_focused(self) -> Option<Node> {
        if self.focused {
            return Some(self);
        }

        self.nodes
            .into_iter()
            .chain(self.floating_nodes)
            .find_map(Node::take_focused)
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct OutputNode {
    focused: Option<bool>,
    rect: crate::Region,
}

fn find_output_rect(outputs: &[OutputNode]) -> Option<&crate::Region> {
    outputs.iter().find_map(|n| {
        if n.focused == Some(true) {
            Some(&n.rect)
        } else {
            None
        }
    })
}

#[derive(Error, Debug)]
pub(crate) enum Error {
    #[error("no focused output found")]
    NoFocusedOutput,

    #[error("no focused window found")]
    NoFocusedWindow,

    #[error("running swaymsg failed")]
    SwayMsgFailed(#[from] io::Error),

    #[error("swaymsg returned invalid JSON output")]
    SwayMsgBogusOutput(#[from] serde_json::Error),
}

/// Returns the currently focused window.
pub(crate) fn get_active_window_geom() -> Result<crate::Region, Error> {
    let output = Command::new("swaymsg")
        .args(["-t", "get_tree"])
        .stdout(Stdio::piped())
        .output()?;

    let tree: Node = serde_json::from_slice(&output.stdout)?;
    let focused = tree.take_focused().ok_or(Error::NoFocusedWindow)?;

    Ok(focused.rect)
}

/// Returns the currently focused output rect.
pub(crate) fn get_focused_output_rect() -> Result<crate::Region, Error> {
    let output = Command::new("swaymsg")
        .args(["-t", "get_outputs"])
        .stdout(Stdio::piped())
        .output()?;

    let tree: Vec<OutputNode> = serde_json::from_slice(&output.stdout)?;
    let output_rect = find_output_rect(&tree).ok_or(Error::NoFocusedOutput)?;

    Ok(output_rect.clone())
}
