use mel_sema::{
    CommandKind, CommandModeMask, CommandRegistry, CommandSchema, CommandSourceKind, FlagArity,
    FlagArityByMode, FlagSchema, PositionalSchema, PositionalSlotSchema, PositionalSourcePolicy,
    PositionalTailSchema, ReturnBehavior, ValidatedCommandSchema, ValueShape,
};
use std::sync::OnceLock;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
/// Builtin Maya command registry embedded in the crate.
pub struct MayaCommandRegistry;

impl MayaCommandRegistry {
    #[must_use]
    /// Construct the builtin Maya command registry.
    pub const fn new() -> Self {
        Self
    }
}

impl CommandRegistry for MayaCommandRegistry {
    fn lookup(&self, name: &str) -> Option<&ValidatedCommandSchema> {
        shared_command_schemas()
            .binary_search_by(|schema| schema.name.as_ref().cmp(name))
            .ok()
            .map(|index| &shared_command_schemas()[index])
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EmbeddedFlagSchema {
    long_name: &'static str,
    short_name: Option<&'static str>,
    mode_mask: CommandModeMask,
    arity_by_mode: FlagArityByMode,
    value_shapes: &'static [ValueShape],
    allows_multiple: bool,
}

impl EmbeddedFlagSchema {
    fn to_shared_schema(self) -> FlagSchema {
        FlagSchema {
            long_name: self.long_name.into(),
            short_name: self.short_name.map(Into::into),
            mode_mask: self.mode_mask,
            arity_by_mode: self.arity_by_mode,
            value_shapes: self.value_shapes.into(),
            allows_multiple: self.allows_multiple,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EmbeddedCommandSchema {
    name: &'static str,
    kind: CommandKind,
    source_kind: CommandSourceKind,
    mode_mask: CommandModeMask,
    return_behavior: ReturnBehavior,
    positionals: PositionalSchema,
    flags: &'static [EmbeddedFlagSchema],
}

impl EmbeddedCommandSchema {
    fn to_shared_schema(self) -> ValidatedCommandSchema {
        let schema = CommandSchema {
            name: self.name.into(),
            kind: self.kind,
            source_kind: self.source_kind,
            mode_mask: self.mode_mask,
            return_behavior: self.return_behavior,
            positionals: self.positionals,
            flags: self.build_effective_flags().into(),
        };
        ValidatedCommandSchema::new(schema)
            .expect("embedded command schemas must satisfy mel-sema schema invariants")
    }

    fn build_effective_flags(self) -> Vec<FlagSchema> {
        let mut flags: Vec<FlagSchema> = self
            .flags
            .iter()
            .copied()
            .map(EmbeddedFlagSchema::to_shared_schema)
            .collect();
        push_synthetic_mode_flag(&mut flags, self.mode_mask.create, "create", "c");
        push_synthetic_mode_flag(&mut flags, self.mode_mask.edit, "edit", "e");
        push_synthetic_mode_flag(&mut flags, self.mode_mask.query, "query", "q");
        flags
    }
}

static EMBEDDED_COMMAND_SCHEMAS: &[EmbeddedCommandSchema] =
    include!(concat!(env!("OUT_DIR"), "/embedded_command_schemas.rs"));

fn shared_command_schemas() -> &'static [ValidatedCommandSchema] {
    static COMMAND_SCHEMAS: OnceLock<Vec<ValidatedCommandSchema>> = OnceLock::new();
    COMMAND_SCHEMAS.get_or_init(|| {
        EMBEDDED_COMMAND_SCHEMAS
            .iter()
            .copied()
            .map(EmbeddedCommandSchema::to_shared_schema)
            .collect()
    })
}

pub(crate) fn push_synthetic_mode_flag(
    flags: &mut Vec<FlagSchema>,
    enabled: bool,
    long_name: &str,
    short_name: &str,
) {
    if !enabled
        || flags.iter().any(|flag| {
            flag.long_name.as_ref() == long_name || flag.short_name.as_deref() == Some(short_name)
        })
    {
        return;
    }

    flags.push(FlagSchema {
        long_name: long_name.into(),
        short_name: Some(short_name.into()),
        mode_mask: CommandModeMask {
            create: true,
            edit: true,
            query: true,
        },
        arity_by_mode: FlagArityByMode {
            create: FlagArity::None,
            edit: FlagArity::None,
            query: FlagArity::None,
        },
        value_shapes: Vec::new().into(),
        allows_multiple: false,
    });
}

pub(crate) struct OverlayRegistry<'a, R: ?Sized> {
    primary: &'a R,
    fallback: MayaCommandRegistry,
}

impl<'a, R> OverlayRegistry<'a, R>
where
    R: CommandRegistry + ?Sized,
{
    pub(crate) const fn new(primary: &'a R) -> Self {
        Self {
            primary,
            fallback: MayaCommandRegistry::new(),
        }
    }
}

impl<R> CommandRegistry for OverlayRegistry<'_, R>
where
    R: CommandRegistry + ?Sized,
{
    fn lookup(&self, name: &str) -> Option<&ValidatedCommandSchema> {
        self.primary
            .lookup(name)
            .or_else(|| self.fallback.lookup(name))
    }
}
