//! Predefined constants for HTML tags.

#![allow(non_upper_case_globals)]
#![allow(dead_code)]

use crate::{HtmlTag, property};

pub const a: HtmlTag = HtmlTag::constant("a");
pub const abbr: HtmlTag = HtmlTag::constant("abbr");
pub const address: HtmlTag = HtmlTag::constant("address");
pub const area: HtmlTag = HtmlTag::constant("area");
pub const article: HtmlTag = HtmlTag::constant("article");
pub const aside: HtmlTag = HtmlTag::constant("aside");
pub const audio: HtmlTag = HtmlTag::constant("audio");
pub const b: HtmlTag = HtmlTag::constant("b");
pub const base: HtmlTag = HtmlTag::constant("base");
pub const bdi: HtmlTag = HtmlTag::constant("bdi");
pub const bdo: HtmlTag = HtmlTag::constant("bdo");
pub const blockquote: HtmlTag = HtmlTag::constant("blockquote");
pub const body: HtmlTag = HtmlTag::constant("body");
pub const br: HtmlTag = HtmlTag::constant("br");
pub const button: HtmlTag = HtmlTag::constant("button");
pub const canvas: HtmlTag = HtmlTag::constant("canvas");
pub const caption: HtmlTag = HtmlTag::constant("caption");
pub const cite: HtmlTag = HtmlTag::constant("cite");
pub const code: HtmlTag = HtmlTag::constant("code");
pub const col: HtmlTag = HtmlTag::constant("col");
pub const colgroup: HtmlTag = HtmlTag::constant("colgroup");
pub const data: HtmlTag = HtmlTag::constant("data");
pub const datalist: HtmlTag = HtmlTag::constant("datalist");
pub const dd: HtmlTag = HtmlTag::constant("dd");
pub const del: HtmlTag = HtmlTag::constant("del");
pub const details: HtmlTag = HtmlTag::constant("details");
pub const dfn: HtmlTag = HtmlTag::constant("dfn");
pub const dialog: HtmlTag = HtmlTag::constant("dialog");
pub const div: HtmlTag = HtmlTag::constant("div");
pub const dl: HtmlTag = HtmlTag::constant("dl");
pub const dt: HtmlTag = HtmlTag::constant("dt");
pub const em: HtmlTag = HtmlTag::constant("em");
pub const embed: HtmlTag = HtmlTag::constant("embed");
pub const fieldset: HtmlTag = HtmlTag::constant("fieldset");
pub const figcaption: HtmlTag = HtmlTag::constant("figcaption");
pub const figure: HtmlTag = HtmlTag::constant("figure");
pub const footer: HtmlTag = HtmlTag::constant("footer");
pub const form: HtmlTag = HtmlTag::constant("form");
pub const h1: HtmlTag = HtmlTag::constant("h1");
pub const h2: HtmlTag = HtmlTag::constant("h2");
pub const h3: HtmlTag = HtmlTag::constant("h3");
pub const h4: HtmlTag = HtmlTag::constant("h4");
pub const h5: HtmlTag = HtmlTag::constant("h5");
pub const h6: HtmlTag = HtmlTag::constant("h6");
pub const head: HtmlTag = HtmlTag::constant("head");
pub const header: HtmlTag = HtmlTag::constant("header");
pub const hgroup: HtmlTag = HtmlTag::constant("hgroup");
pub const hr: HtmlTag = HtmlTag::constant("hr");
pub const html: HtmlTag = HtmlTag::constant("html");
pub const i: HtmlTag = HtmlTag::constant("i");
pub const iframe: HtmlTag = HtmlTag::constant("iframe");
pub const img: HtmlTag = HtmlTag::constant("img");
pub const input: HtmlTag = HtmlTag::constant("input");
pub const ins: HtmlTag = HtmlTag::constant("ins");
pub const kbd: HtmlTag = HtmlTag::constant("kbd");
pub const label: HtmlTag = HtmlTag::constant("label");
pub const legend: HtmlTag = HtmlTag::constant("legend");
pub const li: HtmlTag = HtmlTag::constant("li");
pub const link: HtmlTag = HtmlTag::constant("link");
pub const main: HtmlTag = HtmlTag::constant("main");
pub const map: HtmlTag = HtmlTag::constant("map");
pub const mark: HtmlTag = HtmlTag::constant("mark");
pub const menu: HtmlTag = HtmlTag::constant("menu");
pub const meta: HtmlTag = HtmlTag::constant("meta");
pub const meter: HtmlTag = HtmlTag::constant("meter");
pub const nav: HtmlTag = HtmlTag::constant("nav");
pub const noscript: HtmlTag = HtmlTag::constant("noscript");
pub const object: HtmlTag = HtmlTag::constant("object");
pub const ol: HtmlTag = HtmlTag::constant("ol");
pub const optgroup: HtmlTag = HtmlTag::constant("optgroup");
pub const option: HtmlTag = HtmlTag::constant("option");
pub const output: HtmlTag = HtmlTag::constant("output");
pub const p: HtmlTag = HtmlTag::constant("p");
pub const picture: HtmlTag = HtmlTag::constant("picture");
pub const pre: HtmlTag = HtmlTag::constant("pre");
pub const progress: HtmlTag = HtmlTag::constant("progress");
pub const q: HtmlTag = HtmlTag::constant("q");
pub const rp: HtmlTag = HtmlTag::constant("rp");
pub const rt: HtmlTag = HtmlTag::constant("rt");
pub const ruby: HtmlTag = HtmlTag::constant("ruby");
pub const s: HtmlTag = HtmlTag::constant("s");
pub const samp: HtmlTag = HtmlTag::constant("samp");
pub const script: HtmlTag = HtmlTag::constant("script");
pub const search: HtmlTag = HtmlTag::constant("search");
pub const section: HtmlTag = HtmlTag::constant("section");
pub const select: HtmlTag = HtmlTag::constant("select");
pub const slot: HtmlTag = HtmlTag::constant("slot");
pub const small: HtmlTag = HtmlTag::constant("small");
pub const source: HtmlTag = HtmlTag::constant("source");
pub const span: HtmlTag = HtmlTag::constant("span");
pub const strong: HtmlTag = HtmlTag::constant("strong");
pub const style: HtmlTag = HtmlTag::constant("style");
pub const sub: HtmlTag = HtmlTag::constant("sub");
pub const summary: HtmlTag = HtmlTag::constant("summary");
pub const sup: HtmlTag = HtmlTag::constant("sup");
pub const table: HtmlTag = HtmlTag::constant("table");
pub const tbody: HtmlTag = HtmlTag::constant("tbody");
pub const td: HtmlTag = HtmlTag::constant("td");
pub const template: HtmlTag = HtmlTag::constant("template");
pub const textarea: HtmlTag = HtmlTag::constant("textarea");
pub const tfoot: HtmlTag = HtmlTag::constant("tfoot");
pub const th: HtmlTag = HtmlTag::constant("th");
pub const thead: HtmlTag = HtmlTag::constant("thead");
pub const time: HtmlTag = HtmlTag::constant("time");
pub const title: HtmlTag = HtmlTag::constant("title");
pub const tr: HtmlTag = HtmlTag::constant("tr");
pub const track: HtmlTag = HtmlTag::constant("track");
pub const u: HtmlTag = HtmlTag::constant("u");
pub const ul: HtmlTag = HtmlTag::constant("ul");
pub const var: HtmlTag = HtmlTag::constant("var");
pub const video: HtmlTag = HtmlTag::constant("video");
pub const wbr: HtmlTag = HtmlTag::constant("wbr");

