use std::sync::mpsc;

use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::parse::Parsable;

use lsp_types::request::{
    CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
    CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
    DocumentHighlightRequest, DocumentLinkRequest, DocumentSymbolRequest, ExecuteCommand,
    FoldingRangeRequest, GotoDefinition, GotoImplementation, GotoImplementationParams,
    GotoTypeDefinition, GotoTypeDefinitionParams, HoverRequest, InlayHintRequest,
    InlayHintResolveRequest, References, ResolveCompletionItem, SelectionRangeRequest,
    SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol,
};
use lsp_types::{
    CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
    CodeAction, CodeActionParams, CodeLensParams, CompletionItem, CompletionParams,
    DocumentHighlightParams, DocumentLinkParams, DocumentSymbolParams, ExecuteCommandParams,
    FoldingRangeParams, GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams,
    ReferenceParams, RenameFilesParams, SelectionRangeParams, SemanticTokensParams,
    SignatureHelpParams, WorkspaceSymbolParams,
};

use crate::server::Server;

#[derive(Debug, Clone)]
pub enum WorkerMessage<P> {
    Request(i64, P),
    Kill,
}

impl<P> From<(i64, P)> for WorkerMessage<P> {
    fn from((id, params): (i64, P)) -> Self {
        Self::Request(id, params)
    }
}

#[derive(Debug, Clone)]
pub struct SendChannels {
    completion: mpsc::Sender<WorkerMessage<CompletionParams>>,
    resolve_completion: mpsc::Sender<WorkerMessage<CompletionItem>>,
    goto_definition: mpsc::Sender<WorkerMessage<GotoDefinitionParams>>,
    goto_type_definition: mpsc::Sender<WorkerMessage<GotoTypeDefinitionParams>>,
    goto_implementation: mpsc::Sender<WorkerMessage<GotoImplementationParams>>,
    semantic_tokens_full: mpsc::Sender<WorkerMessage<SemanticTokensParams>>,
    inlay_hint: mpsc::Sender<WorkerMessage<InlayHintParams>>,
    inlay_hint_resolve: mpsc::Sender<WorkerMessage<InlayHint>>,
    hover: mpsc::Sender<WorkerMessage<HoverParams>>,
    references: mpsc::Sender<WorkerMessage<ReferenceParams>>,
    code_lens: mpsc::Sender<WorkerMessage<CodeLensParams>>,
    code_action: mpsc::Sender<WorkerMessage<CodeActionParams>>,
    code_action_resolve: mpsc::Sender<WorkerMessage<CodeAction>>,
    signature_help: mpsc::Sender<WorkerMessage<SignatureHelpParams>>,
    will_rename_files: mpsc::Sender<WorkerMessage<RenameFilesParams>>,
    execute_command: mpsc::Sender<WorkerMessage<ExecuteCommandParams>>,
    workspace_symbol: mpsc::Sender<WorkerMessage<WorkspaceSymbolParams>>,
    document_symbol: mpsc::Sender<WorkerMessage<DocumentSymbolParams>>,
    call_hierarchy_prepare: mpsc::Sender<WorkerMessage<CallHierarchyPrepareParams>>,
    call_hierarchy_incoming: mpsc::Sender<WorkerMessage<CallHierarchyIncomingCallsParams>>,
    call_hierarchy_outgoing: mpsc::Sender<WorkerMessage<CallHierarchyOutgoingCallsParams>>,
    folding_range: mpsc::Sender<WorkerMessage<FoldingRangeParams>>,
    selection_range: mpsc::Sender<WorkerMessage<SelectionRangeParams>>,
    document_highlight: mpsc::Sender<WorkerMessage<DocumentHighlightParams>>,
    document_link: mpsc::Sender<WorkerMessage<DocumentLinkParams>>,
    pub(crate) health_check: mpsc::Sender<WorkerMessage<()>>,
}

