use std::collections::HashMap;

use camino::Utf8PathBuf;

use crate::repository::open_repository::OpenRepository;
use crate::vscode_sys;

mod open_repository;

#[derive(Default)]
pub struct OpenRepositories {
    // TODO: proper URIs
    // TODO: multiple repositories per workspace folder
    repositories: HashMap<Utf8PathBuf, OpenRepository>,
}

impl OpenRepositories {
    #[tracing::instrument(skip_all)]
    pub fn new() -> Self {
        Self::default()
    }

    #[tracing::instrument(skip_all)]
    pub fn get_open_repository(
        &self,
        env: &napi::Env,
        uri: &vscode_sys::Uri,
    ) -> Result<Option<(Utf8PathBuf, &OpenRepository)>, napi::Error> {
        let Some(workspace_folder) = vscode_sys::workspace::get_workspace_folder(env, uri)? else {
            tracing::debug!(
                message = "URI does not correspond to a workspace folder",
                uri = ?uri.to_string()
            );

            return Ok(None);
        };

        let workspace_uri = workspace_folder.get_uri()?;
        let workspace_path = Utf8PathBuf::from(workspace_uri.get_fs_path()?);
        Ok(self
            .repositories
            .get(&workspace_path)
            .map(|open_repository| (workspace_path, open_repository)))
    }

    #[tracing::instrument(skip_all)]
    pub fn get_open_repository_mut(
        &mut self,
        env: &napi::Env,
        uri: &vscode_sys::Uri,
    ) -> Result<Option<(Utf8PathBuf, &mut OpenRepository)>, napi::Error> {
        let Some(workspace_folder) = vscode_sys::workspace::get_workspace_folder(env, uri)? else {
            tracing::debug!(
                message = "URI does not correspond to a workspace folder",
                uri = ?uri.to_string()
            );

            return Ok(None);
        };

        let workspace_uri = workspace_folder.get_uri()?;
        let workspace_path = Utf8PathBuf::from(workspace_uri.get_fs_path()?);
        Ok(self
            .repositories
            .get_mut(&workspace_path)
            .map(|open_repository| (workspace_path, open_repository)))
    }

    pub fn register_text_editors(
        &mut self,
        env: &napi::Env,
        text_editors: Vec<vscode_sys::TextEditor>,
    ) -> Result<(), napi::Error> {
        for text_editor in text_editors {
            let document = text_editor.get_document()?;
            let uri = document.get_uri()?;

            if let Some((repository_path, open_repository)) =
                self.get_open_repository_mut(env, &uri)?
            {
                let absolute_editor_path = Utf8PathBuf::from(uri.get_fs_path()?);
                let relative_editor_path = absolute_editor_path
                    .strip_prefix(&repository_path)
                    // TODO: error handling
                    .unwrap()
                    .to_path_buf();

                open_repository.register_text_editor(relative_editor_path, text_editor)?;

                tracing::debug!(
                    message = "Registered text editor",
                    ?repository_path,
                    uri = uri.to_string()?
                );
            } else {
                // Ignore documents not part of any workspace folder
                tracing::debug!(
                    message = "Ignoring text editor without valid workspace",
                    uri = uri.to_string()?
                );
            }
        }

        Ok(())
    }

    #[tracing::instrument(skip_all)]
    pub fn open_workspace_folder(
        &mut self,
        env: &napi::Env,
        workspace_folder: vscode_sys::WorkspaceFolder,
    ) -> Result<(), napi::Error> {
        let repository_uri = workspace_folder.get_uri()?;
        let repository_path = Utf8PathBuf::from(repository_uri.get_fs_path()?);
        let open_repository = OpenRepository::new(env, &repository_uri)?;

        if self
            .repositories
            .try_insert(repository_path.clone(), open_repository)
            .is_err()
        {
            tracing::error!(
                message = "Failed to insert duplicate repository",
                ?repository_path
            );
        }

        Ok(())
    }

    #[tracing::instrument(skip_all)]
    pub fn close_workspace_folder(
        &mut self,
        workspace_folder: vscode_sys::WorkspaceFolder,
    ) -> Result<(), napi::Error> {
        let repository_uri = workspace_folder.get_uri()?;
        let repository_path = Utf8PathBuf::from(repository_uri.get_fs_path()?);

        if self.repositories.remove(&repository_path).is_none() {
            tracing::error!(
                message = "Failed to remove repository that does not exist",
                ?repository_path
            );
        }

        Ok(())
    }

    // TODO: remove this
    #[tracing::instrument(skip_all)]
    pub fn iter_repositories(&self) -> impl Iterator<Item = (&Utf8PathBuf, &OpenRepository)> {
        self.repositories.iter()
    }
}