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

use std::path::PathBuf;
use std::sync::Arc;

use amalthea::comm::server_comm::ServerStartMessage;
use amalthea::comm::server_comm::ServerStartedMessage;
use amalthea::language::server_handler::ServerHandler;
use amalthea::socket::comm::CommOutgoingTx;
use bus::BusReader;
use crossbeam::channel::Sender;
use stdext::spawn;
use tokio::runtime::Builder;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::UnboundedSender as AsyncUnboundedSender;

use super::backend;
use crate::console::ConsoleNotification;
use crate::console::KernelInfo;

pub(crate) struct Lsp {
    r_home: PathBuf,
    runtime: Arc<Runtime>,
    kernel_init_rx: BusReader<KernelInfo>,
    kernel_initialized: bool,
    console_notification_tx: AsyncUnboundedSender<ConsoleNotification>,
}

impl Lsp {
    pub(crate) fn new(
        r_home: PathBuf,
        kernel_init_rx: BusReader<KernelInfo>,
        console_notification_tx: AsyncUnboundedSender<ConsoleNotification>,
    ) -> Self {
        let rt = Builder::new_multi_thread()
            .enable_all()
            // Workers serve tower-lsp and the auxiliary loop. The main loop has
            // its own thread, and background analysis, workspace scans, and
            // source fetches have their own pools of OS threads.
            .worker_threads(2)
            .build()
            .unwrap();

        Self {
            r_home,
            runtime: Arc::new(rt),
            kernel_init_rx,
            kernel_initialized: false,
            console_notification_tx,
        }
    }
}

impl ServerHandler for Lsp {
    fn start(
        &mut self,
        server_start: ServerStartMessage,
        server_started_tx: Sender<ServerStartedMessage>,
        _comm_tx: CommOutgoingTx,
    ) -> Result<(), amalthea::error::Error> {
        // If the kernel hasn't been initialized yet, wait for it to finish.
        // This prevents the LSP from attempting to start up before the kernel
        // is ready; on subsequent starts (reconnects), the kernel will already
        // be initialized.
        if !self.kernel_initialized {
            let status = self.kernel_init_rx.recv();
            if let Err(error) = status {
                log::error!("Error waiting for kernel to initialize: {}", error);
            }
            self.kernel_initialized = true;
        }

        // Retain ownership of the tokio `runtime` inside the `Lsp` to
        // account for potential reconnects
        let runtime = self.runtime.clone();

        let r_home = self.r_home.clone();
        let console_notification_tx = self.console_notification_tx.clone();
        spawn!("ark-lsp", move || {
            backend::start_lsp(
                r_home,
                runtime,
                server_start,
                server_started_tx,
                console_notification_tx,
            )
        });
        Ok(())
    }
}
