//! Macros for convenient key creation and domain definition in domain-key
//!
//! This module provides helpful macros that simplify the creation and usage
//! of domain-specific keys, reducing boilerplate and improving ergonomics.

// ============================================================================
// STATIC KEY MACRO
// ============================================================================

/// Create a validated static key from a string literal.
///
/// This macro combines **compile-time** and **runtime** validation:
///
/// 1. **Compile-time** — a `const` assertion checks the literal against the
///    default [`KeyDomain`](crate::KeyDomain) rules (character set, length ≤ `MAX_LENGTH`,
///    end-character, no consecutive separators).  An invalid literal is a
///    **compile error**, not a runtime panic.
///
/// 2. **Runtime** — [`Key::try_from_static`](crate::Key::try_from_static) is still called to enforce any
///    *custom* domain rules added via [`KeyDomain::validate_domain_rules`](crate::KeyDomain::validate_domain_rules).
///    If those rules reject the key this will panic; treat it as a bug in the
///    domain configuration, not a recoverable error.
///
/// # Arguments
///
/// * `$key_type` - The key type alias (e.g. `UserKey`)
/// * `$key_str`  - A string literal for the key value
///
/// # Examples
///
/// ```rust
/// use domain_key::{Key, Domain, KeyDomain, static_key};
///
/// #[derive(Debug)]
/// struct AdminDomain;
/// impl Domain for AdminDomain {
///     const DOMAIN_NAME: &'static str = "admin";
/// }
/// impl KeyDomain for AdminDomain {}
/// type AdminKey = Key<AdminDomain>;
///
/// // Invalid literals are caught at *compile time*, not at runtime:
/// let key = static_key!(AdminKey, "system_admin");
/// assert_eq!(key.as_str(), "system_admin");
/// ```
#[macro_export]
macro_rules! static_key {
    ($key_type:ty, $key_str:literal) => {{
        // ── Compile-time validation ──────────────────────────────────────
        // `is_valid_key_const` is a `const fn` that runs the default
        // KeyDomain rules against T::MAX_LENGTH.  Evaluating it inside a
        // `const` item forces the compiler to check it right here; an
        // invalid literal becomes a *compile error*.
        const _: () = assert!(
            <$key_type>::is_valid_key_const($key_str),
            concat!(
                "static_key!: literal failed compile-time validation: ",
                $key_str
            ),
        );

        // ── Runtime validation ───────────────────────────────────────────
        // Still needed to enforce any custom `validate_domain_rules`.
        // A panic here means the domain's custom rules reject a key that
        // passed the default rules — fix the key or the domain, not the
        // macro call site.
        match <$key_type>::try_from_static($key_str) {
            Ok(key) => key,
            Err(e) => panic!("static_key!: runtime validation failed: {}", e),
        }
    }};
}

// ============================================================================
// DOMAIN DEFINITION MACRO
// ============================================================================