// HTML spec § 13.1.2 Elements

/// Whether this is a void tag whose associated element may not have
/// children.
pub fn is_void(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::area
            | self::base
            | self::br
            | self::col
            | self::embed
            | self::hr
            | self::img
            | self::input
            | self::link
            | self::meta
            | self::source
            | self::track
            | self::wbr
    )
}

/// Whether this is a tag containing raw text.
pub fn is_raw(tag: HtmlTag) -> bool {
    matches!(tag, self::script | self::style)
}

/// Whether this is a tag containing escapable raw text.
pub fn is_escapable_raw(tag: HtmlTag) -> bool {
    matches!(tag, self::textarea | self::title)
}

/// Whether this is a foreign tag whose associated element is from another
/// namespace.
///
/// Currently includes MathML tags, but could be extended to SVG.
pub fn is_foreign(tag: HtmlTag) -> bool {
    self::mathml::is_mathml(tag)
}

/// Whether this is a foreign element whose tag is self-closing.
pub fn is_foreign_self_closing(tag: HtmlTag) -> bool {
    matches!(tag, self::mathml::mprescripts | self::mathml::mspace)
}

// HTML spec § 3.2.5.2 Kinds of content

/// Whether an element is considered metadata content.
pub fn is_metadata_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::base
            | self::link
            | self::meta
            | self::noscript
            | self::script
            | self::style
            | self::template
            | self::title
    )
}

