/*
 * comm_channel.rs
 *
 * Copyright (C) 2022-2026 Posit Software, PBC. All rights reserved.
 *
 */

use serde_json::Value;
use strum_macros::EnumString;

use super::ui_comm::UiFrontendRequest;
use crate::wire::header::JupyterHeader;
use crate::wire::jupyter_message::MessageType;

#[derive(EnumString, PartialEq)]
#[strum(serialize_all = "camelCase")]
pub enum Comm {
    /// A variables pane.
    Variables,

    /// A wrapper for a Language Server Protocol server.
    Lsp,

    /// A wrapper for a Debug Adapter Protocol server.
    Dap,

    /// A dynamic (resizable) plot.
    Plot,

    /// A data viewer.
    DataViewer,

    /// The Positron help pane.
    Help,

    /// The Positron frontend.
    Ui,

    /// Some other comm with a custom name.
    Other(String),
}

/// Messages exchanged on a comm channel.
///
/// The `Open` and `Close` variants are lifecycle signals rather than wire
/// content. They exist because the legacy comm path routes everything through
/// a single `incoming_tx`/`outgoing_tx` channel pair. Once all comms are
/// migrated to the blocking `CommHandler` path, these variants can be removed
/// and `CommMsg` can be reduced to just `Rpc` and `Data`. `Open` is a
/// special case: backend-initiated comms currently send it through
/// `outgoing_tx` to trigger the `comm_open` IOPub message. Removing it
/// would require a dedicated channel or method for that notification.
#[derive(Clone, Debug)]
pub enum CommMsg {
    /// A message indicating that the comm channel is being opened.
    /// Used for backend-initiated comms to notify the frontend.
    Open {
        /// The target name (comm type identifier)
        target_name: String,
        /// Initial data to send with the open message
        data: Value,
    },

    /// A message that is part of a Remote Procedure Call (RPC).
    Rpc {
        /// Unique ID of the RPC invocation (the Jupyter message ID)
        id: String,
        /// Parent header from the original request, echoed back in replies
        /// for proper message parenting
        parent_header: JupyterHeader,
        /// The data associated with the RPC (request or response)
        data: Value,
    },

    /// A message representing any other data sent on the comm channel; usually
    /// used for events.
    Data(Value),

    /// A message indicating that the comm channel should be closed.
    Close,
}

impl MessageType for UiFrontendRequest {
    fn message_type() -> String {
        String::from("rpc_request")
    }
}

/// Create an RPC message nested in a comm message
/// Creates a `CommMsg::Data` with `method` and `params` fields.
///
/// Example usage:
///
/// ```
/// comm_rpc_message!("my_method")
/// comm_rpc_message!("my_method", foo = 1, bar = my_value)
/// ```
#[macro_export]
macro_rules! comm_rpc_message {
    ($method:expr) => {
        CommMsg::Data(serde_json::json!({
            "method": $method,
            "params": {}
        }))
    };
    ($method:expr, $($param_key:ident = $param_value:expr),+ $(,)?) => {
        CommMsg::Data(serde_json::json!({
            "method": $method,
            "params": {
                $(
                    stringify!($param_key): $param_value
                ),*
            }
        }))
    };
}

pub fn comm_rpc_message(method: &str, params: Value) -> CommMsg {
    CommMsg::Data(serde_json::json!({
        "method": method,
        "params": params
    }))
}
