use std::{ops::Not, path::{Path, PathBuf}};

mod git;


pub(crate) fn construct_and_validate_path(
    repo_dir: &Path,
    base_path: &Path,
    relative_path: Option<(PathBuf, proc_macro2::Span)>,
) -> Result<PathBuf, syn::Error> {
    let base_path = repo_dir.join(base_path);

    match relative_path {
        Some((relative_path, span)) => {
            let path = resolve_path(&base_path, &relative_path)
                .map_err(|message| syn::Error::new(span, message))?;

            check_path_in_repo(&path, repo_dir)
                .map_err(|message| syn::Error::new(span, message))?;

            Ok(path)
        }
        None => Ok(base_path),
    }
}

fn resolve_path(base_path: &Path, relative_path: &Path) -> Result<PathBuf, Error> {
    if relative_path.is_absolute() {
        return Err(Error::from("Path may not be absolute."));
    }

    let path = base_path.join(relative_path);

    if path.exists().not() {
        return Err(format!("Path does not exist: {}", path.display()));
    }

    dunce::canonicalize(&path)
        .map_err(|cause| format!("Path '{}' could not be canonicalized: {}", path.display(), cause))
}

fn check_path_in_repo(path: &Path, repo_dir: &Path) -> Result<(), Error> {
    if path.starts_with(repo_dir).not() {
        return Err(format!("Constructed path would be outside of the repository: {}", path.display()));
    }

    #[cfg(feature="gitignore")]
    {
        let repo = git::Repository::open(repo_dir)
            .map_err(|cause| format!("Failed to open repository at {repo_dir:?} for checking gitignore. This is likely a bug.\n  {cause}"))?;

        let is_ignored = repo.is_path_ignored(path)
            .map_err(|cause| format!("Failed to determine whether {path:?} is in gitignore:\n  {cause}"))?;

        if is_ignored {
            return Err(format!(
r#"Constructed path is matched by a gitignore file:
{}

Ignored files cannot be specified in `repo_path!()`, because
this would prevent the compilation in a freshly cloned repository.

Instead, you can specify the ignored path via `.join()`,
like this: `repo_path!().join("target/debug/")`

If you would like to disable this check entirely,
disable the `gitignore` feature of the `repo_path` crate.
"#,
                path.display()
            ));
        }
    }

    Ok(())
}

pub type Error = String;
