use pagetop::prelude::*;

use crate::prelude::*;
use crate::LOCALES_BOOTSIER;

/// Componente para crear un **menú desplegable**.
///
/// Renderiza un botón (único o desdoblado, ver [`with_button_split()`](Self::with_button_split))
/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra/oculta según la
/// interacción del usuario. Admite variaciones de tamaño/color del botón, también dirección de
/// apertura, alineación o política de cierre.
///
/// Si no tiene título (ver [`with_title()`](Self::with_title)) se muestra únicamente la lista de
/// elementos sin ningún botón para interactuar.
///
/// Si este componente se usa en un menú [`Nav`] (ver [`nav::Item::dropdown()`]) sólo se tendrán en
/// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el
/// resto de propiedades no afectarán a su representación en [`Nav`].
///
/// Ver ejemplo en el módulo [`dropdown`].
/// Si no contiene elementos, el componente **no se renderiza**.
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Dropdown {
    #[getters(skip)]
    id: AttrId,
    /// Devuelve las clases CSS asociadas al menú desplegable.
    classes: Classes,
    /// Devuelve el título del menú desplegable.
    title: L10n,
    /// Devuelve el tamaño configurado del botón.
    button_size: ButtonSize,
    /// Devuelve el color/estilo configurado del botón.
    button_color: ButtonColor,
    /// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*).
    button_split: bool,
    /// Devuelve si el botón del menú está integrado en un grupo de botones.
    button_grouped: bool,
    /// Devuelve la política de cierre automático del menú desplegado.
    auto_close: dropdown::AutoClose,
    /// Devuelve la dirección de despliegue configurada.
    direction: dropdown::Direction,
    /// Devuelve la configuración de alineación horizontal del menú desplegable.
    menu_align: dropdown::MenuAlign,
    /// Devuelve la posición configurada para el menú desplegable.
    menu_position: dropdown::MenuPosition,
    /// Devuelve la lista de elementos del menú.
    items: Children,
}

impl Component for Dropdown {
    fn new() -> Self {
        Self::default()
    }

    fn id(&self) -> Option<String> {
        self.id.get()
    }

    fn setup(&mut self, _cx: &Context) {
        self.alter_classes(
            ClassesOp::Prepend,
            self.direction().class_with(*self.button_grouped()),
        );
    }

    fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
        // Si no hay elementos en el menú, no se prepara.
        let items = self.items().render(cx);
        if items.is_empty() {
            return Ok(html! {});
        }

        // Título opcional para el menú desplegable.
        let title = self.title().using(cx);

        Ok(html! {
            div id=[self.id()] class=[self.classes().get()] {
                @if !title.is_empty() {
                    @let mut btn_classes = Classes::new({
                        let mut classes = "btn".to_string();
                        self.button_size().push_class(&mut classes);
                        self.button_color().push_class(&mut classes);
                        classes
                    });
                    @let pos = self.menu_position();
                    @let offset = pos.data_offset();
                    @let reference = pos.data_reference();
                    @let auto_close = self.auto_close.as_str();
                    @let menu_classes = Classes::new({
                        let mut classes = "dropdown-menu".to_string();
                        self.menu_align().push_class(&mut classes);
                        classes
                    });

                    // Renderizado en modo split (dos botones) o simple (un botón).
                    @if *self.button_split() {
                        // Botón principal (acción/etiqueta).
                        @let btn = html! {
                            button
                                type="button"
                                class=[btn_classes.get()]
                            {
                                (title)
                            }
                        };
                        // Botón *toggle* que abre/cierra el menú asociado.
                        @let btn_toggle = html! {
                            button
                                type="button"
                                class=[btn_classes.alter_classes(
                                    ClassesOp::Add, "dropdown-toggle dropdown-toggle-split"
                                ).get()]
                                data-bs-toggle="dropdown"
                                data-bs-offset=[offset]
                                data-bs-reference=[reference]
                                data-bs-auto-close=[auto_close]
                                aria-expanded="false"
                            {
                                span class="visually-hidden" {
                                    (L10n::t("dropdown_toggle", &LOCALES_BOOTSIER).using(cx))
                                }
                            }
                        };
                        // Orden según dirección (en `dropstart` el *toggle* se sitúa antes).
                        @match self.direction() {
                            dropdown::Direction::Dropstart => {
                                (btn_toggle)
                                ul class=[menu_classes.get()] { (items) }
                                (btn)
                            }
                            _ => {
                                (btn)
                                (btn_toggle)
                                ul class=[menu_classes.get()] { (items) }
                            }
                        }
                    } @else {
                        // Botón único con funcionalidad de *toggle*.
                        button
                            type="button"
                            class=[btn_classes.alter_classes(
                                ClassesOp::Add, "dropdown-toggle"
                            ).get()]
                            data-bs-toggle="dropdown"
                            data-bs-offset=[offset]
                            data-bs-reference=[reference]
                            data-bs-auto-close=[auto_close]
                            aria-expanded="false"
                        {
                            (title)
                        }
                        ul class=[menu_classes.get()] { (items) }
                    }
                } @else {
                    // Sin botón: sólo el listado como menú contextual.
                    ul class="dropdown-menu" { (items) }
                }
            }
        })
    }
}

