//! Definiciones para crear campos de texto de una línea.

use pagetop::prelude::*;

use crate::theme::form;
use crate::LOCALES_BOOTSIER;

use std::fmt;

// **< Kind >***************************************************************************************

/// Tipo de campo para un [`form::input::Field`].
///
/// Determina el tipo de entrada que acepta, así como el comportamiento del navegador al interactuar
/// con el campo. Implícitamente se aplica al crear el control: [`text()`](Field::text),
/// [`password()`](Field::password), [`search()`](Field::search), [`email()`](Field::email),
/// [`telephone()`](Field::telephone) o [`url()`](Field::url).
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum Kind {
    /// Entrada de texto genérico (`type="text"`). Es el tipo por defecto.
    #[default]
    Text,
    /// Entrada de una contraseña (`type="password"`). El contenido aparece enmascarado.
    Password,
    /// Campo de búsqueda (`type="search"`). Es un tipo semántico para los cuadros de búsqueda.
    Search,
    /// Entrada de un correo electrónico (`type="email"`). Permite validar el formato del correo.
    Email,
    /// Entrada de un teléfono (`type="tel"`). Activa el teclado de llamadas en móviles.
    Telephone,
    /// Entrada de una URL (`type="url"`). Comprueba que la entrada sea una URL bien formada.
    Url,
}

impl fmt::Display for Kind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Kind::Text => "text",
            Kind::Password => "password",
            Kind::Search => "search",
            Kind::Email => "email",
            Kind::Telephone => "tel",
            Kind::Url => "url",
        })
    }
}

// **< Mode >***************************************************************************************

/// Sugerencia para el teclado virtual de un [`form::input::Field`].
///
/// Indica al navegador qué tipo de teclado virtual mostrar en dispositivos móviles o táctiles al
/// editar el campo. A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los
/// valores aceptados ni activa la validación del navegador; es sólo una sugerencia de presentación.
///
/// Se establece con [`form::input::Field::with_inputmode()`].
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Mode {
    /// Suprime el teclado virtual. Útil en campos con teclado personalizado basado en JavaScript.
    None,
    /// Teclado de texto genérico.
    Text,
    /// Teclado decimal, con dígitos y separador decimal.
    Decimal,
    /// Teclado numérico, con sólo dígitos.
    Numeric,
    /// Teclado de teléfono, con dígitos y símbolos `+`, `*` y `#`.
    Tel,
    /// Teclado optimizado para búsquedas (puede incluir tecla de búsqueda).
    Search,
    /// Teclado optimizado para correo electrónico (incluye `@` y `.`).
    Email,
    /// Teclado optimizado para URL (incluye `/`, `.` y `.com`).
    Url,
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Mode::None => "none",
            Mode::Text => "text",
            Mode::Decimal => "decimal",
            Mode::Numeric => "numeric",
            Mode::Tel => "tel",
            Mode::Search => "search",
            Mode::Email => "email",
            Mode::Url => "url",
        })
    }
}

// **< Field >**************************************************************************************

