//! Typed builders for the most common `hx-*` attribute values.
//!
//! These are not wrappers around HTML; they produce plain `&str`/`String`
//! values ready to be embedded into Leptos `view!{}` attribute slots.
//! Keeping these typed prevents typos in trigger/swap strings that would
//! silently fall back to htmx defaults.

use std::fmt;

/// `hx-target` value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HxTarget {
    /// Replace `this` element (the one carrying the trigger).
    This,
    /// Replace the element matched by the CSS selector.
    Selector(String),
    /// Replace the closest ancestor matching the selector.
    Closest(String),
    /// Replace the next sibling matching the selector.
    Next(String),
    /// Replace the previous sibling matching the selector.
    Previous(String),
    /// Replace the body. Equivalent to `body`.
    Body,
}

impl fmt::Display for HxTarget {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            HxTarget::This => f.write_str("this"),
            HxTarget::Selector(s) => f.write_str(s),
            HxTarget::Closest(s) => write!(f, "closest {s}"),
            HxTarget::Next(s) => write!(f, "next {s}"),
            HxTarget::Previous(s) => write!(f, "previous {s}"),
            HxTarget::Body => f.write_str("body"),
        }
    }
}

/// `hx-swap` value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HxSwap {
    InnerHtml,
    OuterHtml,
    BeforeBegin,
    AfterBegin,
    BeforeEnd,
    AfterEnd,
    Delete,
    None,
    /// Requires the `idiomorph` extension; preserves form/input state.
    Morph,
    /// Requires the `idiomorph` extension; morphs only innerHTML.
    MorphInner,
}

impl fmt::Display for HxSwap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            HxSwap::InnerHtml => "innerHTML",
            HxSwap::OuterHtml => "outerHTML",
            HxSwap::BeforeBegin => "beforebegin",
            HxSwap::AfterBegin => "afterbegin",
            HxSwap::BeforeEnd => "beforeend",
            HxSwap::AfterEnd => "afterend",
            HxSwap::Delete => "delete",
            HxSwap::None => "none",
            HxSwap::Morph => "morph",
            HxSwap::MorphInner => "morph:innerHTML",
        })
    }
}

/// `hx-trigger` value. Composable: multiple triggers join with `, `.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HxTrigger {
    Click,
    Submit,
    /// `change` event — typical for `<select>` and checkboxes.
    Change,
    /// `blur` event — used for per-field form validation.
    Blur,
    /// `keyup changed delayed:NNms` — instant-search style with debounce.
    KeyupDebounced {
        delay_ms: u32,
    },
    /// `every Ns` — polling.
    Every {
        seconds: u32,
    },
    /// `load` event — fires once on element load.
    Load,
    /// Raw trigger string when none of the above fits.
    Raw(String),
}

impl fmt::Display for HxTrigger {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            HxTrigger::Click => f.write_str("click"),
            HxTrigger::Submit => f.write_str("submit"),
            HxTrigger::Change => f.write_str("change"),
            HxTrigger::Blur => f.write_str("blur"),
            HxTrigger::KeyupDebounced { delay_ms } => {
                write!(f, "keyup changed delay:{delay_ms}ms")
            }
            HxTrigger::Every { seconds } => write!(f, "every {seconds}s"),
            HxTrigger::Load => f.write_str("load"),
            HxTrigger::Raw(s) => f.write_str(s),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn target_renders_each_variant() {
        assert_eq!(HxTarget::This.to_string(), "this");
        assert_eq!(HxTarget::Body.to_string(), "body");
        assert_eq!(HxTarget::Selector("#x".into()).to_string(), "#x");
        assert_eq!(
            HxTarget::Closest(".field".into()).to_string(),
            "closest .field"
        );
        assert_eq!(HxTarget::Next("section".into()).to_string(), "next section");
        assert_eq!(HxTarget::Previous("p".into()).to_string(), "previous p");
    }

    #[test]
    fn swap_renders_each_variant() {
        assert_eq!(HxSwap::InnerHtml.to_string(), "innerHTML");
        assert_eq!(HxSwap::OuterHtml.to_string(), "outerHTML");
        assert_eq!(HxSwap::Delete.to_string(), "delete");
        assert_eq!(HxSwap::Morph.to_string(), "morph");
        assert_eq!(HxSwap::MorphInner.to_string(), "morph:innerHTML");
    }

    #[test]
    fn trigger_keyup_includes_debounce() {
        let t = HxTrigger::KeyupDebounced { delay_ms: 300 };
        assert_eq!(t.to_string(), "keyup changed delay:300ms");
    }

    #[test]
    fn trigger_every_renders_seconds() {
        assert_eq!(HxTrigger::Every { seconds: 5 }.to_string(), "every 5s");
    }
}