/// Define a key domain with minimal boilerplate.
///
/// This macro generates:
///
/// * A `#[derive(Debug)]` unit struct with the given visibility.
/// * An impl of [`Domain`](crate::Domain) that sets `DOMAIN_NAME`.
/// * An impl of [`KeyDomain`](crate::KeyDomain) that sets `MAX_LENGTH`.
/// * An inherent `impl` block containing:
///   - `pub const fn is_valid_key(s: &str) -> bool` — evaluates the default
///     domain rules at **compile time** using `MAX_LENGTH` for this domain.
///     Useful in `const` assertions and with [`static_key!`](macro@crate::static_key).
///
/// # Arguments
///
/// * `$name`        — The domain struct name.
/// * `$domain_name` — The human-readable string name embedded in error messages.
/// * `$max_length`  — Optional maximum key length (defaults to
///   [`DEFAULT_MAX_KEY_LENGTH`](crate::DEFAULT_MAX_KEY_LENGTH)).
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_domain, Key};
///
/// // Simple domain — MAX_LENGTH defaults to DEFAULT_MAX_KEY_LENGTH
/// define_domain!(UserDomain, "user");
/// type UserKey = Key<UserDomain>;
///
/// // Domain with a custom max length
/// define_domain!(SessionDomain, "session", 128);
/// type SessionKey = Key<SessionDomain>;
///
/// // Compile-time key validation via the generated const fn
/// const _: () = assert!(UserDomain::is_valid_key("john_doe"));
/// const _: () = assert!(!UserDomain::is_valid_key(""));
///
/// let user    = UserKey::new("john_doe")?;
/// let session = SessionKey::new("sess_abc123")?;
/// # Ok::<(), domain_key::KeyParseError>(())
/// ```
#[macro_export]
macro_rules! define_domain {
    ($vis:vis $name:ident, $domain_name:literal) => {
        $crate::define_domain!($vis $name, $domain_name, $crate::DEFAULT_MAX_KEY_LENGTH);
    };

    ($vis:vis $name:ident, $domain_name:literal, $max_length:expr) => {
        #[derive(Debug)]
        $vis struct $name;

        impl $crate::Domain for $name {
            const DOMAIN_NAME: &'static str = $domain_name;
        }

        impl $crate::KeyDomain for $name {
            const MAX_LENGTH: usize = $max_length;
        }

        #[allow(dead_code)]
        impl $name {
            /// Check whether `s` satisfies the **default** [`KeyDomain`]
            /// validation rules for this domain.
            ///
            /// This is a `const fn` — it is evaluated entirely at compile
            /// time when called in a `const` context.
            ///
            /// # What is checked
            ///
            /// * Non-empty
            /// * `s.len() <= MAX_LENGTH` for this domain
            /// * Every byte is ASCII alphanumeric, `_`, `-`, or `.`
            /// * The last byte is ASCII alphanumeric (not `_`, `-`, `.`)
            /// * No consecutive identical separators (`__`, `--`, `..`)
            ///
            /// # What is **not** checked
            ///
            /// Custom rules added via [`KeyDomain::validate_domain_rules`]
            /// are **not** verified — those are enforced at runtime by
            /// [`Key::new`] / [`Key::try_from_static`].
            ///
            /// # Examples
            ///
            /// ```rust,ignore
            /// // Evaluated entirely at compile time:
            /// const _: () = assert!(MyDomain::is_valid_key("good_key"));
            /// const _: () = assert!(!MyDomain::is_valid_key("bad key!"));
            /// ```
            #[must_use]
            pub const fn is_valid_key(s: &str) -> bool {
                $crate::is_valid_key_default(s, $max_length)
            }
        }
    };
}

// ============================================================================
// KEY TYPE ALIAS MACRO
// ============================================================================

/// Create a key type alias
///
/// This macro creates a type alias for a key.
///
/// # Arguments
///
/// * `$key_name` - The name for the key type alias
/// * `$domain` - The domain type
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_domain, key_type};
///
/// define_domain!(UserDomain, "user");
/// key_type!(UserKey, UserDomain);
///
/// let user = UserKey::new("john")?;
/// # Ok::<(), domain_key::KeyParseError>(())
/// ```
#[macro_export]
macro_rules! key_type {
    ($vis:vis $key_name:ident, $domain:ty) => {
        $vis type $key_name = $crate::Key<$domain>;
    };
}

// ============================================================================
// ID DOMAIN DEFINITION MACRO
// ============================================================================

/// Define an ID domain with minimal boilerplate
///
/// This macro simplifies the definition of ID domains by generating the
/// required trait implementations automatically.
///
/// # Arguments
///
/// * `$name` - The domain struct name
/// * `$domain_name` - The string name for the domain
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_id_domain, Id};
///
/// define_id_domain!(UserIdDomain, "user");
/// type UserId = Id<UserIdDomain>;
///
/// let id = UserId::new(42).unwrap();
/// assert_eq!(id.domain(), "user");
/// ```
#[macro_export]
macro_rules! define_id_domain {
    // Without explicit name — uses stringify
    ($vis:vis $name:ident) => {
        $crate::define_id_domain!(@inner $vis $name, stringify!($name));
    };
    // With explicit string literal
    ($vis:vis $name:ident, $domain_name:literal) => {
        $crate::define_id_domain!(@inner $vis $name, $domain_name);
    };
    (@inner $vis:vis $name:ident, $domain_name:expr) => {
        #[derive(Debug)]
        $vis struct $name;

        impl $crate::Domain for $name {
            const DOMAIN_NAME: &'static str = $domain_name;
        }

        impl $crate::IdDomain for $name {}
    };
}