/// Wether an element is considered flow content.
pub fn is_flow_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::a
            | self::abbr
            | self::address
            | self::area
            | self::article
            | self::aside
            | self::audio
            | self::b
            | self::bdi
            | self::bdo
            | self::blockquote
            | self::br
            | self::button
            | self::canvas
            | self::cite
            | self::code
            | self::data
            | self::datalist
            | self::del
            | self::details
            | self::dfn
            | self::dialog
            | self::div
            | self::dl
            | self::em
            | self::embed
            | self::fieldset
            | self::figure
            | self::footer
            | self::form
            | self::h1
            | self::h2
            | self::h3
            | self::h4
            | self::h5
            | self::h6
            | self::header
            | self::hgroup
            | self::hr
            | self::i
            | self::iframe
            | self::img
            | self::input
            | self::ins
            | self::kbd
            | self::label
            | self::link
            | self::main
            | self::map
            | self::mark
            | self::mathml::math
            | self::menu
            | self::meta
            | self::meter
            | self::nav
            | self::noscript
            | self::object
            | self::ol
            | self::output
            | self::p
            | self::picture
            | self::pre
            | self::progress
            | self::q
            | self::ruby
            | self::s
            | self::samp
            | self::script
            | self::search
            | self::section
            | self::select
            | self::slot
            | self::small
            | self::span
            | self::strong
            | self::sub
            | self::sup
            | self::table
            | self::template
            | self::textarea
            | self::time
            | self::u
            | self::ul
            | self::var
            | self::video
            | self::wbr
    )
}

/// Whether an element is considered sectioning content.
pub fn is_sectioning_content(tag: HtmlTag) -> bool {
    matches!(tag, self::article | self::aside | self::nav | self::section)
}

/// Whether an element is considered heading content.
pub fn is_heading_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::hgroup
    )
}

/// Whether an element is considered phrasing content.
pub fn is_phrasing_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::a
            | self::abbr
            | self::area
            | self::audio
            | self::b
            | self::bdi
            | self::bdo
            | self::br
            | self::button
            | self::canvas
            | self::cite
            | self::code
            | self::data
            | self::datalist
            | self::del
            | self::dfn
            | self::em
            | self::embed
            | self::i
            | self::iframe
            | self::img
            | self::input
            | self::ins
            | self::kbd
            | self::label
            | self::link
            | self::map
            | self::mark
            | self::mathml::math
            | self::meta
            | self::meter
            | self::noscript
            | self::object
            | self::output
            | self::picture
            | self::progress
            | self::q
            | self::ruby
            | self::s
            | self::samp
            | self::script
            | self::select
            | self::slot
            | self::small
            | self::span
            | self::strong
            | self::sub
            | self::sup
            | self::template
            | self::textarea
            | self::time
            | self::u
            | self::var
            | self::video
            | self::wbr
    )
}

/// Whether an element is considered embedded content.
pub fn is_embedded_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::audio
            | self::canvas
            | self::embed
            | self::iframe
            | self::img
            | self::mathml::math
            | self::object
            | self::picture
            | self::video
    )
}

