//! Crate path resolution for generated code.
//!
//! Macro expansions are compiled in the downstream crate, so generated absolute
//! paths must point at crates the downstream user can actually name. Prefer the
//! facade crate when present, then fall back to leaf crates for direct users.

use proc_macro_crate::{FoundCrate, crate_name};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::LitStr;

const FACADE_PACKAGE: &str = "potential-expectations";
const FACADE_CRATE: &str = "potential_expectations";

#[derive(Clone)]
pub(crate) struct MacroPaths {
    pub core: TokenStream,
    #[cfg_attr(not(feature = "node-dsl"), allow(dead_code))]
    pub graph: TokenStream,
    pub serde: TokenStream,
    pub serde_crate: LitStr,
}

impl MacroPaths {
    pub(crate) fn new() -> Self {
        if let Some(facade) = resolve_facade() {
            let facade_path = facade.tokens;
            let facade_path_string = facade.path_string;
            return Self {
                core: quote! { #facade_path::__private::pe_core },
                graph: quote! { #facade_path::__private::pe_graph },
                serde: quote! { #facade_path::__private::serde },
                serde_crate: LitStr::new(
                    &format!("{facade_path_string}::__private::serde"),
                    Span::call_site(),
                ),
            };
        }

        let core = resolve_crate("pe-core", "pe_core", ItselfMode::Crate)
            .unwrap_or_else(|| CratePath::absolute("pe_core"));
        let graph = resolve_crate("pe-graph", "pe_graph", ItselfMode::Crate)
            .unwrap_or_else(|| CratePath::absolute("pe_graph"));
        let serde = resolve_crate("serde", "serde", ItselfMode::Crate)
            .unwrap_or_else(|| CratePath::absolute("serde"));

        Self {
            core: core.tokens,
            graph: graph.tokens,
            serde: serde.tokens,
            serde_crate: LitStr::new(&serde.path_string, Span::call_site()),
        }
    }
}

#[derive(Clone, Copy)]
enum ItselfMode {
    Crate,
    NamedSelf,
}

struct CratePath {
    tokens: TokenStream,
    path_string: String,
}

impl CratePath {
    fn absolute(crate_name: &str) -> Self {
        let ident = Ident::new(crate_name, Span::call_site());
        Self {
            tokens: quote! { ::#ident },
            path_string: format!("::{crate_name}"),
        }
    }
}

fn resolve_facade() -> Option<CratePath> {
    resolve_crate(FACADE_PACKAGE, FACADE_CRATE, ItselfMode::NamedSelf)
}

fn resolve_crate(package: &str, default_crate: &str, itself_mode: ItselfMode) -> Option<CratePath> {
    match crate_name(package).ok()? {
        FoundCrate::Itself => match itself_mode {
            ItselfMode::Crate => Some(CratePath {
                tokens: quote! { crate },
                path_string: "crate".to_string(),
            }),
            ItselfMode::NamedSelf => Some(CratePath::absolute(default_crate)),
        },
        FoundCrate::Name(name) => Some(CratePath::absolute(&name)),
    }
}
