//! Convert protocol `Operation` values into IDE `Operation` values.
//!
//! Extensions emit `oo_protocol::Operation` values (via `__oo_host_call`).
//! This module translates those into the strongly-typed `crate::operation::Operation`
//! enum that the IDE event loop understands.

use std::path::PathBuf;

use oo_protocol::{CommandOp, Operation as ProtoOp, TerminalOp, WindowOp, WorkspaceOp};

use crate::operation::{NotificationLevel, Operation, PickerItem as OpPickerItem, StatusSlot};
use crate::prelude::*;

// ---------------------------------------------------------------------------
// Public entry point
// ---------------------------------------------------------------------------

/// Translate a protocol operation into an IDE operation.
pub(crate) fn to_operation(op: ProtoOp) -> Result<Operation> {
    match op {
        ProtoOp::Window(WindowOp::ShowNotification { message, level }) => {
            let lvl = match level.as_str() {
                "warn" => NotificationLevel::Warn,
                "error" => NotificationLevel::Error,
                _ => NotificationLevel::Info,
            };
            Ok(Operation::ShowNotification {
                message,
                level: lvl,
            })
        }

        ProtoOp::Window(WindowOp::UpdateStatusBar { text, slot }) => {
            let s = match slot.as_deref() {
                Some("left") => StatusSlot::Left,
                Some("center") => StatusSlot::Center,
                _ => StatusSlot::Right,
            };
            Ok(Operation::UpdateStatusBar { text, slot: s })
        }

        ProtoOp::Window(WindowOp::ShowPicker { title, items }) => {
            let mapped = items
                .into_iter()
                .map(|i| OpPickerItem {
                    label: i.label,
                    value: i.value,
                })
                .collect();
            Ok(Operation::ShowPicker {
                title: title.unwrap_or_default(),
                items: mapped,
            })
        }

        ProtoOp::Workspace(WorkspaceOp::OpenFile { path }) => Ok(Operation::OpenFile {
            path: PathBuf::from(path),
        }),

        ProtoOp::Terminal(TerminalOp::Create { command }) => {
            Ok(Operation::CreateTerminal { command })
        }

        ProtoOp::Command(CommandOp::Run { id }) => {
            let cmd_id = id
                .parse::<crate::commands::CommandId>()
                .map_err(|e| anyhow!("invalid command id {:?}: {}", id, e))?;
            Ok(Operation::RunCommand {
                id: cmd_id,
                args: Default::default(),
            })
        }
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use oo_protocol::{Operation as ProtoOp, TerminalOp, WindowOp, WorkspaceOp};

    #[test]
    fn show_notification_info() {
        let op = to_operation(ProtoOp::Window(WindowOp::ShowNotification {
            message: "Build ok".into(),
            level: "info".into(),
        }))
        .unwrap();
        assert!(
            matches!(op, Operation::ShowNotification { ref message, level: NotificationLevel::Info } if message == "Build ok")
        );
    }

    #[test]
    fn show_notification_warn() {
        let op = to_operation(ProtoOp::Window(WindowOp::ShowNotification {
            message: "Watch out".into(),
            level: "warn".into(),
        }))
        .unwrap();
        assert!(matches!(
            op,
            Operation::ShowNotification {
                level: NotificationLevel::Warn,
                ..
            }
        ));
    }

    #[test]
    fn open_file_op() {
        let op = to_operation(ProtoOp::Workspace(WorkspaceOp::OpenFile {
            path: "/tmp/foo.rs".into(),
        }))
        .unwrap();
        assert!(
            matches!(op, Operation::OpenFile { ref path } if path.to_str() == Some("/tmp/foo.rs"))
        );
    }

    #[test]
    fn update_status_bar_right() {
        let op = to_operation(ProtoOp::Window(WindowOp::UpdateStatusBar {
            text: "✓ ok".into(),
            slot: Some("right".into()),
        }))
        .unwrap();
        assert!(matches!(
            op,
            Operation::UpdateStatusBar {
                slot: StatusSlot::Right,
                ..
            }
        ));
    }

    #[test]
    fn create_terminal_op() {
        let op = to_operation(ProtoOp::Terminal(TerminalOp::Create {
            command: "cargo build".into(),
        }))
        .unwrap();
        assert!(
            matches!(op, Operation::CreateTerminal { ref command } if command == "cargo build")
        );
    }
}