/// Whether an element is considered interactive content.
pub fn is_interactive_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::a
            | self::audio
            | self::button
            | self::details
            | self::embed
            | self::iframe
            | self::img
            | self::input
            | self::label
            | self::select
            | self::textarea
            | self::video
    )
}

/// Whether an element is considered palpable content.
pub fn is_palpable_content(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::a
            | self::abbr
            | self::address
            | self::article
            | self::aside
            | self::audio
            | self::b
            | self::bdi
            | self::bdo
            | self::blockquote
            | self::button
            | self::canvas
            | self::cite
            | self::code
            | self::data
            | self::del
            | self::details
            | self::dfn
            | self::div
            | self::dl
            | self::em
            | self::embed
            | self::fieldset
            | self::figure
            | self::footer
            | self::form
            | self::h1
            | self::h2
            | self::h3
            | self::h4
            | self::h5
            | self::h6
            | self::header
            | self::hgroup
            | self::i
            | self::iframe
            | self::img
            | self::input
            | self::ins
            | self::kbd
            | self::label
            | self::main
            | self::map
            | self::mark
            | self::mathml::math
            | self::menu
            | self::meter
            | self::nav
            | self::object
            | self::ol
            | self::output
            | self::p
            | self::picture
            | self::pre
            | self::progress
            | self::q
            | self::ruby
            | self::s
            | self::samp
            | self::search
            | self::section
            | self::select
            | self::small
            | self::span
            | self::strong
            | self::sub
            | self::sup
            | self::table
            | self::textarea
            | self::time
            | self::u
            | self::ul
            | self::var
            | self::video
    )
}

/// Whether an element is considered a script-supporting element.
pub fn is_script_supporting_element(tag: HtmlTag) -> bool {
    matches!(tag, self::script | self::template)
}

// § 15.4 Replaced elements

/// Whether the element is considered a "replaced element".
///
/// See:
/// - § 15.4 Replaced elements
/// - <https://www.w3.org/TR/css-display-3/#replaced-element>
pub fn is_replaced(tag: HtmlTag) -> bool {
    matches!(
        tag,
        self::audio
            | self::canvas
            | self::embed
            | self::iframe
            | self::img
            | self::input
            | self::object
            | self::video
    )
}

// Non-standard sets.

/// Whether HTML whitespace next to an element with this tag will be collapsed
/// (assuming normal user agent styles).
pub fn is_whitespace_collapsing(tag: HtmlTag) -> bool {
    // TODO: Reconsider this check. What about e.g. tables?
    property::Display::default_for(tag) == Some(property::Display::Block)
        || tag == self::br
}

/// Whether we group this kind of HTML element into paragraphs _if_ paragraphs
/// are forced in some way (either through being top-level or due to being in
/// the same flow as a Typst-native `block` element or `parbreak`). When using
/// solely the low-level HTML API, no paragraphs are ever forced.
///
/// The maximal set of tags we can choose here is HTML "phrasing content" as
/// that is the content model of `<p>`. Most elements that are phrasing content
/// are also reasonable to group into paragraphs. However, it's a bit more
/// subtle:
///
/// - A few elements that are phrasing content are `display: none` through the
///   user agent style sheet, e.g. `<script>`. These are called hidden content.
///   It does not make sense to group them into paragraphs as they are not
///   visible and also not really used in prose. We exclude those.
///
/// - A few elements that are phrasing content are actually kind of "generic".
///   They can be used in paragraphs and may then also only contain phrasing
///   content or they may be used directly in the block flow and then they may
///   also include arbitrary flow content. This includes the `<a>`, `<ins>`,
///   `<del>`, `<noscript>`, and `<map>` elements, which have a "transparent"
///   content model.
///
///   It would make sense for these elements to group into paragraphs only if
///   their contents themselves are fully groupable. This would require
///   realizing their bodies to check what they contain, in a way that is not
///   unlike measurement in layouting. We currently unconditionally group these
///   elements, but we might want to do the smarter thing in the future.
///
///   There are a few more elements that have a "transparent" content model, but
///   their contents are only used as fallback content. This includes `<video>`,
///   `<audio>`, `<canvas>`, `<object>`, and `<slot>`. Sacrificing consistency
///   of the multi-media elements with `<img>` to make their grouping behavior
///   depend on their _fallback_ body seems not so useful.
///
/// Finally, note that we don't impose Typst's ideas about blockiness here. For
/// example, `<img>` is included here even though Typst's `image` function is
/// block-level by default. Instead the built-in `image` show rule wraps the
/// produced `<img>` in a Typst `block`. With `box` and `block`, it's always
/// possible to override the default defined by this function.
pub fn should_group_into_pars(tag: HtmlTag) -> bool {
    is_phrasing_content(tag)
        && property::Display::default_for(tag) != Some(property::Display::None)
}

