use std::{collections::HashSet, path::PathBuf, str::FromStr};

use itertools::Itertools;
use lux_lib::{
    config::Config, git::shorthand::RemoteGitUrlShorthand, lua_version::LuaVersion,
    operations::Sync, package::PackageReq, tree::Tree, workspace::Workspace,
};
use miette::{Context, Result};
use walkdir::WalkDir;

pub fn top_level_ignored_files(project: &Workspace) -> Vec<PathBuf> {
    let top_level_project_files = ignore::WalkBuilder::new(project.root())
        .max_depth(Some(1))
        .build()
        .filter_map(Result::ok)
        .filter_map(|entry| {
            let file = entry.into_path();
            if file.is_dir() || file.extension().is_some_and(|ext| ext == "lua") {
                Some(file)
            } else {
                None
            }
        })
        .collect::<HashSet<_>>();

    let top_level_files = WalkDir::new(project.root())
        .max_depth(1)
        .into_iter()
        .filter_map(Result::ok)
        .filter_map(|entry| {
            let file = entry.into_path();
            if file.is_dir() || file.extension().is_some_and(|ext| ext == "lua") {
                Some(file)
            } else {
                None
            }
        })
        .collect::<HashSet<_>>();

    top_level_files
        .difference(&top_level_project_files)
        .cloned()
        .collect_vec()
}

/// Used for parsing alternatives between a git URL shorthand and a package requirement.
// The `FromStr` instance tries to parse a git URL shorthand first (expecting a git host prefix),
// and then a package requirement.
#[derive(Debug, Clone)]
pub enum PackageReqOrGitShorthand {
    PackageReq(PackageReq),
    GitShorthand(RemoteGitUrlShorthand),
}

impl FromStr for PackageReqOrGitShorthand {
    type Err = miette::Report;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match RemoteGitUrlShorthand::parse_with_prefix(s) {
            Ok(shorthand) => Ok(Self::GitShorthand(shorthand)),
            Err(_) => Ok(Self::PackageReq(PackageReq::parse(s)?)),
        }
    }
}

/// Get the current workspaces tree, or fall back to
/// the user tree if not in a project
pub fn current_workspace_or_user_tree(config: &Config) -> Result<Tree> {
    let workspace = Workspace::current()?;
    Ok(match &workspace {
        Some(workspace) => workspace.tree(config)?,
        None => {
            let lua_version = LuaVersion::from(config)?.clone();
            config.user_tree(lua_version)?
        }
    })
}

pub async fn sync_dependencies_if_locked(workspace: &Workspace, config: &Config) -> Result<()> {
    // NOTE: We only update the lockfile if one exists.
    // Otherwise, the next `lx build` will remove the packages.
    Sync::new(workspace, config)
        .sync_dependencies()
        .await
        .wrap_err("syncing dependencies with the project lockfile failed.")?;
    Ok(())
}

pub async fn sync_build_dependencies_if_locked(
    workspace: &Workspace,
    config: &Config,
) -> Result<()> {
    Sync::new(workspace, config)
        .sync_build_dependencies()
        .await
        .wrap_err("syncing build dependencies with the project lockfile failed.")?;
    Ok(())
}

pub async fn sync_test_dependencies_if_locked(
    workspace: &Workspace,
    config: &Config,
) -> Result<()> {
    Sync::new(workspace, config)
        .sync_test_dependencies()
        .await
        .wrap_err("syncing test dependencies with the project lockfile failed.")?;
    Ok(())
}

pub fn exists_matching_workspace_member(package_req: &PackageReq) -> Result<bool> {
    let workspace = Workspace::current()?;
    Ok(workspace.is_some_and(|ws| {
        ws.select_member(package_req.name()).is_ok_and(|project| {
            project
                .toml()
                .version()
                .is_ok_and(|version| package_req.version_req().matches(&version))
        })
    }))
}