// ============================================================================
// UUID DOMAIN DEFINITION MACRO
// ============================================================================

/// Define a UUID domain with minimal boilerplate
///
/// This macro simplifies the definition of UUID domains by generating the
/// required trait implementations automatically.
///
/// Requires the `uuid` feature.
///
/// # Arguments
///
/// * `$name` - The domain struct name
/// * `$domain_name` - The string name for the domain
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "uuid")]
/// # {
/// use domain_key::{define_uuid_domain, Uuid};
///
/// define_uuid_domain!(OrderUuidDomain, "order");
/// type OrderUuid = Uuid<OrderUuidDomain>;
///
/// let id = OrderUuid::nil();
/// assert_eq!(id.domain(), "order");
/// # }
/// ```
#[cfg(feature = "uuid")]
#[macro_export]
macro_rules! define_uuid_domain {
    // Without explicit name — uses stringify
    ($vis:vis $name:ident) => {
        $crate::define_uuid_domain!(@inner $vis $name, stringify!($name));
    };
    // With explicit string literal
    ($vis:vis $name:ident, $domain_name:literal) => {
        $crate::define_uuid_domain!(@inner $vis $name, $domain_name);
    };
    (@inner $vis:vis $name:ident, $domain_name:expr) => {
        #[derive(Debug)]
        $vis struct $name;

        impl $crate::Domain for $name {
            const DOMAIN_NAME: &'static str = $domain_name;
        }

        impl $crate::UuidDomain for $name {}
    };
}

// ============================================================================
// ID TYPE ALIAS MACRO
// ============================================================================

/// Create an Id type alias
///
/// This macro creates a type alias for a numeric Id.
///
/// # Arguments
///
/// * `$id_name` - The name for the Id type alias
/// * `$domain` - The domain type (must implement `IdDomain`)
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_id_domain, id_type};
///
/// define_id_domain!(UserIdDomain, "user");
/// id_type!(UserId, UserIdDomain);
///
/// let id = UserId::new(1).unwrap();
/// assert_eq!(id.get(), 1);
/// ```
#[macro_export]
macro_rules! id_type {
    ($vis:vis $id_name:ident, $domain:ty) => {
        $vis type $id_name = $crate::Id<$domain>;
    };
}

// ============================================================================
// UUID TYPE ALIAS MACRO
// ============================================================================

/// Create a Uuid type alias
///
/// This macro creates a type alias for a typed Uuid.
///
/// Requires the `uuid` feature.
///
/// # Arguments
///
/// * `$uuid_name` - The name for the Uuid type alias
/// * `$domain` - The domain type (must implement `UuidDomain`)
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "uuid")]
/// # {
/// use domain_key::{define_uuid_domain, uuid_type};
///
/// define_uuid_domain!(OrderUuidDomain, "order");
/// uuid_type!(OrderUuid, OrderUuidDomain);
///
/// let id = OrderUuid::nil();
/// assert!(id.is_nil());
/// # }
/// ```
#[cfg(feature = "uuid")]
#[macro_export]
macro_rules! uuid_type {
    ($vis:vis $uuid_name:ident, $domain:ty) => {
        $vis type $uuid_name = $crate::Uuid<$domain>;
    };
}

// ============================================================================
// COMBINED DOMAIN + TYPE ALIAS MACROS
// ============================================================================

