//! Macro to eliminate boilerplate in message-type test config modules.
//!
//! Each message-type config module (orders.rs, invoic.rs, etc.) defines the same
//! constants, CONFIG struct, and ~11 wrapper functions. This macro generates all
//! of that from a declarative specification.
//!
//! MIG/AHB XML paths are discovered automatically at runtime by pattern matching
//! in `xml-migs-and-ahbs/{format_version}/`. No hardcoded XML filenames needed.

/// Generates a complete message-type test config module with all standard
/// constants and wrapper functions.
///
/// # Fixture discovery variants
///
/// - `simple` — checks `GENERATED_FIXTURE_DIR/{pid}.edi`
/// - `prefer_local($local_dir)` — tries `$local_dir/{pid}.edi`, then `FIXTURE_DIR/generated/{pid}.edi`
/// - `search_prefix` — scans `FIXTURE_DIR/generated/` for `{pid}_*.edi`
/// - `prefer_local_search($local_dir)` — tries `$local_dir/{pid}.edi`, then scans `FIXTURE_DIR/generated/` for `{pid}_*.edi`
macro_rules! define_message_type_config {
    // ── simple discovery ──
    (
        fixture_dir: $fixture_dir:expr,
        mappings_base: $mappings_base:expr,
        schema_dir: $schema_dir:expr,
        message_type: $message_type:expr,
        variant: $variant:expr,
        tx_group: $tx_group:expr,
        format_version: $format_version:expr,
        generated_fixture_dir: $generated_fixture_dir:expr,
        fixture_discovery: simple $(,)?
    ) => {
        use super::test_utils::MessageTypeConfig;
        use mig_bo4e::engine::MappingEngine;
        use mig_bo4e::path_resolver::PathResolver;
        use mig_bo4e::pid_schema_index::PidSchemaIndex;
        use mig_types::schema::mig::MigSchema;
        use std::path::{Path, PathBuf};

        pub const FIXTURE_DIR: &str = $fixture_dir;
        pub const MAPPINGS_BASE: &str = $mappings_base;
        pub const SCHEMA_DIR: &str = $schema_dir;
        pub const TX_GROUP: &str = $tx_group;
        pub const GENERATED_FIXTURE_DIR: &str = $generated_fixture_dir;

        const CONFIG: MessageTypeConfig = MessageTypeConfig {
            fixture_dir: FIXTURE_DIR,
            mappings_base: MAPPINGS_BASE,
            schema_dir: SCHEMA_DIR,
            message_type: $message_type,
            variant: $variant,
            tx_group: TX_GROUP,
            format_version: $format_version,
        };

        define_message_type_config!(@wrapper_fns);

        pub fn discover_generated_fixture(pid: &str) -> Option<PathBuf> {
            let path = Path::new(GENERATED_FIXTURE_DIR).join(format!("{pid}.edi"));
            if path.exists() {
                Some(path)
            } else {
                None
            }
        }
    };

    // ── prefer_local discovery ──
    (
        fixture_dir: $fixture_dir:expr,
        mappings_base: $mappings_base:expr,
        schema_dir: $schema_dir:expr,
        message_type: $message_type:expr,
        variant: $variant:expr,
        tx_group: $tx_group:expr,
        format_version: $format_version:expr,
        generated_fixture_dir: $generated_fixture_dir:expr,
        fixture_discovery: prefer_local($local_dir:expr) $(,)?
    ) => {
        use super::test_utils::MessageTypeConfig;
        use mig_bo4e::engine::MappingEngine;
        use mig_bo4e::path_resolver::PathResolver;
        use mig_bo4e::pid_schema_index::PidSchemaIndex;
        use mig_types::schema::mig::MigSchema;
        use std::path::{Path, PathBuf};

        pub const FIXTURE_DIR: &str = $fixture_dir;
        pub const MAPPINGS_BASE: &str = $mappings_base;
        pub const SCHEMA_DIR: &str = $schema_dir;
        pub const TX_GROUP: &str = $tx_group;
        pub const GENERATED_FIXTURE_DIR: &str = $generated_fixture_dir;

        const CONFIG: MessageTypeConfig = MessageTypeConfig {
            fixture_dir: FIXTURE_DIR,
            mappings_base: MAPPINGS_BASE,
            schema_dir: SCHEMA_DIR,
            message_type: $message_type,
            variant: $variant,
            tx_group: TX_GROUP,
            format_version: $format_version,
        };

        define_message_type_config!(@wrapper_fns);

        pub fn discover_generated_fixture(pid: &str) -> Option<PathBuf> {
            let local = Path::new($local_dir).join(format!("{pid}.edi"));
            if local.exists() {
                return Some(local);
            }
            let path = Path::new(GENERATED_FIXTURE_DIR).join(format!("{pid}.edi"));
            if path.exists() {
                Some(path)
            } else {
                None
            }
        }
    };

    // ── search_prefix discovery ──
    (
        fixture_dir: $fixture_dir:expr,
        mappings_base: $mappings_base:expr,
        schema_dir: $schema_dir:expr,
        message_type: $message_type:expr,
        variant: $variant:expr,
        tx_group: $tx_group:expr,
        format_version: $format_version:expr,
        fixture_discovery: search_prefix $(,)?
    ) => {
        use super::test_utils::MessageTypeConfig;
        use mig_bo4e::engine::MappingEngine;
        use mig_bo4e::path_resolver::PathResolver;
        use mig_bo4e::pid_schema_index::PidSchemaIndex;
        use mig_types::schema::mig::MigSchema;
        use std::path::{Path, PathBuf};

        pub const FIXTURE_DIR: &str = $fixture_dir;
        pub const MAPPINGS_BASE: &str = $mappings_base;
        pub const SCHEMA_DIR: &str = $schema_dir;
        pub const TX_GROUP: &str = $tx_group;

        const CONFIG: MessageTypeConfig = MessageTypeConfig {
            fixture_dir: FIXTURE_DIR,
            mappings_base: MAPPINGS_BASE,
            schema_dir: SCHEMA_DIR,
            message_type: $message_type,
            variant: $variant,
            tx_group: TX_GROUP,
            format_version: $format_version,
        };

        define_message_type_config!(@wrapper_fns);

        pub fn discover_generated_fixture(pid: &str) -> Option<PathBuf> {
            let dir = Path::new(FIXTURE_DIR).join("generated");
            let prefix = format!("{pid}_");
            if !dir.exists() {
                return None;
            }
            std::fs::read_dir(&dir)
                .ok()?
                .filter_map(|e| e.ok())
                .map(|e| e.path())
                .find(|p| {
                    p.file_name()
                        .and_then(|n| n.to_str())
                        .map(|n| n.starts_with(&prefix) && n.ends_with(".edi"))
                        .unwrap_or(false)
                })
        }
    };

    // ── prefer_local_search: try local dir, then search_prefix in FIXTURE_DIR/generated/ ──
    (
        fixture_dir: $fixture_dir:expr,
        mappings_base: $mappings_base:expr,
        schema_dir: $schema_dir:expr,
        message_type: $message_type:expr,
        variant: $variant:expr,
        tx_group: $tx_group:expr,
        format_version: $format_version:expr,
        fixture_discovery: prefer_local_search($local_dir:expr) $(,)?
    ) => {
        use super::test_utils::MessageTypeConfig;
        use mig_bo4e::engine::MappingEngine;
        use mig_bo4e::path_resolver::PathResolver;
        use mig_bo4e::pid_schema_index::PidSchemaIndex;
        use mig_types::schema::mig::MigSchema;
        use std::path::{Path, PathBuf};

        pub const FIXTURE_DIR: &str = $fixture_dir;
        pub const MAPPINGS_BASE: &str = $mappings_base;
        pub const SCHEMA_DIR: &str = $schema_dir;
        pub const TX_GROUP: &str = $tx_group;

        const CONFIG: MessageTypeConfig = MessageTypeConfig {
            fixture_dir: FIXTURE_DIR,
            mappings_base: MAPPINGS_BASE,
            schema_dir: SCHEMA_DIR,
            message_type: $message_type,
            variant: $variant,
            tx_group: TX_GROUP,
            format_version: $format_version,
        };

        define_message_type_config!(@wrapper_fns);

        pub fn discover_generated_fixture(pid: &str) -> Option<PathBuf> {
            let local = Path::new($local_dir).join(format!("{pid}.edi"));
            if local.exists() {
                return Some(local);
            }
            let dir = Path::new(FIXTURE_DIR).join("generated");
            let prefix = format!("{pid}_");
            if !dir.exists() {
                return None;
            }
            std::fs::read_dir(&dir)
                .ok()?
                .filter_map(|e| e.ok())
                .map(|e| e.path())
                .find(|p| {
                    p.file_name()
                        .and_then(|n| n.to_str())
                        .map(|n| n.starts_with(&prefix) && n.ends_with(".edi"))
                        .unwrap_or(false)
                })
        }
    };

    // ── internal: shared wrapper functions ──
    (@wrapper_fns) => {
        pub fn path_resolver() -> PathResolver {
            CONFIG.path_resolver()
        }

        pub fn message_dir() -> PathBuf {
            CONFIG.message_dir()
        }

        pub fn common_dir() -> PathBuf {
            CONFIG.common_dir()
        }

        pub fn pid_dir(pid: &str) -> PathBuf {
            CONFIG.pid_dir(pid)
        }

        pub fn schema_index(pid: &str) -> PidSchemaIndex {
            CONFIG.schema_index(pid)
        }

        pub fn load_pid_filtered_mig(pid_id: &str) -> Option<MigSchema> {
            CONFIG.load_pid_filtered_mig(pid_id)
        }

        pub fn discover_fixtures(pid: &str) -> Vec<PathBuf> {
            CONFIG.discover_fixtures(pid)
        }

        pub fn load_split_engines(pid: &str) -> (MappingEngine, MappingEngine) {
            CONFIG.load_split_engines(pid)
        }

        pub fn load_message_engine() -> MappingEngine {
            CONFIG.load_message_engine()
        }

        pub fn run_full_roundtrip(pid: &str) {
            CONFIG.run_full_roundtrip(pid);
        }

        pub fn run_full_roundtrip_with_skip(pid: &str, known_incomplete: &[&str]) {
            CONFIG.run_full_roundtrip_with_skip(pid, known_incomplete);
        }
    };
}