/// Componente para crear un **campo de texto de una línea**.
///
/// Renderiza los tipos más habituales en formularios:
///
/// - [`form::input::Field::text()`]: campo de texto genérico (`type="text"`, por defecto).
/// - [`form::input::Field::password()`]: contraseña (`type="password"`).
/// - [`form::input::Field::search()`]: búsqueda (`type="search"`).
/// - [`form::input::Field::email()`]: correo electrónico (`type="email"`).
/// - [`form::input::Field::telephone()`]: teléfono (`type="tel"`).
/// - [`form::input::Field::url()`]: URL (`type="url"`).
///
/// # Ejemplo
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// let email = form::input::Field::email()
///     .with_name("email")
///     .with_label(L10n::n("Email address"))
///     .with_placeholder(L10n::n("user@example.com"))
///     .with_autocomplete(Some(form::Autocomplete::email()))
///     .with_required(true);
/// ```
///
/// Al enviar el formulario el navegador transmite `name=valor`. Un campo de texto siempre envía su
/// valor, incluso si está vacío. En el servidor se deserializa como `String`:
///
/// ```rust,ignore
/// #[derive(serde::Deserialize)]
/// struct FormData {
///     email: String, // Siempre presente; cadena vacía si el usuario no escribió nada.
/// }
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Field {
    #[getters(skip)]
    id: AttrId,
    /// Devuelve las clases CSS del contenedor del campo.
    classes: Classes,
    /// Devuelve el tipo de campo.
    kind: Kind,
    /// Devuelve el nombre del campo.
    name: AttrName,
    /// Devuelve el valor inicial del campo.
    value: AttrValue,
    /// Devuelve la etiqueta del campo.
    label: Attr<L10n>,
    /// Devuelve si la etiqueta se muestra flotante sobre el campo.
    floating_label: bool,
    /// Devuelve el texto de ayuda del campo.
    help_text: Attr<L10n>,
    /// Devuelve la longitud mínima permitida en caracteres.
    minlength: Attr<u16>,
    /// Devuelve la longitud máxima permitida en caracteres.
    maxlength: Attr<u16>,
    /// Devuelve el texto indicativo del campo.
    placeholder: Attr<L10n>,
    /// Devuelve la configuración de autocompletado del campo.
    autocomplete: Attr<form::Autocomplete>,
    /// Devuelve si el campo recibe el foco automáticamente al cargar la página.
    autofocus: bool,
    /// Devuelve si el campo es de sólo lectura.
    readonly: bool,
    /// Devuelve si el campo es obligatorio.
    required: bool,
    /// Devuelve si el campo está deshabilitado.
    disabled: bool,
    /// Devuelve si el campo se muestra como texto plano sin bordes ni fondo.
    plaintext: bool,
    /// Devuelve la sugerencia de teclado virtual para el campo.
    inputmode: Attr<Mode>,
}

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

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

    fn setup(&mut self, _cx: &Context) {
        if *self.floating_label() {
            self.alter_classes(ClassesOp::Prepend, "form-floating");
        }
        self.alter_classes(
            ClassesOp::Prepend,
            util::join!("form-field form-field-", self.kind().to_string()),
        );
    }

    fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
        let container_id = self
            .id()
            .or_else(|| self.name().get().map(|n| util::join!("edit-", n)));
        let input_id = container_id.as_deref().map(|id| util::join!(id, "-input"));
        let input_class = if *self.plaintext() {
            "form-control-plaintext"
        } else {
            "form-control"
        };
        // La etiqueta flotante requiere el atributo `placeholder` para detectar cuándo el campo
        // está vacío y animar la etiqueta; si no está definido, se fuerza `placeholder=""`.
        let placeholder = if *self.floating_label() {
            Some(self.placeholder().lookup(cx).unwrap_or_default())
        } else {
            self.placeholder().lookup(cx)
        };
        let label = match self.label().lookup(cx) {
            Some(text) => html! {
                label for=[input_id.as_deref()] class="form-label" {
                    (text)
                    @if *self.required() {
                        span
                            class="form-required"
                            title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx))
                        {
                            "*"
                        }
                    }
                }
            },
            None => html! {},
        };
        Ok(html! {
            div id=[container_id.as_deref()] class=[self.classes().get()] {
                @if !*self.floating_label() {
                    (label)
                }
                input
                    type=(self.kind())
                    id=[input_id.as_deref()]
                    class=(input_class)
                    name=[self.name().get()]
                    value=[self.value().get()]
                    minlength=[self.minlength().get()]
                    maxlength=[self.maxlength().get()]
                    placeholder=[placeholder]
                    inputmode=[self.inputmode().get()]
                    autocomplete=[self.autocomplete().get()]
                    autofocus[*self.autofocus()]
                    readonly[*self.readonly() || *self.plaintext()]
                    required[*self.required()]
                    disabled[*self.disabled()];
                @if *self.floating_label() {
                    (label)
                }
                @if let Some(description) = self.help_text().lookup(cx) {
                    div class="form-text" { (description) }
                }
            }
        })
    }
}

impl Field {
    /// Crea un campo de **texto genérico** (`type="text"`).
    ///
    /// Es el tipo por defecto. Adecuado para nombres, apellidos, ciudades y cualquier entrada
    /// textual sin restricciones de formato específicas.
    pub fn text() -> Self {
        Self::default()
    }

    /// Crea un campo de **contraseña** (`type="password"`).
    ///
    /// El navegador oculta los caracteres introducidos. Se recomienda usar con
    /// [`with_autocomplete()`](Self::with_autocomplete) para permitir autorrellenar con una
    /// contraseña guardada o dejar al usuario recibir sugerencias o crear una nueva.
    pub fn password() -> Self {
        Self {
            kind: Kind::Password,
            ..Default::default()
        }
    }

    /// Crea un campo de **búsqueda** (`type="search"`).
    ///
    /// Semánticamente equivalente a `text` pero optimizado para búsquedas: algunos
    /// navegadores añaden un botón para borrar el contenido.
    pub fn search() -> Self {
        Self {
            kind: Kind::Search,
            ..Default::default()
        }
    }

    /// Crea un campo de **correo electrónico** (`type="email"`).
    ///
    /// El navegador valida el formato de la dirección antes de enviar el formulario. En
    /// dispositivos móviles muestra un teclado adaptado para introducir direcciones de correo.
    pub fn email() -> Self {
        Self {
            kind: Kind::Email,
            ..Default::default()
        }
    }

    /// Crea un campo de **teléfono** (`type="tel"`).
    ///
    /// No impone ninguna restricción de formato (los formatos de teléfono varían por país), pero
    /// en dispositivos móviles muestra el teclado numérico de llamadas.
    pub fn telephone() -> Self {
        Self {
            kind: Kind::Telephone,
            ..Default::default()
        }
    }