/// Define an Id domain and type alias in one step
///
/// This is a convenience macro that combines [`define_id_domain!`] and [`id_type!`].
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_id, Id};
///
/// define_id!(UserIdDomain => UserId);
///
/// let id = UserId::new(42).unwrap();
/// assert_eq!(id.domain(), "UserId");
/// ```
#[macro_export]
macro_rules! define_id {
    ($vis:vis $domain:ident => $alias:ident) => {
        $crate::define_id_domain!(@inner $vis $domain, stringify!($alias));
        $crate::id_type!($vis $alias, $domain);
    };
}

/// Define a Uuid domain and type alias in one step
///
/// This is a convenience macro that combines [`define_uuid_domain!`] and [`uuid_type!`].
///
/// Requires the `uuid` feature.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "uuid")]
/// # {
/// use domain_key::{define_uuid, Uuid};
///
/// define_uuid!(OrderUuidDomain => OrderUuid);
///
/// let id = OrderUuid::nil();
/// assert_eq!(id.domain(), "OrderUuid");
/// # }
/// ```
#[cfg(feature = "uuid")]
#[macro_export]
macro_rules! define_uuid {
    ($vis:vis $domain:ident => $alias:ident) => {
        $crate::define_uuid_domain!(@inner $vis $domain, stringify!($alias));
        $crate::uuid_type!($vis $alias, $domain);
    };
}

// ============================================================================
// ULID DOMAIN DEFINITION MACROS
// ============================================================================

/// Define a [`UlidDomain`](crate::UlidDomain) with a string [`DOMAIN_NAME`](crate::Domain::DOMAIN_NAME) and prefix.
///
/// Requires the `ulid` feature.
///
/// # Forms
///
/// * `define_ulid_domain!(vis Name, "domain_name", "prefix");`
/// * `define_ulid_domain!(vis Name, prefix = "exe");` — `DOMAIN_NAME` is [`stringify!`](std::stringify) of the struct name.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "ulid")]
/// # {
/// use domain_key::{define_ulid_domain, Ulid};
///
/// define_ulid_domain!(pub OrgDomain, "org", "org");
/// type OrgUlid = Ulid<OrgDomain>;
///
/// let id = OrgUlid::nil();
/// assert_eq!(id.domain(), "org");
/// # }
/// ```
#[cfg(feature = "ulid")]
#[macro_export]
macro_rules! define_ulid_domain {
    ($vis:vis $name:ident, $domain_name:expr, $prefix:literal) => {
        #[derive(Debug)]
        $vis struct $name;

        impl $crate::Domain for $name {
            const DOMAIN_NAME: &'static str = $domain_name;
        }

        impl $crate::UlidDomain for $name {
            const PREFIX: &'static str = $prefix;
        }
    };
    ($vis:vis $name:ident, prefix = $prefix:literal) => {
        $crate::define_ulid_domain!($vis $name, stringify!($name), $prefix);
    };
}

/// Create a [`Ulid`](crate::Ulid) type alias.
///
/// Requires the `ulid` feature.
#[cfg(feature = "ulid")]
#[macro_export]
macro_rules! ulid_type {
    ($vis:vis $ulid_name:ident, $domain:ty) => {
        $vis type $ulid_name = $crate::Ulid<$domain>;
    };
}

/// Define a [`UlidDomain`](crate::UlidDomain) and [`Ulid`](crate::Ulid) alias with a prefix.
///
/// `DOMAIN_NAME` is [`stringify!`](std::stringify) of the alias ident (same pattern as [`define_uuid!`]).
///
/// Requires the `ulid` feature.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "ulid")]
/// # {
/// use domain_key::{define_ulid, Ulid};
///
/// define_ulid!(pub ExecutionIdDomain => ExecutionId, prefix = "exe");
///
/// let id = ExecutionId::nil();
/// assert!(id.to_string().starts_with("exe_"));
/// # }
/// ```
#[cfg(feature = "ulid")]
#[macro_export]
macro_rules! define_ulid {
    ($vis:vis $domain:ident => $alias:ident, prefix = $prefix:literal) => {
        $crate::define_ulid_domain!($vis $domain, stringify!($alias), $prefix);
        $crate::ulid_type!($vis $alias, $domain);
    };
}

