//! Types for HTML attributes.

use std::collections::{btree_map, BTreeMap};
use std::iter::FromIterator;
use std::ops::{Index, Range};

use crate::offset::Offset;

/// A map of HTML attributes.
///
/// Does not preserve the order of attributes.
/// Iterating always yields attributes in order by name.
///
/// # Example
///
/// ```
/// # use html5tokenizer::attr::AttributeMap;
/// let attrs: AttributeMap<()> = vec![("href".into(), "http://example.com".into())]
///     .into_iter()
///     .collect();
/// assert_eq!(&attrs["href"], "http://example.com");
/// ```
#[derive(Debug, Default, PartialEq, Eq)]
pub struct AttributeMap<O> {
    pub(crate) inner: BTreeMap<String, AttrInternal<O>>,
}

/// The value type internally used by the [`AttributeMap`].
/// Not part of the public API.
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct AttrInternal<O> {
    pub value: String,
    /// The span of the attribute name.
    pub name_span: Range<O>,
    /// The span of the attribute value.
    /// For the empty attribute syntax this is just `O::default()..O::default()`.
    /// We intentionally don't use `Option<Range<O>>` here to spare us a byte (and padding) per attribute.
    pub value_span: Range<O>,
    pub value_syntax: Option<AttrValueSyntax>,
}

/// The syntax of the attribute value.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AttrValueSyntax {
    /// An unquoted attribute value, e.g. `id=foo`.
    Unquoted,
    /// A single-quoted attribute value, e.g. `id='foo'`.
    SingleQuoted,
    /// A double-quoted attribute value, e.g. `id="foo"`.
    DoubleQuoted,
}

/// An HTML attribute borrowed from an [`AttributeMap`].
#[derive(Debug, Eq, PartialEq)]
pub struct Attribute<'a, O> {
    name: &'a str,
    map_val: &'a AttrInternal<O>,
}

/// An owned HTML attribute.
#[derive(Debug, PartialEq, Eq)]
pub struct AttributeOwned<O> {
    /// The attribute name.
    /// Uppercase ASCII characters (A-Z) have been converted to lowercase.
    pub name: String,
    /// The attribute value. Character references have been resolved.
    pub value: String,
    /// The span of the attribute name.
    pub name_span: Range<O>,
    /// The span of the attribute value.
    /// `None` in case of the empty attribute syntax (e.g. `disabled` in `<input disabled>`).
    pub value_span: Option<Range<O>>,
    /// The syntax of the attribute value.
    /// `None` indicates the empty attribute syntax (e.g. `disabled` in `<input disabled>`).
    pub value_syntax: Option<AttrValueSyntax>,
}

impl<O> AttributeMap<O> {
    /// Returns the attribute with the given name.
    ///
    /// The name must not contain any uppercase ASCII character (A-Z)
    /// or the method will always return `None`.
    pub fn get(&self, name: &str) -> Option<Attribute<O>> {
        self.inner
            .get_key_value(name)
            .map(|(name, map_val)| Attribute { name, map_val })
    }
}

impl<'a, O: Offset> Attribute<'a, O> {
    /// Returns the attribute name.
    /// Uppercase ASCII characters (A-Z) have been converted to lowercase.
    pub fn name(&self) -> &'a str {
        self.name
    }

    /// Returns the attribute value. Character references have been resolved.
    pub fn value(&self) -> &'a str {
        &self.map_val.value
    }

    /// Returns the span of the attribute name.
    pub fn name_span(&self) -> Range<O> {
        self.map_val.name_span.clone()
    }

    /// For explicitly defined values returns the span of the attribute value.
    ///
    /// Returns `None` for attributes using the empty attribute syntax (e.g. `disabled` in `<input disabled>`).
    pub fn value_span(&self) -> Option<Range<O>> {
        if self.map_val.value_syntax.is_none() {
            return None;
        }
        Some(self.map_val.value_span.clone())
    }

    /// Returns the attribute value syntax in case the value is explicitly defined.
    ///
    /// Returns `None` for attributes using the empty attribute syntax (e.g. `disabled` in `<input disabled>`).
    pub fn value_syntax(&self) -> Option<AttrValueSyntax> {
        self.map_val.value_syntax
    }
}

// We cannot impl Index<Output=Attribute> because Index::index returns a reference of
// the Output type (and you cannot return a value referencing a temporary value).
impl<O> Index<&str> for AttributeMap<O> {
    type Output = str;

    /// Returns the attribute value with the given name.
    ///
    /// The name must not contain any uppercase ASCII character (A-Z)
    /// or the method will always panic.
    fn index(&self, name: &str) -> &Self::Output {
        &self.inner[name].value
    }
}

impl<O> IntoIterator for AttributeMap<O> {
    type Item = AttributeOwned<O>;

    type IntoIter = AttrIntoIter<O>;

    fn into_iter(self) -> Self::IntoIter {
        AttrIntoIter(self.inner.into_iter())
    }
}

/// A consuming iterator over the attributes of an [`AttributeMap`].
pub struct AttrIntoIter<O>(btree_map::IntoIter<String, AttrInternal<O>>);

impl<O> Iterator for AttrIntoIter<O> {
    type Item = AttributeOwned<O>;

    fn next(&mut self) -> Option<Self::Item> {
        let (name, map_val) = self.0.next()?;
        Some(AttributeOwned {
            name,
            value: map_val.value,
            name_span: map_val.name_span,
            value_span: map_val.value_syntax.is_some().then_some(map_val.value_span),
            value_syntax: map_val.value_syntax,
        })
    }
}

impl<'a, O> IntoIterator for &'a AttributeMap<O> {
    type Item = Attribute<'a, O>;

    type IntoIter = AttrIter<'a, O>;

    fn into_iter(self) -> Self::IntoIter {
        AttrIter(self.inner.iter())
    }
}

/// A borrowed iterator over the attributes of an [`AttributeMap`].
pub struct AttrIter<'a, S>(btree_map::Iter<'a, String, AttrInternal<S>>);

impl<'a, S> Iterator for AttrIter<'a, S> {
    type Item = Attribute<'a, S>;

    fn next(&mut self) -> Option<Self::Item> {
        let (name, map_val) = self.0.next()?;
        Some(Attribute { name, map_val })
    }
}

impl<O: Default> FromIterator<(String, String)> for AttributeMap<O> {
    fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
        Self {
            inner: iter
                .into_iter()
                .map(|(name, value)| {
                    (
                        name,
                        AttrInternal {
                            value,
                            name_span: O::default()..O::default(),
                            value_span: O::default()..O::default(),
                            value_syntax: Some(AttrValueSyntax::DoubleQuoted),
                        },
                    )
                })
                .collect(),
        }
    }
}