use spacesh_proto::ids::{GroupId, SurfaceId, WorkspaceId};
use spacesh_proto::message::{Cmd, Edge, PresetSlot, SplitDir};
use spacesh_proto::status::SurfaceState;
use crate::cli::{DirArg, EdgeArg, GroupAction, StateArg, Sub};

pub fn state_of(a: StateArg) -> SurfaceState {
    match a {
        StateArg::Work => SurfaceState::Work,
        StateArg::Wait => SurfaceState::Wait,
        StateArg::Done => SurfaceState::Done,
        StateArg::Error => SurfaceState::Error,
        StateArg::Idle => SurfaceState::Idle,
    }
}

/// Map a parsed subcommand to a bus command. `Completions` has no Cmd (handled
/// before dispatch), so callers must not pass it here.
pub fn to_cmd(sub: Sub) -> Cmd {
    match sub {
        Sub::Open { path } => Cmd::Open { path },
        Sub::Status => Cmd::Status,
        Sub::NewSurface { workspace_id, cmd, args, cols, rows } => Cmd::NewSurface {
            workspace_id: WorkspaceId(workspace_id), command: cmd, args, cols, rows,
        },
        Sub::Split { surface_id, dir, cmd, args } => Cmd::SplitSurface {
            surface_id: SurfaceId(surface_id),
            dir: match dir { DirArg::Right => SplitDir::Right, DirArg::Down => SplitDir::Down },
            command: cmd, args,
        },
        Sub::Close { surface_id } => Cmd::Close { surface_id: SurfaceId(surface_id) },
        Sub::Focus { surface_id } => Cmd::Focus { surface_id: SurfaceId(surface_id) },
        Sub::Restart { surface_id, resume } => Cmd::RestartSurface { surface_id: SurfaceId(surface_id), resume },
        Sub::Notify { surface, state } => Cmd::SetState { surface_id: SurfaceId(surface), state: state_of(state) },
        Sub::ApplyPreset { workspace_id, preset, agents } => Cmd::ApplyPreset {
            workspace_id: WorkspaceId(workspace_id),
            preset_id: preset,
            slots: agents.into_iter().map(|a| if a == "shell" {
                PresetSlot { command: None, args: vec![] }
            } else {
                PresetSlot { command: Some(a), args: vec![] }
            }).collect(),
        },
        Sub::SetRatios { workspace_id, path, ratios } => Cmd::SetRatios {
            workspace_id: WorkspaceId(workspace_id), node_path: path, ratios,
        },
        Sub::Move { surface_id, target, edge } => Cmd::MoveSurface {
            surface_id: SurfaceId(surface_id),
            target_surface_id: SurfaceId(target),
            edge: match edge { EdgeArg::Left => Edge::Left, EdgeArg::Right => Edge::Right, EdgeArg::Top => Edge::Top, EdgeArg::Bottom => Edge::Bottom },
        },
        Sub::CloseWorkspace { workspace_id } => Cmd::CloseWorkspace { workspace_id: WorkspaceId(workspace_id) },
        Sub::Group { action } => match action {
            GroupAction::Create { name, color } => Cmd::CreateGroup { name, color },
            GroupAction::Set { group_id, name, color, order } => Cmd::SetGroup { group_id: GroupId(group_id), name, color, order },
            GroupAction::Delete { group_id } => Cmd::DeleteGroup { group_id: GroupId(group_id) },
        },
        Sub::SetMeta { workspace_id, name, group, unread, order } => Cmd::SetWorkspaceMeta {
            workspace_id: WorkspaceId(workspace_id),
            name,
            // None = no change; Some("") = ungroup; Some(g) = set group.
            group_id: group.map(|g| if g.is_empty() { None } else { Some(GroupId(g)) }),
            unread,
            order,
            pinned: None,
        },
        Sub::Shutdown => Cmd::Shutdown,
        Sub::Completions { .. } => unreachable!("completions handled before dispatch"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cli::Cli;
    use clap::Parser;

    fn parse(argv: &[&str]) -> Sub {
        Cli::try_parse_from(argv).unwrap().cmd
    }

    #[test]
    fn notify_maps_to_set_state() {
        let cmd = to_cmd(parse(&["spacesh", "notify", "--surface", "s_1", "--state", "done"]));
        assert!(matches!(cmd, Cmd::SetState { state: SurfaceState::Done, .. }));
    }

    #[test]
    fn split_default_dir_is_right() {
        let cmd = to_cmd(parse(&["spacesh", "split", "s_1"]));
        match cmd { Cmd::SplitSurface { dir, .. } => assert_eq!(dir, SplitDir::Right), _ => panic!() }
    }

    #[test]
    fn set_ratios_parses_csv() {
        let cmd = to_cmd(parse(&["spacesh", "set-ratios", "w_1", "--path", "0,1", "--ratios", "0.3,0.7"]));
        match cmd { Cmd::SetRatios { node_path, ratios, .. } => { assert_eq!(node_path, vec![0,1]); assert_eq!(ratios, vec![0.3,0.7]); }, _ => panic!() }
    }

    #[test]
    fn apply_preset_shell_agent_is_empty_slot() {
        let cmd = to_cmd(parse(&["spacesh", "apply-preset", "w_1", "--preset", "2lr", "--agent", "shell", "--agent", "claude"]));
        match cmd {
            Cmd::ApplyPreset { slots, .. } => {
                assert!(slots[0].command.is_none());
                assert_eq!(slots[1].command.as_deref(), Some("claude"));
            }
            _ => panic!(),
        }
    }

    #[test]
    fn set_meta_group_empty_means_ungroup() {
        let cmd = to_cmd(parse(&["spacesh", "set-meta", "w_1", "--group", ""]));
        match cmd { Cmd::SetWorkspaceMeta { group_id, .. } => assert_eq!(group_id, Some(None)), _ => panic!() }
    }
}
