// GENERATED by `tf-schema codegen --target rpc-rust` — DO NOT EDIT BY HAND.

#![allow(unused_imports, non_camel_case_types, non_snake_case, clippy::all)]

use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::mpsc;

use tf_types::rpc::{RpcClient, RpcContext, RpcError, RpcServer, RpcTransport};


/// Example code-helper service used by Phase 4 tests and demos.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FetchFileRequest {
    /// Repo-relative path.
    pub path: String,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FetchFileResponse {
    /// Echoed path.
    pub path: String,
    /// UTF-8 file contents.
    pub contents: String,
    /// Byte length of contents.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub size: Option<i64>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StreamDirectoryRequest {
    /// Directory path.
    pub path: String,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StreamDirectoryResponse {
    /// Entry name.
    pub name: String,
    /// Entry type.
    pub kind: StreamDirectoryResponse_Kind,
    /// Byte size for files, 0 for dirs.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub size: Option<i64>,
}

/// Entry type.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum StreamDirectoryResponse_Kind {
    #[serde(rename = "file")]
    File,
    #[serde(rename = "dir")]
    Dir,
}


pub struct CodeHelperClient<T: RpcTransport + 'static> {
    inner: Arc<RpcClient<T>>,
}

impl<T: RpcTransport + 'static> CodeHelperClient<T> {
    pub fn new(inner: Arc<RpcClient<T>>) -> Self {
        CodeHelperClient { inner }
    }
    /// Return the contents of a file in the project working tree.
    pub async fn fetch_file(&self, req: &FetchFileRequest) -> Result<FetchFileResponse, tf_types::rpc::RpcCallError> {
        let value = self.inner.call_raw("fetchFile", serde_json::to_value(req).map_err(|e| tf_types::rpc::RpcCallError { code: tf_types::rpc::RpcErrorCode::InvalidArgument, message: e.to_string() })?).await?;
        serde_json::from_value(value).map_err(|e| tf_types::rpc::RpcCallError { code: tf_types::rpc::RpcErrorCode::Internal, message: e.to_string() })
    }
    /// Stream directory entries one at a time.
    pub fn stream_directory(&self, req: &StreamDirectoryRequest) -> mpsc::UnboundedReceiver<Result<StreamDirectoryResponse, RpcError>> {
        let value = serde_json::to_value(req).expect("serialize request");
        let mut raw = self.inner.server_stream_raw("streamDirectory", value);
        let (tx, rx) = mpsc::unbounded_channel();
        tokio::spawn(async move {
            while let Some(item) = raw.recv().await {
                match item {
                    Ok(v) => {
                        match serde_json::from_value::<StreamDirectoryResponse>(v) {
                            Ok(parsed) => { let _ = tx.send(Ok(parsed)); },
                            Err(e) => { let _ = tx.send(Err(RpcError { code: tf_types::rpc::RpcErrorCode::Internal, message: e.to_string() })); return; },
                        }
                    }
                    Err(e) => { let _ = tx.send(Err(e)); return; },
                }
            }
        });
        rx
    }
}

#[async_trait::async_trait]
pub trait CodeHelperServer: Send + Sync + 'static {
    /// Return the contents of a file in the project working tree.
    async fn fetch_file(&self, req: FetchFileRequest, ctx: RpcContext) -> Result<FetchFileResponse, RpcError>;
    /// Stream directory entries one at a time.
    async fn stream_directory(&self, req: StreamDirectoryRequest, ctx: RpcContext, tx: mpsc::UnboundedSender<Result<StreamDirectoryResponse, RpcError>>);
}

pub fn register_code_helper<T: RpcTransport + 'static>(
    server: &RpcServer<T>,
    handler: Arc<dyn CodeHelperServer>,
) {
    {
        let handler = handler.clone();
        server.register_unary(
            "fetchFile",
            "file.read",
            Arc::new(move |req_value, ctx| {
                let handler = handler.clone();
                Box::pin(async move {
                    let req: FetchFileRequest = serde_json::from_value(req_value)
                        .map_err(|e| RpcError { code: tf_types::rpc::RpcErrorCode::InvalidArgument, message: e.to_string() })?;
                    let resp = handler.fetch_file(req, ctx).await?;
                    serde_json::to_value(resp).map_err(|e| RpcError { code: tf_types::rpc::RpcErrorCode::Internal, message: e.to_string() })
                })
            }),
        );
    }
    {
        let handler = handler.clone();
        server.register_stream(
            "streamDirectory",
            "file.read",
            Arc::new(move |req_value, ctx, tx_value| {
                let handler = handler.clone();
                Box::pin(async move {
                    let req: StreamDirectoryRequest = match serde_json::from_value(req_value) {
                        Ok(r) => r,
                        Err(e) => { let _ = tx_value.send(Err(RpcError { code: tf_types::rpc::RpcErrorCode::InvalidArgument, message: e.to_string() })); return; },
                    };
                    let (inner_tx, mut inner_rx) = mpsc::unbounded_channel::<Result<StreamDirectoryResponse, RpcError>>();
                    let h = handler.clone();
                    tokio::spawn(async move { h.stream_directory(req, ctx, inner_tx).await; });
                    while let Some(item) = inner_rx.recv().await {
                        let mapped = item.and_then(|v| serde_json::to_value(v).map_err(|e| RpcError { code: tf_types::rpc::RpcErrorCode::Internal, message: e.to_string() }));
                        if tx_value.send(mapped).is_err() { return; }
                    }
                })
            }),
        );
    }
}
