use clap::{Parser, Subcommand, ValueEnum};
use kitlib::session;
use kitlib::xinput::{ScrollDirection, XInput};

#[derive(Parser, Debug)]
#[command(
    name = "kommander",
    version,
    about = "Send input actions to a live kitwin desktop session"
)]
struct Args {
    /// Persistent session name to control.
    #[arg(long)]
    session: String,

    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand, Debug)]
enum Command {
    /// Click a desktop pixel coordinate.
    Click {
        x: u32,
        y: u32,
        #[arg(long, default_value = "left")]
        button: Button,
    },
    /// Drag from one desktop pixel coordinate to another.
    Drag {
        x1: u32,
        y1: u32,
        x2: u32,
        y2: u32,
        #[arg(long, default_value = "left")]
        button: Button,
        #[arg(long, default_value_t = 20)]
        steps: u32,
    },
    /// Move the mouse pointer.
    Move { x: u32, y: u32 },
    /// Press a mouse button at a coordinate.
    Down {
        x: u32,
        y: u32,
        #[arg(long, default_value = "left")]
        button: Button,
    },
    /// Release a mouse button at a coordinate.
    Up {
        x: u32,
        y: u32,
        #[arg(long, default_value = "left")]
        button: Button,
    },
    /// Type literal text into the focused X window.
    Type {
        text: String,
        #[arg(long)]
        enter: bool,
    },
    /// Send an xdotool key chord, for example Return or ctrl+l.
    Key { chord: String },
    /// Scroll in a direction.
    Scroll {
        direction: ScrollArg,
        #[arg(long, default_value_t = 1)]
        amount: u32,
    },
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum Button {
    Left,
    Middle,
    Right,
    #[value(name = "1")]
    One,
    #[value(name = "2")]
    Two,
    #[value(name = "3")]
    Three,
}

impl Button {
    fn number(self) -> u8 {
        match self {
            Self::Left | Self::One => 1,
            Self::Middle | Self::Two => 2,
            Self::Right | Self::Three => 3,
        }
    }
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum ScrollArg {
    Up,
    Down,
    Left,
    Right,
}

impl From<ScrollArg> for ScrollDirection {
    fn from(value: ScrollArg) -> Self {
        match value {
            ScrollArg::Up => ScrollDirection::Up,
            ScrollArg::Down => ScrollDirection::Down,
            ScrollArg::Left => ScrollDirection::Left,
            ScrollArg::Right => ScrollDirection::Right,
        }
    }
}

pub fn run() {
    let args = Args::parse();

    if !session::valid_name(&args.session) {
        eprintln!("kommander: invalid session name '{}'", args.session);
        std::process::exit(1);
    }

    let Some(state) = session::lookup_live(&args.session) else {
        eprintln!(
            "kommander: no live session '{}'. Start one with: kstarter --session {}",
            args.session, args.session
        );
        std::process::exit(1);
    };

    let input = XInput::new(state.display);
    let result = match args.command {
        Command::Click { x, y, button } => {
            validate_point(&state, x, y).and_then(|()| input.click(button.number(), x, y))
        }
        Command::Drag {
            x1,
            y1,
            x2,
            y2,
            button,
            steps,
        } => validate_point(&state, x1, y1)
            .and_then(|()| validate_point(&state, x2, y2))
            .and_then(|()| input.drag(button.number(), (x1, y1), (x2, y2), steps)),
        Command::Move { x, y } => {
            validate_point(&state, x, y).and_then(|()| input.mouse_move(x, y))
        }
        Command::Down { x, y, button } => {
            validate_point(&state, x, y).and_then(|()| input.mouse_down(button.number(), x, y))
        }
        Command::Up { x, y, button } => {
            validate_point(&state, x, y).and_then(|()| input.mouse_up(button.number(), x, y))
        }
        Command::Type { text, enter } => input
            .type_text(&text)
            .and_then(|()| if enter { input.key("Return") } else { Ok(()) }),
        Command::Key { chord } => input.key(&chord),
        Command::Scroll { direction, amount } => input.scroll(direction.into(), amount),
    };

    if let Err(err) = result {
        eprintln!("kommander: {}", err);
        std::process::exit(1);
    }
}

fn validate_point(
    state: &session::State,
    x: u32,
    y: u32,
) -> Result<(), Box<dyn std::error::Error>> {
    if x >= state.width || y >= state.height {
        return Err(format!(
            "coordinate ({}, {}) is outside session bounds {}x{}",
            x, y, state.width, state.height
        )
        .into());
    }
    Ok(())
}