impl SendChannels {
    pub fn new() -> (Self, ReceiveChannels) {
        let (tx_completion, rx_completion) = mpsc::channel();
        let (tx_resolve_completion, rx_resolve_completion) = mpsc::channel();
        let (tx_goto_definition, rx_goto_definition) = mpsc::channel();
        let (tx_goto_type_definition, rx_goto_type_definition) = mpsc::channel();
        let (tx_goto_implementation, rx_goto_implementation) = mpsc::channel();
        let (tx_semantic_tokens_full, rx_semantic_tokens_full) = mpsc::channel();
        let (tx_inlay_hint, rx_inlay_hint) = mpsc::channel();
        let (tx_inlay_hint_resolve, rx_inlay_hint_resolve) = mpsc::channel();
        let (tx_hover, rx_hover) = mpsc::channel();
        let (tx_references, rx_references) = mpsc::channel();
        let (tx_code_lens, rx_code_lens) = mpsc::channel();
        let (tx_code_action, rx_code_action) = mpsc::channel();
        let (tx_code_action_resolve, rx_code_action_resolve) = mpsc::channel();
        let (tx_sig_help, rx_sig_help) = mpsc::channel();
        let (tx_will_rename_files, rx_will_rename_files) = mpsc::channel();
        let (tx_execute_command, rx_execute_command) = mpsc::channel();
        let (tx_workspace_symbol, rx_workspace_symbol) = mpsc::channel();
        let (tx_document_symbol, rx_document_symbol) = mpsc::channel();
        let (tx_call_hierarchy_prepare, rx_call_hierarchy_prepare) = mpsc::channel();
        let (tx_call_hierarchy_incoming, rx_call_hierarchy_incoming) = mpsc::channel();
        let (tx_call_hierarchy_outgoing, rx_call_hierarchy_outgoing) = mpsc::channel();
        let (tx_folding_range, rx_folding_range) = mpsc::channel();
        let (tx_selection_range, rx_selection_range) = mpsc::channel();
        let (tx_document_highlight, rx_document_highlight) = mpsc::channel();
        let (tx_document_link, rx_document_link) = mpsc::channel();
        let (tx_health_check, rx_health_check) = mpsc::channel();
        (
            Self {
                completion: tx_completion,
                resolve_completion: tx_resolve_completion,
                goto_definition: tx_goto_definition,
                goto_type_definition: tx_goto_type_definition,
                goto_implementation: tx_goto_implementation,
                semantic_tokens_full: tx_semantic_tokens_full,
                inlay_hint: tx_inlay_hint,
                inlay_hint_resolve: tx_inlay_hint_resolve,
                hover: tx_hover,
                references: tx_references,
                code_lens: tx_code_lens,
                code_action: tx_code_action,
                code_action_resolve: tx_code_action_resolve,
                signature_help: tx_sig_help,
                will_rename_files: tx_will_rename_files,
                execute_command: tx_execute_command,
                workspace_symbol: tx_workspace_symbol,
                document_symbol: tx_document_symbol,
                call_hierarchy_prepare: tx_call_hierarchy_prepare,
                call_hierarchy_incoming: tx_call_hierarchy_incoming,
                call_hierarchy_outgoing: tx_call_hierarchy_outgoing,
                folding_range: tx_folding_range,
                selection_range: tx_selection_range,
                document_highlight: tx_document_highlight,
                document_link: tx_document_link,
                health_check: tx_health_check,
            },
            ReceiveChannels {
                completion: rx_completion,
                resolve_completion: rx_resolve_completion,
                goto_definition: rx_goto_definition,
                goto_type_definition: rx_goto_type_definition,
                goto_implementation: rx_goto_implementation,
                semantic_tokens_full: rx_semantic_tokens_full,
                inlay_hint: rx_inlay_hint,
                inlay_hint_resolve: rx_inlay_hint_resolve,
                hover: rx_hover,
                references: rx_references,
                code_lens: rx_code_lens,
                code_action: rx_code_action,
                code_action_resolve: rx_code_action_resolve,
                signature_help: rx_sig_help,
                will_rename_files: rx_will_rename_files,
                execute_command: rx_execute_command,
                workspace_symbol: rx_workspace_symbol,
                document_symbol: rx_document_symbol,
                call_hierarchy_prepare: rx_call_hierarchy_prepare,
                call_hierarchy_incoming: rx_call_hierarchy_incoming,
                call_hierarchy_outgoing: rx_call_hierarchy_outgoing,
                folding_range: rx_folding_range,
                selection_range: rx_selection_range,
                document_highlight: rx_document_highlight,
                document_link: rx_document_link,
                health_check: rx_health_check,
            },
        )
    }

