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

use camino::Utf8PathBuf;
use iri_string::types::UriAbsoluteString;
use tokio::sync::mpsc::UnboundedSender;

use crate::event_loop::js_function::Functions;
use crate::event_loop::{Event, ExtensionState, Repository};

#[tracing::instrument(skip(extension_state, js_functions, sender))]
pub async fn handle(
    workspace_uri: UriAbsoluteString,
    extension_state: &mut ExtensionState,
    js_functions: &Functions,
    sender: &mut UnboundedSender<Event>,
) {
    // TODO: handle different schemes using `vscode.workspace.fs`
    if workspace_uri.scheme_str() != "file" {
        tracing::info!(message = "Skipping unhandled URI scheme", ?workspace_uri);
        return;
    }

    let workspace_path = Utf8PathBuf::from(workspace_uri.path_str());
    let ignore_root = workspace_path.clone();

    let (dot_directory_sender, mut dot_directory_receiver) = tokio::sync::mpsc::unbounded_channel();
    // TODO: better separate sync vs non-sync code
    std::thread::spawn(move || {
        let walker = ignore::WalkBuilder::new(&ignore_root)
            .current_dir(&ignore_root)
            .build_parallel();

        walker.run(move || {
            let path_sender = dot_directory_sender.clone();

            Box::new(move |entry_result| {
                // TODO: handle errors
                if let Ok(entry) = entry_result
                    && let Some(file_type) = entry.file_type()
                    && file_type.is_dir()
                {
                    // Path: {ENTRY}/.pijul/
                    let dot_directory_path = entry.path().join(libpijul::DOT_DIR);

                    if dot_directory_path.is_dir() {
                        path_sender.send(entry.into_path()).unwrap();
                        // Don't traverse repositories
                        return ignore::WalkState::Skip;
                    }
                }

                ignore::WalkState::Continue
            })
        });
    });

    // TODO: debug assertion that no discovered repositories are nested

    while let Some(os_repository_path) = dot_directory_receiver.recv().await {
        let repository_path = match Utf8PathBuf::from_path_buf(os_repository_path.to_path_buf()) {
            Ok(repository_path) => repository_path,
            Err(utf8_path_error) => {
                tracing::error!(
                    message = "Failed to convert OS path to UTF-8 path",
                    ?os_repository_path,
                    ?utf8_path_error
                );
                continue;
            }
        };

        let std::collections::hash_map::Entry::Vacant(repository_entry) =
            extension_state.repositories.entry(repository_path.clone())
        else {
            tracing::warn!(message = "Ignoring existing repository");
            continue;
        };

        let repository_uri = match js_functions.uri_file(repository_path.to_string()).await {
            Ok(repository_uri) => repository_uri,
            Err(error) => {
                tracing::error!(message = "Failed to parse URI", ?repository_path, ?error);
                continue;
            }
        };

        let (source_control, unrecorded_changes, untracked_paths) = match js_functions
            .initialize_source_control(
                repository_uri,
                &extension_state.quick_diff_provider,
                String::from("Pijul"),
                String::from("Changes"),
                String::from("Untracked"),
            )
            .await
        {
            Ok(initialized_source_control) => initialized_source_control,
            Err(error) => {
                tracing::error!(message = "Unable to create source control", ?error);
                continue;
            }
        };

        let file_system_repository =
            match pijul_extension::FileSystemRepository::new(&repository_path) {
                Ok(repository) => repository,
                Err(error) => {
                    tracing::error!(message = "Failed to open repository", ?error);
                    continue;
                }
            };

        repository_entry.insert(Repository {
            repository: file_system_repository,
            source_control,
            open_editors: HashMap::new(),
            unrecorded_changes: Rc::new(unrecorded_changes),
            untracked_paths: Rc::new(untracked_paths),
        });

        tracing::info!(
            message = "Opened repository",
            ?repository_path,
            ?workspace_path
        );

        sender
            .send(Event::UpdateResourceStates { repository_path })
            .expect("Receiver should be open");
    }
}