/// Elements in the MathML namespace.
///
/// Only the MathML Core elements at the moment.
pub mod mathml {
    use super::HtmlTag;

    pub const annotation: HtmlTag = HtmlTag::constant("annotation");
    pub const annotation_xml: HtmlTag = HtmlTag::constant("annotation-xml");
    pub const maction: HtmlTag = HtmlTag::constant("maction");
    pub const math: HtmlTag = HtmlTag::constant("math");
    pub const merror: HtmlTag = HtmlTag::constant("merror");
    pub const mfrac: HtmlTag = HtmlTag::constant("mfrac");
    pub const mi: HtmlTag = HtmlTag::constant("mi");
    pub const mmultiscripts: HtmlTag = HtmlTag::constant("mmultiscripts");
    pub const mn: HtmlTag = HtmlTag::constant("mn");
    pub const mo: HtmlTag = HtmlTag::constant("mo");
    pub const mover: HtmlTag = HtmlTag::constant("mover");
    pub const mpadded: HtmlTag = HtmlTag::constant("mpadded");
    pub const mphantom: HtmlTag = HtmlTag::constant("mphantom");
    pub const mprescripts: HtmlTag = HtmlTag::constant("mprescripts");
    pub const mroot: HtmlTag = HtmlTag::constant("mroot");
    pub const mrow: HtmlTag = HtmlTag::constant("mrow");
    pub const ms: HtmlTag = HtmlTag::constant("ms");
    pub const mspace: HtmlTag = HtmlTag::constant("mspace");
    pub const msqrt: HtmlTag = HtmlTag::constant("msqrt");
    pub const mstyle: HtmlTag = HtmlTag::constant("mstyle");
    pub const msub: HtmlTag = HtmlTag::constant("msub");
    pub const msubsup: HtmlTag = HtmlTag::constant("msubsup");
    pub const msup: HtmlTag = HtmlTag::constant("msup");
    pub const mtable: HtmlTag = HtmlTag::constant("mtable");
    pub const mtd: HtmlTag = HtmlTag::constant("mtd");
    pub const mtext: HtmlTag = HtmlTag::constant("mtext");
    pub const mtr: HtmlTag = HtmlTag::constant("mtr");
    pub const munder: HtmlTag = HtmlTag::constant("munder");
    pub const munderover: HtmlTag = HtmlTag::constant("munderover");
    pub const semantics: HtmlTag = HtmlTag::constant("semantics");

    /// Whether this is a MathML token element.
    pub fn is_token(tag: HtmlTag) -> bool {
        matches!(
            tag,
            self::mtext | self::mi | self::mn | self::mo | self::mspace | self::ms
        )
    }

    /// Whether this tag is an element in the MathML namespace.
    pub fn is_mathml(tag: HtmlTag) -> bool {
        matches!(
            tag,
            self::annotation
                | self::annotation_xml
                | self::maction
                | self::math
                | self::merror
                | self::mfrac
                | self::mi
                | self::mmultiscripts
                | self::mn
                | self::mo
                | self::mover
                | self::mpadded
                | self::mphantom
                | self::mprescripts
                | self::mroot
                | self::mrow
                | self::ms
                | self::mspace
                | self::msqrt
                | self::mstyle
                | self::msub
                | self::msubsup
                | self::msup
                | self::mtable
                | self::mtd
                | self::mtext
                | self::mtr
                | self::munder
                | self::munderover
                | self::semantics
        )
    }
}