    pub(crate) fn close(&self) {
        let _ = self.completion.send(WorkerMessage::Kill);
        let _ = self.resolve_completion.send(WorkerMessage::Kill);
        let _ = self.goto_definition.send(WorkerMessage::Kill);
        let _ = self.goto_type_definition.send(WorkerMessage::Kill);
        let _ = self.goto_implementation.send(WorkerMessage::Kill);
        let _ = self.semantic_tokens_full.send(WorkerMessage::Kill);
        let _ = self.inlay_hint.send(WorkerMessage::Kill);
        let _ = self.inlay_hint_resolve.send(WorkerMessage::Kill);
        let _ = self.hover.send(WorkerMessage::Kill);
        let _ = self.references.send(WorkerMessage::Kill);
        let _ = self.code_lens.send(WorkerMessage::Kill);
        let _ = self.code_action.send(WorkerMessage::Kill);
        let _ = self.code_action_resolve.send(WorkerMessage::Kill);
        let _ = self.signature_help.send(WorkerMessage::Kill);
        let _ = self.will_rename_files.send(WorkerMessage::Kill);
        let _ = self.execute_command.send(WorkerMessage::Kill);
        let _ = self.workspace_symbol.send(WorkerMessage::Kill);
        let _ = self.document_symbol.send(WorkerMessage::Kill);
        let _ = self.call_hierarchy_prepare.send(WorkerMessage::Kill);
        let _ = self.call_hierarchy_incoming.send(WorkerMessage::Kill);
        let _ = self.call_hierarchy_outgoing.send(WorkerMessage::Kill);
        let _ = self.folding_range.send(WorkerMessage::Kill);
        let _ = self.selection_range.send(WorkerMessage::Kill);
        let _ = self.document_highlight.send(WorkerMessage::Kill);
        let _ = self.health_check.send(WorkerMessage::Kill);
    }
}

#[derive(Debug)]
pub struct ReceiveChannels {
    pub(crate) completion: mpsc::Receiver<WorkerMessage<CompletionParams>>,
    pub(crate) resolve_completion: mpsc::Receiver<WorkerMessage<CompletionItem>>,
    pub(crate) goto_definition: mpsc::Receiver<WorkerMessage<GotoDefinitionParams>>,
    pub(crate) goto_type_definition: mpsc::Receiver<WorkerMessage<GotoTypeDefinitionParams>>,
    pub(crate) goto_implementation: mpsc::Receiver<WorkerMessage<GotoImplementationParams>>,
    pub(crate) semantic_tokens_full: mpsc::Receiver<WorkerMessage<SemanticTokensParams>>,
    pub(crate) inlay_hint: mpsc::Receiver<WorkerMessage<InlayHintParams>>,
    pub(crate) inlay_hint_resolve: mpsc::Receiver<WorkerMessage<InlayHint>>,
    pub(crate) hover: mpsc::Receiver<WorkerMessage<HoverParams>>,
    pub(crate) references: mpsc::Receiver<WorkerMessage<ReferenceParams>>,
    pub(crate) code_lens: mpsc::Receiver<WorkerMessage<CodeLensParams>>,
    pub(crate) code_action: mpsc::Receiver<WorkerMessage<CodeActionParams>>,
    pub(crate) code_action_resolve: mpsc::Receiver<WorkerMessage<CodeAction>>,
    pub(crate) signature_help: mpsc::Receiver<WorkerMessage<SignatureHelpParams>>,
    pub(crate) will_rename_files: mpsc::Receiver<WorkerMessage<RenameFilesParams>>,
    pub(crate) execute_command: mpsc::Receiver<WorkerMessage<ExecuteCommandParams>>,
    pub(crate) workspace_symbol: mpsc::Receiver<WorkerMessage<WorkspaceSymbolParams>>,
    pub(crate) document_symbol: mpsc::Receiver<WorkerMessage<DocumentSymbolParams>>,
    pub(crate) call_hierarchy_prepare: mpsc::Receiver<WorkerMessage<CallHierarchyPrepareParams>>,
    pub(crate) call_hierarchy_incoming:
        mpsc::Receiver<WorkerMessage<CallHierarchyIncomingCallsParams>>,
    pub(crate) call_hierarchy_outgoing:
        mpsc::Receiver<WorkerMessage<CallHierarchyOutgoingCallsParams>>,
    pub(crate) folding_range: mpsc::Receiver<WorkerMessage<FoldingRangeParams>>,
    pub(crate) selection_range: mpsc::Receiver<WorkerMessage<SelectionRangeParams>>,
    pub(crate) document_highlight: mpsc::Receiver<WorkerMessage<DocumentHighlightParams>>,
    pub(crate) document_link: mpsc::Receiver<WorkerMessage<DocumentLinkParams>>,
    pub(crate) health_check: mpsc::Receiver<WorkerMessage<()>>,
}