impl Dropdown {
    // **< Dropdown BUILDER >***********************************************************************

    /// Establece el identificador único (`id`) del menú desplegable.
    #[builder_fn]
    pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
        self.id.alter_id(id);
        self
    }

    /// Modifica la lista de clases CSS aplicadas al menú desplegable.
    #[builder_fn]
    pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
        self.classes.alter_classes(op, classes);
        self
    }

    /// Establece el título del menú desplegable.
    #[builder_fn]
    pub fn with_title(mut self, title: L10n) -> Self {
        self.title = title;
        self
    }

    /// Ajusta el tamaño del botón.
    #[builder_fn]
    pub fn with_button_size(mut self, size: ButtonSize) -> Self {
        self.button_size = size;
        self
    }

    /// Define el color/estilo del botón.
    #[builder_fn]
    pub fn with_button_color(mut self, color: ButtonColor) -> Self {
        self.button_color = color;
        self
    }

    /// Activa/desactiva el modo *split* (botón de acción + *toggle*).
    #[builder_fn]
    pub fn with_button_split(mut self, split: bool) -> Self {
        self.button_split = split;
        self
    }

    /// Indica si el botón del menú está integrado en un grupo de botones.
    #[builder_fn]
    pub fn with_button_grouped(mut self, grouped: bool) -> Self {
        self.button_grouped = grouped;
        self
    }

    /// Establece la política de cierre automático del menú desplegable.
    #[builder_fn]
    pub fn with_auto_close(mut self, auto_close: dropdown::AutoClose) -> Self {
        self.auto_close = auto_close;
        self
    }

    /// Establece la dirección de despliegue del menú.
    #[builder_fn]
    pub fn with_direction(mut self, direction: dropdown::Direction) -> Self {
        self.direction = direction;
        self
    }

    /// Configura la alineación horizontal (con posible comportamiento *responsive* adicional).
    #[builder_fn]
    pub fn with_menu_align(mut self, align: dropdown::MenuAlign) -> Self {
        self.menu_align = align;
        self
    }

    /// Configura la posición del menú.
    #[builder_fn]
    pub fn with_menu_position(mut self, position: dropdown::MenuPosition) -> Self {
        self.menu_position = position;
        self
    }

    /// Añade un nuevo elemento al menú o modifica la lista de elementos del menú con una operación
    /// [`ChildOp`].
    ///
    /// # Ejemplo
    ///
    /// ```rust,ignore
    /// dropdown.with_item(dropdown::Item::link("Opción", "/ruta"));
    /// dropdown.with_item(ChildOp::AddMany(vec![
    ///     dropdown::Item::link(...).into(),
    ///     dropdown::Item::divider().into(),
    ///     dropdown::Item::link(...).into(),
    /// ]));
    /// ```
    #[builder_fn]
    pub fn with_item(mut self, op: impl Into<ChildOp>) -> Self {
        self.items.alter_child(op.into());
        self
    }
}