    /// Crea un campo de **URL** (`type="url"`).
    ///
    /// El navegador valida que el valor sea una URL bien formada antes de enviar el formulario.
    pub fn url() -> Self {
        Self {
            kind: Kind::Url,
            ..Default::default()
        }
    }

    // **< Field BUILDER >**************************************************************************

    /// Establece el identificador único (`id`) del contenedor del campo.
    #[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 contenedor del campo.
    #[builder_fn]
    pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
        self.classes.alter_classes(op, classes);
        self
    }

    /// Establece el nombre del campo (atributo `name`).
    ///
    /// Sin él, el valor del campo no se transmite al servidor al enviar el formulario. Para
    /// deserializar el campo en el servidor es recomendable establecer un `name` explícito.
    #[builder_fn]
    pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
        self.name.alter_name(name);
        self
    }

    /// Establece el valor inicial del campo.
    #[builder_fn]
    pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
        self.value.alter_str(value);
        self
    }

    /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla).
    #[builder_fn]
    pub fn with_label(mut self, label: impl Into<Option<L10n>>) -> Self {
        self.label.alter_opt(label.into());
        self
    }

    /// Establece si la etiqueta se muestra flotante sobre el campo.
    ///
    /// Cuando está activo, la etiqueta se superpone al campo y asciende al enfocarlo o cuando tiene
    /// contenido.
    #[builder_fn]
    pub fn with_floating_label(mut self, floating_label: bool) -> Self {
        self.floating_label = floating_label;
        self
    }

    /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo).
    #[builder_fn]
    pub fn with_help_text(mut self, help_text: impl Into<Option<L10n>>) -> Self {
        self.help_text.alter_opt(help_text.into());
        self
    }

    /// Establece la longitud mínima permitida en caracteres (`None` para no imponer mínimo).
    #[builder_fn]
    pub fn with_minlength(mut self, minlength: Option<u16>) -> Self {
        self.minlength.alter_opt(minlength);
        self
    }

    /// Establece la longitud máxima permitida en caracteres (`None` para no imponer límite).
    #[builder_fn]
    pub fn with_maxlength(mut self, maxlength: Option<u16>) -> Self {
        self.maxlength.alter_opt(maxlength);
        self
    }

    /// Establece o elimina el texto indicativo del campo (`None` para quitarlo).
    ///
    /// Este texto aparece en el mismo campo y desaparece en cuanto el usuario empieza a escribir.
    /// Al ser texto visible para el usuario se acepta [`L10n`] para poder localizarlo.
    #[builder_fn]
    pub fn with_placeholder(mut self, placeholder: impl Into<Option<L10n>>) -> Self {
        self.placeholder.alter_opt(placeholder.into());
        self
    }

    /// Establece la configuración de autocompletado del campo.
    ///
    /// Usar los métodos de [`form::Autocomplete`] para los valores más habituales (p. ej.
    /// [`Autocomplete::email()`](form::Autocomplete::email) o
    /// [`Autocomplete::current_password()`](form::Autocomplete::current_password)).
    #[builder_fn]
    pub fn with_autocomplete(mut self, autocomplete: Option<form::Autocomplete>) -> Self {
        self.autocomplete.alter_opt(autocomplete);
        self
    }

    /// Establece si el campo recibe el foco automáticamente al cargar la página.
    #[builder_fn]
    pub fn with_autofocus(mut self, autofocus: bool) -> Self {
        self.autofocus = autofocus;
        self
    }

    /// Establece si el campo es de sólo lectura.
    #[builder_fn]
    pub fn with_readonly(mut self, readonly: bool) -> Self {
        self.readonly = readonly;
        self
    }

    /// Establece si el campo es obligatorio.
    #[builder_fn]
    pub fn with_required(mut self, required: bool) -> Self {
        self.required = required;
        self
    }

    /// Establece si el campo está deshabilitado.
    #[builder_fn]
    pub fn with_disabled(mut self, disabled: bool) -> Self {
        self.disabled = disabled;
        self
    }

    /// Establece si el campo se muestra como texto plano (sin bordes ni fondo).
    ///
    /// Útil para mostrar un valor no editable en pantalla que sí se envía al servidor con el
    /// formulario.
    #[builder_fn]
    pub fn with_plaintext(mut self, plaintext: bool) -> Self {
        self.plaintext = plaintext;
        self
    }

    /// Establece el modo de entrada sugerido para el teclado virtual en dispositivos móviles.
    ///
    /// A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los valores aceptados
    /// ni activa la validación del navegador; es sólo una sugerencia de presentación.
    #[builder_fn]
    pub fn with_inputmode(mut self, inputmode: Option<Mode>) -> Self {
        self.inputmode.alter_opt(inputmode);
        self
    }
}