// ============================================================================
// BATCH KEY CREATION MACRO
// ============================================================================

/// Create multiple keys at once with error handling
///
/// This macro simplifies the creation of multiple keys from string literals
/// or expressions, with automatic error collection.
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_domain, key_type, batch_keys};
///
/// define_domain!(UserDomain, "user");
/// key_type!(UserKey, UserDomain);
///
/// // Create multiple keys, collecting any errors
/// let result = batch_keys!(UserKey => [
///     "user_1",
///     "user_2",
///     "user_3",
/// ]);
///
/// match result {
///     Ok(keys) => println!("Created {} keys", keys.len()),
///     Err(errors) => println!("Failed to create {} keys", errors.len()),
/// }
/// ```
#[macro_export]
macro_rules! batch_keys {
    ($key_type:ty => [$($key_str:expr),* $(,)?]) => {{
        use $crate::__private::{Vec, ToString};
        let mut keys = Vec::new();
        let mut errors = Vec::new();

        $(
            match <$key_type>::new($key_str) {
                Ok(key) => keys.push(key),
                Err(e) => errors.push(($key_str.to_string(), e)),
            }
        )*

        if errors.is_empty() {
            Ok(keys)
        } else {
            Err(errors)
        }
    }};
}

// ============================================================================
// TESTING HELPERS
// ============================================================================

/// Generate test cases for key domains
///
/// This macro creates comprehensive test cases for a domain,
/// testing both valid and invalid keys. The macro generates a `domain_tests`
/// submodule with test functions.
///
/// **Important**: This macro must be used at module level, not inside functions.
///
/// # Arguments
///
/// * `$domain` - The domain type to test
/// * `valid` - Array of string literals that should be valid keys
/// * `invalid` - Array of string literals that should be invalid keys
///
/// # Examples
///
/// ```rust
/// use domain_key::{define_domain, test_domain};
///
/// define_domain!(MyTestDomain, "test");
///
/// // This creates a `domain_tests` module with test functions
/// test_domain!(MyTestDomain {
///     valid: [
///         "valid_key",
///         "another_valid",
///         "key123",
///     ],
///     invalid: [
///         "",
///         "key with spaces",
///     ]
/// });
/// ```
///
/// The generated tests will:
/// - Test that all valid keys can be created successfully
/// - Test that all invalid keys fail to create with appropriate errors
/// - Test basic domain properties (name, max length, etc.)
///
/// Note: This macro should be used at module level, not inside functions.
#[macro_export]
macro_rules! test_domain {
    // With explicit module name: test_domain!(MyDomain as my_domain_tests { ... })
    //
    // Use this form when invoking the macro more than once in the same module to
    // avoid the `mod domain_tests` name collision.
    ($domain:ty as $mod_name:ident {
        valid: [$($valid:literal),* $(,)?],
        invalid: [$($invalid:literal),* $(,)?] $(,)?
    }) => {
        #[cfg(test)]
        mod $mod_name {
            use super::*;

            type TestKey = $crate::Key<$domain>;

            #[test]
            fn test_valid_keys() {
                $(
                    let key = TestKey::new($valid);
                    assert!(key.is_ok(), "Key '{}' should be valid: {:?}", $valid, key.err());
                )*
            }

            #[test]
            fn test_invalid_keys() {
                $(
                    let key = TestKey::new($invalid);
                    assert!(key.is_err(), "Key '{}' should be invalid", $invalid);
                )*
            }

            #[test]
            fn test_domain_properties() {
                use $crate::Domain;
                use $crate::KeyDomain;

                // Test domain constants
                assert!(!<$domain>::DOMAIN_NAME.is_empty());
                assert!(<$domain>::MAX_LENGTH > 0);

                // Test validation help if available
                if let Some(help) = <$domain>::validation_help() {
                    assert!(!help.is_empty());
                }
            }
        }
    };

    // Without explicit module name (backward-compatible) — defaults to `domain_tests`.
    //
    // Note: Only one such invocation is allowed per module.  If you need a second
    // invocation in the same module, supply an explicit name with the `as` form above.
    ($domain:ty {
        valid: [$($valid:literal),* $(,)?],
        invalid: [$($invalid:literal),* $(,)?] $(,)?
    }) => {
        $crate::test_domain!($domain as domain_tests {
            valid: [$($valid),*],
            invalid: [$($invalid),*]
        });
    };
}