pub trait Sendable<R: lsp_types::request::Request + 'static> {
    fn send(
        &self,
        id: i64,
        params: R::Params,
    ) -> Result<(), mpsc::SendError<WorkerMessage<R::Params>>>;
}

macro_rules! impl_sendable {
    ($Request: ident, $Params: ident, $receiver: ident) => {
        impl<Checker: BuildRunnable, Parser: Parsable> Sendable<$Request>
            for Server<Checker, Parser>
        {
            fn send(
                &self,
                id: i64,
                params: $Params,
            ) -> Result<(), mpsc::SendError<WorkerMessage<$Params>>> {
                self.channels
                    .as_ref()
                    .ok_or_else(|| {
                        erg_common::lsp_log!("channels are closed");
                        mpsc::SendError(WorkerMessage::Kill)
                    })?
                    .$receiver
                    .send($crate::channels::WorkerMessage::Request(id, params))
            }
        }
    };
}

impl_sendable!(Completion, CompletionParams, completion);
impl_sendable!(ResolveCompletionItem, CompletionItem, resolve_completion);
impl_sendable!(GotoDefinition, GotoDefinitionParams, goto_definition);
impl_sendable!(
    GotoTypeDefinition,
    GotoTypeDefinitionParams,
    goto_type_definition
);
impl_sendable!(
    GotoImplementation,
    GotoImplementationParams,
    goto_implementation
);
impl_sendable!(
    SemanticTokensFullRequest,
    SemanticTokensParams,
    semantic_tokens_full
);
impl_sendable!(InlayHintRequest, InlayHintParams, inlay_hint);
impl_sendable!(InlayHintResolveRequest, InlayHint, inlay_hint_resolve);
impl_sendable!(HoverRequest, HoverParams, hover);
impl_sendable!(References, ReferenceParams, references);
impl_sendable!(CodeLensRequest, CodeLensParams, code_lens);
impl_sendable!(CodeActionRequest, CodeActionParams, code_action);
impl_sendable!(CodeActionResolveRequest, CodeAction, code_action_resolve);
impl_sendable!(SignatureHelpRequest, SignatureHelpParams, signature_help);
impl_sendable!(WillRenameFiles, RenameFilesParams, will_rename_files);
impl_sendable!(ExecuteCommand, ExecuteCommandParams, execute_command);
impl_sendable!(WorkspaceSymbol, WorkspaceSymbolParams, workspace_symbol);
impl_sendable!(DocumentSymbolRequest, DocumentSymbolParams, document_symbol);
impl_sendable!(
    CallHierarchyPrepare,
    CallHierarchyPrepareParams,
    call_hierarchy_prepare
);
impl_sendable!(
    CallHierarchyIncomingCalls,
    CallHierarchyIncomingCallsParams,
    call_hierarchy_incoming
);
impl_sendable!(
    CallHierarchyOutgoingCalls,
    CallHierarchyOutgoingCallsParams,
    call_hierarchy_outgoing
);
impl_sendable!(FoldingRangeRequest, FoldingRangeParams, folding_range);
impl_sendable!(SelectionRangeRequest, SelectionRangeParams, selection_range);
impl_sendable!(
    DocumentHighlightRequest,
    DocumentHighlightParams,
    document_highlight
);
impl_sendable!(DocumentLinkRequest, DocumentLinkParams, document_link);
