use std::collections::HashMap;
use std::rc::Rc;
use std::sync::OnceLock;

use camino::{Utf8Path, Utf8PathBuf};
use iri_string::types::UriAbsoluteStr;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};

use crate::vscode_sys;

// Re-export Event types so it can be used by callers
pub use event::{EditorContentsChange, Event};

mod event;
mod js_function;

static EVENT_SENDER: OnceLock<UnboundedSender<Event>> = OnceLock::new();

const EVENT_BATCH_LIMIT: usize = 8;

struct Repository {
    repository: pijul_extension::FileSystemRepository,
    source_control: vscode_sys::reference::SourceControlRef,
    open_editors: HashMap<Utf8PathBuf, Rc<vscode_sys::reference::TextEditorRef>>,
    unrecorded_changes: Rc<vscode_sys::reference::SourceControlResourceGroupRef>,
    untracked_paths: Rc<vscode_sys::reference::SourceControlResourceGroupRef>,
}

struct ExtensionState {
    decoration_change_event_emitter: Rc<vscode_sys::reference::EventEmitterRef>,
    decoration_type: Rc<vscode_sys::reference::TextEditorDecorationTypeRef>,
    localization_context: Rc<l10n_embed::Context>,
    quick_diff_provider: Rc<vscode_sys::reference::QuickDiffProviderRef>,
    repositories: HashMap<Utf8PathBuf, Repository>,
}

impl ExtensionState {
    #[tracing::instrument(skip(self))]
    fn find_repository_root<'uri>(
        &self,
        uri: &'uri UriAbsoluteStr,
    ) -> Option<(&'uri Utf8Path, &'uri Utf8Path)> {
        if uri.scheme_str() != "file" {
            return None;
        }

        let uri_path = Utf8Path::new(uri.path_str());
        for ancestor in uri_path.ancestors() {
            if self.repositories.contains_key(ancestor) {
                return Some((ancestor, uri_path.strip_prefix(ancestor).unwrap()));
            }
        }

        None
    }

    #[tracing::instrument(skip(self))]
    fn get_repository<'uri>(
        &self,
        uri: &'uri UriAbsoluteStr,
    ) -> Option<(&'uri Utf8Path, &'uri Utf8Path, &Repository)> {
        self.find_repository_root(uri)
            .map(|(repository_path, relative_path)| {
                (
                    repository_path,
                    relative_path,
                    self.repositories.get(repository_path).unwrap(),
                )
            })
    }

    #[tracing::instrument(skip(self))]
    fn get_repository_mut<'uri>(
        &mut self,
        uri: &'uri UriAbsoluteStr,
    ) -> Option<(&'uri Utf8Path, &'uri Utf8Path, &mut Repository)> {
        self.find_repository_root(uri)
            .map(|(repository_path, relative_path)| {
                (
                    repository_path,
                    relative_path,
                    self.repositories.get_mut(repository_path).unwrap(),
                )
            })
    }
}

#[tracing::instrument(skip_all)]
async fn event_loop(
    decoration_change_event_emitter: vscode_sys::reference::EventEmitterRef,
    decoration_type: vscode_sys::reference::TextEditorDecorationTypeRef,
    quick_diff_provider: vscode_sys::reference::QuickDiffProviderRef,
    js_functions: js_function::Functions,
    mut sender: UnboundedSender<Event>,
    mut receiver: UnboundedReceiver<Event>,
) {
    tracing::info!("Starting event loop");

    let mut extension_state = ExtensionState {
        decoration_change_event_emitter: Rc::new(decoration_change_event_emitter),
        decoration_type: Rc::new(decoration_type),
        localization_context: Rc::new(l10n_embed::Context::new(
            icu_locale::locale!("en-US"),
            false,
        )),
        quick_diff_provider: Rc::new(quick_diff_provider),
        repositories: HashMap::new(),
    };

    let mut event_buffer = Vec::with_capacity(EVENT_BATCH_LIMIT);
    loop {
        let events_received = receiver
            .recv_many(&mut event_buffer, EVENT_BATCH_LIMIT)
            .await;
        if events_received == 0 {
            // Channel has been closed, stop event loop
            tracing::info!("Shutting down event loop");
            break;
        };

        for event in event_buffer.drain(..) {
            match event {
                Event::OpenWorkspaceFolder { workspace_uri } => {
                    event::open_workspace_folder::handle(
                        workspace_uri,
                        &mut extension_state,
                        &js_functions,
                        &mut sender,
                    )
                    .await
                }
                Event::OpenTextEditor { uri, text_editor } => {
                    event::open_text_editor::handle(
                        uri,
                        text_editor,
                        &mut extension_state,
                        &js_functions,
                    )
                    .await
                }
                Event::ChangeEditorContents { uri, changes } => {
                    event::change_editor_contents::handle(uri, changes, &mut extension_state).await
                }
                Event::MovePath { old_uri, new_uri } => {
                    event::move_path::handle(old_uri, new_uri, &mut extension_state).await
                }
                Event::ChangedFilesystemContents { uri } => {
                    event::changed_filesystem_contents::handle(
                        uri,
                        &mut extension_state,
                        &js_functions,
                    )
                    .await
                }
                Event::RequestInlineCredit { uri } => {
                    event::request_inline_credit::handle(uri, &extension_state, &js_functions).await
                }
                Event::RequestTrackedContents {
                    uri,
                    deferred_promise,
                } => {
                    event::request_tracked_contents::handle(uri, deferred_promise, &extension_state)
                        .await
                }
                Event::RequestFileDecoration {
                    uri,
                    deferred_promise,
                } => {
                    event::request_file_decoration::handle(uri, deferred_promise, &extension_state)
                        .await
                }
                Event::UpdateResourceStates { repository_path } => {
                    event::update_resource_states::handle(
                        repository_path,
                        &extension_state,
                        &js_functions,
                    )
                    .await
                }
            }
        }

        event_buffer.clear();
    }
}

#[tracing::instrument(skip_all)]
pub fn start(
    env: &napi::Env,
    vscode_object: &napi::bindgen_prelude::Object,
    decoration_change_event_emitter: vscode_sys::EventEmitter,
    quick_diff_provider: vscode_sys::QuickDiffProvider,
) -> Result<(), napi::Error> {
    let decoration_change_event_emitter_ref = decoration_change_event_emitter.create_ref()?;
    let decoration_type = crate::inline_credit::create_decoration_type(env)?.create_ref()?;
    let quick_diff_provider_ref = quick_diff_provider.create_ref()?;
    let js_functions = js_function::Functions::get(env, vscode_object)?;

    let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
    if let Err(_existing_value) = EVENT_SENDER.set(sender.clone()) {
        return Err(napi::Error::from_reason(
            "Event sender has already been initialized",
        ));
    }

    let runtime = tokio::runtime::Runtime::new().map_err(|error| {
        napi::Error::from_reason(format!("Failed to create Tokio runtime: {error}"))
    })?;
    std::thread::spawn(move || {
        runtime.block_on(event_loop(
            decoration_change_event_emitter_ref,
            decoration_type,
            quick_diff_provider_ref,
            js_functions,
            sender,
            receiver,
        ))
    });

    Ok(())
}

pub fn send(event: Event) {
    EVENT_SENDER
        .get()
        .expect("EVENT_SENDER should be set")
        .send(event)
        .expect("Receiver should be open");
}