//! Command protocol for manager→engine communication.
//!
//! Every [`HotkeyManager`](crate::manager::HotkeyManager) method translates to a
//! [`Command`] sent through the channel. Commands that need a response
//! include a one-shot reply sender.

use std::sync::Arc;
use std::sync::mpsc;

use kbd::action::Action;
use kbd::binding::Binding;
use kbd::binding::BindingId;
use kbd::hotkey::Hotkey;
use kbd::hotkey::HotkeySequence;
use kbd::hotkey::ModifierSet;
use kbd::introspection::ActiveLayerInfo;
use kbd::introspection::BindingInfo;
use kbd::introspection::ConflictInfo;
use kbd::key::Key;
use kbd::layer::Layer;
use kbd::layer::LayerName;
use kbd::sequence::PendingSequenceInfo;
use kbd::sequence::SequenceOptions;
use kbd::tap_hold::TapHoldOptions;

use super::wake::WakeFd;
use crate::error::ManagerStopped;

pub(crate) enum Command {
    Register {
        binding: Binding,
        reply: mpsc::Sender<Result<(), crate::error::RegisterError>>,
    },
    RegisterSequence {
        sequence: HotkeySequence,
        action: Action,
        options: SequenceOptions,
        reply: mpsc::Sender<Result<BindingId, crate::error::RegisterError>>,
    },
    RegisterTapHold {
        key: Key,
        tap_action: Action,
        hold_action: Action,
        options: TapHoldOptions,
        reply: mpsc::Sender<Result<BindingId, crate::error::RegisterError>>,
    },
    PendingSequence {
        reply: mpsc::Sender<Option<PendingSequenceInfo>>,
    },
    Unregister {
        id: BindingId,
    },
    DefineLayer {
        layer: Layer,
        reply: mpsc::Sender<Result<(), crate::error::LayerError>>,
    },
    PushLayer {
        name: LayerName,
        reply: mpsc::Sender<Result<(), crate::error::LayerError>>,
    },
    PopLayer {
        reply: mpsc::Sender<Result<LayerName, crate::error::LayerError>>,
    },
    ToggleLayer {
        name: LayerName,
        reply: mpsc::Sender<Result<(), crate::error::LayerError>>,
    },
    IsRegistered {
        hotkey: Hotkey,
        reply: mpsc::Sender<bool>,
    },
    IsKeyPressed {
        key: Key,
        reply: mpsc::Sender<bool>,
    },
    ActiveModifiers {
        reply: mpsc::Sender<ModifierSet>,
    },
    ListBindings {
        reply: mpsc::Sender<Vec<BindingInfo>>,
    },
    BindingsForKey {
        hotkey: Hotkey,
        reply: mpsc::Sender<Option<BindingInfo>>,
    },
    ActiveLayers {
        reply: mpsc::Sender<Vec<ActiveLayerInfo>>,
    },
    Conflicts {
        reply: mpsc::Sender<Vec<ConflictInfo>>,
    },
    Shutdown,
}

#[derive(Clone)]
pub(crate) struct CommandSender {
    command_tx: mpsc::Sender<Command>,
    wake_fd: Arc<WakeFd>,
}

impl CommandSender {
    pub(super) fn new(command_tx: mpsc::Sender<Command>, wake_fd: Arc<WakeFd>) -> Self {
        Self {
            command_tx,
            wake_fd,
        }
    }

    pub(crate) fn send(&self, command: Command) -> Result<(), ManagerStopped> {
        self.command_tx.send(command).map_err(|_| ManagerStopped)?;
        self.wake_fd.wake().map_err(|_| ManagerStopped)?;
        Ok(())
    }
}
