use proc_macro::TokenStream;
use quote::quote;
use std::path::PathBuf;
use syn::{LitStr, parse_macro_input};

/// A macro for parsing Sass/SCSS files at compile time.
///
/// This macro takes a file path to a Sass/SCSS file, compiles it to CSS at compile time
/// using the `grass` compiler, and returns the resulting CSS as a `&'static str`.
///
/// # Behavior
///
/// Similar to `include_str!`, this macro:
/// - Reads the specified file at compile time
/// - Compiles the Sass/SCSS to CSS using `grass::from_path()` with default options
/// - Handles `@import` and `@use` directives, resolving imported files relative to the main file
/// - Embeds the resulting CSS string in the binary
/// - Returns a `&'static str` containing the compiled CSS
///
/// # Syntax
///
/// ```notrust
/// sass!("path/to/file.scss")
/// ```
///
/// # Arguments
///
/// - A string literal containing the path to the Sass/SCSS file (relative to the crate root)
///
/// # Example
///
/// ```notrust
/// // Relative to crate root: looks for src/routes/css/main.scss
/// const MY_STYLES: &str = sass!("src/routes/css/main.scss");
///
/// // Use in HTML generation
/// html! {
///     style { (PreEscaped(MY_STYLES)) }
/// }
/// ```
///
/// # Import Support
///
/// The macro fully supports Sass imports and uses:
/// ```notrust
/// // main.scss
/// @import "variables";
/// @use "mixins";
///
/// .button {
///     color: $primary-color;
/// }
/// ```
///
/// All imported files are resolved relative to the location of the main Sass file.
///
/// # Compile-time vs Runtime
///
/// Instead of this runtime code:
/// ```notrust
/// let css = grass::from_path(
///     "styles.scss",
///     &grass::Options::default()
/// ).unwrap();
/// ```
///
/// You can use:
/// ```notrust
/// const CSS: &str = sass!("styles.scss");
/// ```
///
/// # Panics
///
/// This macro will cause a compile error if:
/// - The specified file does not exist
/// - The file path is invalid
/// - The Sass/SCSS file contains syntax errors
/// - Any imported files cannot be found
/// - The grass compiler fails for any reason
///
/// # Note
///
/// The file path is relative to the crate root (where `Cargo.toml` is located), determined
/// by the `CARGO_MANIFEST_DIR` environment variable. This is similar to how `include!()` works
/// but differs from `include_str!()` which is relative to the current file.
#[proc_macro]
pub fn sass(input: TokenStream) -> TokenStream {
	let input_lit = parse_macro_input!(input as LitStr);
	let file_path = PathBuf::from(input_lit.value());

	// Not stable yet, we have to use crate-relative paths :(
	//let span = proc_macro::Span::call_site();
	//let source_file = span.source_file();
	//let path: PathBuf = source_file.path();

	// Use a combination of include_str! and grass compilation
	// include_str! handles the relative path resolution for us
	// We generate code that uses include_str! at the user's call site
	// and compiles it at macro expansion time

	// First, try to read and compile the file at macro expansion time
	// The path is interpreted relative to CARGO_MANIFEST_DIR
	let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_owned());
	let full_path = std::path::Path::new(&manifest_dir).join(&file_path);

	let css = match grass::from_path(&full_path, &grass::Options::default()) {
		Ok(css) => css,
		Err(e) => {
			return syn::Error::new(
				input_lit.span(),
				format!(
					"Failed to compile Sass file '{}': {}",
					file_path.display(),
					e,
				),
			)
			.to_compile_error()
			.into();
		}
	};

	// Generate code that returns the compiled CSS as a string literal
	let expanded = quote! {
		#css
	};

	TokenStream::from(expanded)
}

#[proc_macro]
pub fn sass_str(input: TokenStream) -> TokenStream {
	let input_lit = parse_macro_input!(input as LitStr);
	let sass_str = input_lit.value();

	let css = match grass::from_string(&sass_str, &grass::Options::default()) {
		Ok(css) => css,
		Err(e) => {
			return syn::Error::new(
				input_lit.span(),
				format!("Failed to compile Sass string: {e}."),
			)
			.to_compile_error()
			.into();
		}
	};

	let expanded = quote! { #css };
	TokenStream::from(expanded)
}