// ============================================================================
// TESTS
// ============================================================================

#[cfg(test)]
mod tests {
    use crate::{Domain, Key, KeyDomain};
    #[cfg(not(feature = "std"))]
    use alloc::string::ToString;

    // Test define_domain macro
    define_domain!(MacroTestDomain, "macro_test");
    type MacroTestKey = Key<MacroTestDomain>;

    // Test define_domain with custom max length
    define_domain!(LongDomain, "long", 256);
    #[expect(
        dead_code,
        reason = "alias defined to test compile-time macro expansion"
    )]
    type LongKey = Key<LongDomain>;

    #[test]
    fn define_domain_sets_name_and_max_length() {
        assert_eq!(MacroTestDomain::DOMAIN_NAME, "macro_test");
        assert_eq!(MacroTestDomain::MAX_LENGTH, crate::DEFAULT_MAX_KEY_LENGTH);

        assert_eq!(LongDomain::DOMAIN_NAME, "long");
        assert_eq!(LongDomain::MAX_LENGTH, 256);
    }

    #[test]
    fn static_key_validates_and_creates_key() {
        let key = static_key!(MacroTestKey, "static_test");
        assert_eq!(key.as_str(), "static_test");
        assert_eq!(key.domain(), "macro_test");
    }

    #[test]
    fn key_type_creates_usable_alias() {
        key_type!(TestKey, MacroTestDomain);
        let key = TestKey::new("test_key").unwrap();
        assert_eq!(key.as_str(), "test_key");
    }

    #[test]
    fn batch_keys_collects_all_valid_keys() {
        let result = batch_keys!(MacroTestKey => [
            "key1",
            "key2",
            "key3",
        ]);

        assert!(result.is_ok());
        let keys = result.unwrap();
        assert_eq!(keys.len(), 3);
        assert_eq!(keys[0].as_str(), "key1");
        assert_eq!(keys[1].as_str(), "key2");
        assert_eq!(keys[2].as_str(), "key3");
    }

    #[test]
    fn batch_keys_returns_errors_for_invalid_entries() {
        let result = batch_keys!(MacroTestKey => [
            "valid_key",
            "", // This should fail
            "another_valid",
        ]);

        assert!(result.is_err());
        let errors = result.unwrap_err();
        assert_eq!(errors.len(), 1);
        assert_eq!(errors[0].0, "");
    }

    #[test]
    fn define_id_creates_domain_and_alias() {
        define_id!(TestIdDomain2 => TestId2);
        let id = TestId2::new(42).unwrap();
        assert_eq!(id.get(), 42);
        assert_eq!(id.domain(), "TestId2");
    }

    #[cfg(feature = "uuid")]
    #[test]
    fn define_uuid_creates_domain_and_alias() {
        define_uuid!(TestUuidDomain2 => TestUuid2);
        let id = TestUuid2::nil();
        assert!(id.is_nil());
        assert_eq!(id.domain(), "TestUuid2");
    }

    #[cfg(feature = "ulid")]
    #[test]
    fn define_ulid_creates_domain_and_alias() {
        define_ulid!(TestUlidDomain2 => TestUlid2, prefix = "tu");
        let id = TestUlid2::nil();
        assert!(id.is_nil());
        assert_eq!(id.domain(), "TestUlid2");
        assert!(id.to_string().starts_with("tu_"));
    }

    // Test the test_domain macro - use it at module level
    #[cfg(test)]
    mod test_domain_macro_test {

        // Define a test domain specifically for this test
        define_domain!(pub TestMacroDomain, "test_macro");

        // Apply the test_domain macro
        test_domain!(TestMacroDomain {
            valid: ["valid_key", "another_valid", "key123",],
            invalid: ["",]
        });
    }
}
