/// Macro for defining lints and relevant metadata for the Solidity linter.
///
/// # Parameters
///
/// Each lint requires the following input fields:
/// - `$id`: Identifier of the generated `SolLint` constant.
/// - `$severity`: The `Severity` of the lint (e.g. `High`, `Med`, `Low`, `Info`, `Gas`).
/// - `$str_id`: A unique identifier used to reference a specific lint during configuration.
/// - `$desc`: A short description of the lint.
///
/// # Note
/// Each lint must have a corresponding markdown documentation file at
/// `crates/lint/docs/<str_id>.md`. The `help` URL is auto-generated by the macro and points to
/// the per-lint page on the Foundry docs site (`getfoundry.sh/forge/linting/<str_id>`). To
/// ensure that new lint rules have their corresponding docs, the existence of every registered
/// lint's markdown file is validated by a unit test (see `crates/lint/src/sol/mod.rs`).
#[macro_export]
macro_rules! declare_forge_lint {
    ($id:ident, $severity:expr, $str_id:expr, $desc:expr) => {
        // Declare the static `Lint` metadata
        pub static $id: SolLint = SolLint {
            id: $str_id,
            severity: $severity,
            description: $desc,
            help: concat!("https://getfoundry.sh/forge/linting/", $str_id),
        };
    };
}

/// Registers Solidity linter passes that can have both early and late variants.
///
/// # Parameters
///
/// Each pass is declared with:
/// - `$pass_id`: Identifier of the generated struct that will implement the pass trait(s).
/// - `$pass_type`: `early`, `late`, `both`, or `project`.
/// - `$lints`: A parenthesized, comma-separated list of `SolLint` constants.
/// - an optional constructor that receives the lint-specific configuration.
///
/// # Outputs
///
/// - Marker structs for each linting pass.
/// - `const REGISTERED_LINTS` containing all registered lint objects.
/// - A function that adds fresh pass factories to Solar's lint registry.
#[macro_export]
macro_rules! register_lints {
    // 1. Internal rule for declaring structs and their associated lints.
    ( @declare_structs $( ($pass_id:ident, $pass_type:ident, ($($lint:expr),* $(,)?) $(, $constructor:expr)? ) ),* $(,)? ) => {
        $(
            #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
            pub struct $pass_id;

            impl $pass_id {
                /// Static slice of lint identifiers associated with this pass.
                const LINT_IDS: &'static [&'static str] = &[$($lint.id),*];
            }
        )*
    };

    // 2. Internal rule for declaring the const array of ALL lints.
    ( @declare_consts $( ($pass_id:ident, $pass_type:ident, ($($lint:expr),* $(,)?) $(, $constructor:expr)? ) ),* $(,)? ) => {
        pub const REGISTERED_LINTS: &[SolLint] = &[
            $(
                $($lint,)*
            )*
        ];
    };

    // 3. Internal rule for declaring the helper functions.
    ( @declare_funcs $( ($pass_id:ident, $pass_type:ident, $lints:tt $(, $constructor:expr)?) ),* $(,)? ) => {
        pub fn register_lints(
            registry: &mut solar_lint::LintRegistry,
            config: &std::sync::Arc<foundry_config::lint::LintSpecificConfig>,
        ) {
            let _ = config;
            $(
                register_lints!(@register_early registry, config, $pass_id, $pass_type $(, $constructor)?);
                register_lints!(@register_late registry, config, $pass_id, $pass_type $(, $constructor)?);
                register_lints!(@register_project registry, config, $pass_id, $pass_type $(, $constructor)?);
            )*
        }
    };

    // --- HELPERS ------------------------------------------------------------
    (@register_early $registry:ident, $config:ident, $_pass_id:ident, late $(, $constructor:expr)?) => {};
    (@register_early $registry:ident, $config:ident, $_pass_id:ident, project $(, $constructor:expr)?) => {};
    (@register_early $registry:ident, $config:ident, $pass_id:ident, $_other:ident, $constructor:expr) => {{
        let config = std::sync::Arc::clone($config);
        $registry.register_early_pass(
            $pass_id::LINT_IDS,
            move || ($constructor)(std::sync::Arc::clone(&config)),
        );
    }};
    (@register_early $registry:ident, $config:ident, $pass_id:ident, $_other:ident) => {
        $registry.register_early_pass($pass_id::LINT_IDS, $pass_id::default);
    };

    (@register_late $registry:ident, $config:ident, $_pass_id:ident, early $(, $constructor:expr)?) => {};
    (@register_late $registry:ident, $config:ident, $_pass_id:ident, project $(, $constructor:expr)?) => {};
    (@register_late $registry:ident, $config:ident, $pass_id:ident, $_other:ident, $constructor:expr) => {{
        let config = std::sync::Arc::clone($config);
        $registry.register_late_pass(
            $pass_id::LINT_IDS,
            move || ($constructor)(std::sync::Arc::clone(&config)),
        );
    }};
    (@register_late $registry:ident, $config:ident, $pass_id:ident, $_other:ident) => {
        $registry.register_late_pass($pass_id::LINT_IDS, $pass_id::default);
    };

    (@register_project $registry:ident, $config:ident, $_pass_id:ident, early $(, $constructor:expr)?) => {};
    (@register_project $registry:ident, $config:ident, $_pass_id:ident, late $(, $constructor:expr)?) => {};
    (@register_project $registry:ident, $config:ident, $_pass_id:ident, both $(, $constructor:expr)?) => {};
    (@register_project $registry:ident, $config:ident, $pass_id:ident, $_other:ident, $constructor:expr) => {{
        let config = std::sync::Arc::clone($config);
        $registry.register_project_pass(
            $pass_id::LINT_IDS,
            move || ($constructor)(std::sync::Arc::clone(&config)),
        );
    }};
    (@register_project $registry:ident, $config:ident, $pass_id:ident, $_other:ident) => {
        $registry.register_project_pass($pass_id::LINT_IDS, $pass_id::default);
    };

    // --- ENTRY POINT ---------------------------------------------------------
    ( $($tokens:tt)* ) => {
        register_lints! { @declare_structs $($tokens)* }
        register_lints! { @declare_consts  $($tokens)* }
        register_lints! { @declare_funcs   $($tokens)* }
    };
}
