use std::cell::Cell;
use std::sync::LazyLock;

use codex::styling::{MathStyle, to_style};
use ecow::EcoString;
use typst_syntax::{Span, split_newlines};
use typst_utils::{LazyHash, SliceExt};
use unicode_segmentation::UnicodeSegmentation;

use super::item::*;
use super::multiline::{AlignedRow, expand_multiline_fence};
use super::process::{GroupResult, process_group, process_table_cell};
use crate::diag::{SourceResult, bail, warning};
use crate::engine::Engine;
use crate::foundations::{
    Content, Packed, Style, StyleChain, Styles, SymbolElem, TargetElem,
};
use crate::introspection::{Locator, SplitLocator, TagElem};
use crate::layout::{Abs, Axes, BoxElem, FixedAlignment, HElem, Ratio, Rel, Spacing};
use crate::math::*;
use crate::routines::{Arenas, RealizationKind};
use crate::text::{
    BottomEdge, BottomEdgeMetric, LinebreakElem, SpaceElem, TextElem, TopEdge,
    TopEdgeMetric, is_default_ignorable,
};
use crate::visualize::FixedStroke;

/// Base styles chained to the style chain for text items.
static TEXT_BASE_LOCAL_STYLES: LazyLock<[LazyHash<Style>; 3]> = LazyLock::new(|| {
    [
        TextElem::top_edge.set(TopEdge::Metric(TopEdgeMetric::Bounds)),
        TextElem::bottom_edge.set(BottomEdge::Metric(BottomEdgeMetric::Bounds)),
        TextElem::overhang.set(false),
    ]
    .map(|p| p.wrap())
});

/// The math IR builder.
pub(crate) struct MathResolver<'a, 'v, 'e> {
    /// External engine.
    engine: &'v mut Engine<'e>,
    /// External locator.
    locator: SplitLocator<'a>,
    /// External arenas for bump allocation.
    arenas: &'a Arenas,
    /// The working buffer of resolved items.
    items: Vec<RawMathItem<'a>>,
}

impl<'a, 'v, 'e> MathResolver<'a, 'v, 'e> {
    /// Create a new math builder.
    pub(crate) fn new(
        engine: &'v mut Engine<'e>,
        locator: Locator<'a>,
        arenas: &'a Arenas,
    ) -> Self {
        Self {
            engine,
            locator: locator.split(),
            arenas,
            items: vec![],
        }
    }

    /// Lifetime-extends some styles.
    fn store_styles(&self, styles: impl Into<Styles>) -> &'a Styles {
        self.arenas.styles.alloc(styles.into())
    }

    /// Lifetime-extends a style chain and chains a style onto it.
    fn chain_styles(
        &self,
        base: StyleChain<'a>,
        new: impl Into<Styles>,
    ) -> StyleChain<'a> {
        self.store_chain(base).chain(self.store_styles(new))
    }

    /// Lifetime-extends some style chain.
    fn store_chain<'c>(&self, styles: StyleChain<'c>) -> &'a StyleChain<'c> {
        self.arenas.bump.alloc(styles)
    }

    /// Lifetime-extends some content.
    fn store(&self, content: Content) -> &'a Content {
        self.arenas.content.alloc(content)
    }

    /// Push an item.
    fn push(&mut self, item: impl Into<RawMathItem<'a>>) {
        self.items.push(item.into());
    }

    /// Resolve the given element and return the start index of the resulting
    /// [`RawMathItem`]s.
    fn resolve_into_items(
        &mut self,
        elem: &'a Content,
        styles: StyleChain<'a>,
    ) -> SourceResult<usize> {
        let start = self.items.len();
        self.resolve_into_self(elem, styles)?;
        Ok(start)
    }

    /// Resolve the given element and return the result as a [`MathItem`].
    pub(crate) fn resolve_into_item(
        &mut self,
        elem: &'a Content,
        styles: StyleChain<'a>,
    ) -> SourceResult<MathItem<'a>> {
        let start = self.resolve_into_items(elem, styles)?;
        let len = self.items.len() - start;

        // Don't emit standalone linebreaks or alignment points.
        if len == 1 && matches!(self.items.last(), Some(RawMathItem::Item(_))) {
            return Ok(self.items.pop().unwrap().into_item().unwrap());
        }

        Ok(match process_group(self.items.drain(start..), styles, false, true) {
            GroupResult::Multiline(rows) => MultilineItem::create(rows, styles),
            GroupResult::Flat(items) => MathItem::wrap(items, styles),
        })
    }

    /// Resolve arbitrary content.
    fn resolve_into_self(
        &mut self,
        content: &'a Content,
        styles: StyleChain<'a>,
    ) -> SourceResult<()> {
        let pairs = (self.engine.library.routines.realize)(
            RealizationKind::Math,
            self.engine,
            &mut self.locator,
            self.arenas,
            content,
            styles,
        )?;

        for (elem, styles) in pairs {
            resolve_realized(elem, self, styles)?;
        }

        Ok(())
    }
}

/// Resolves a leaf element resulting from realization.
fn resolve_realized<'a, 'v, 'e>(
    elem: &'a Content,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    if let Some(elem) = elem.to_packed::<SymbolElem>() {
        resolve_symbol(elem, ctx, styles)?;
    } else if elem.is::<SpaceElem>() {
        ctx.push(MathItem::Space);
    } else if let Some(elem) = elem.to_packed::<TextElem>() {
        resolve_text(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<AttachElem>() {
        resolve_attach(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<LrElem>() {
        resolve_lr(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<OpElem>() {
        resolve_op(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<HElem>() {
        resolve_h(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<OverlineElem>() {
        resolve_overline(elem, ctx, styles)?;
    } else if elem.is::<AlignPointElem>() {
        ctx.push(RawMathItem::Align);
    } else if let Some(elem) = elem.to_packed::<PrimesElem>() {
        resolve_primes(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<TagElem>() {
        ctx.push(MathItem::Tag(elem.tag.clone()));
    } else if let Some(elem) = elem.to_packed::<ClassElem>() {
        resolve_class(elem, ctx, styles)?;
    } else if elem.is::<LinebreakElem>() {
        ctx.push(RawMathItem::Linebreak);
    } else if let Some(elem) = elem.to_packed::<FracElem>() {
        resolve_frac(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<AccentElem>() {
        resolve_accent(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<LimitsElem>() {
        resolve_limits(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<StretchElem>() {
        resolve_stretch(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<RootElem>() {
        resolve_root(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<BoxElem>() {
        let locator = ctx.locator.next(&elem.span());
        ctx.push(BoxItem::create(elem, styles, locator));
    } else if let Some(elem) = elem.to_packed::<MatElem>() {
        resolve_mat(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<MidElem>() {
        resolve_mid(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<UnderlineElem>() {
        resolve_underline(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<CasesElem>() {
        resolve_cases(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<UnderbraceElem>() {
        resolve_underbrace(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<ScriptsElem>() {
        resolve_scripts(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<CancelElem>() {
        resolve_cancel(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<UnderbracketElem>() {
        resolve_underbracket(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<VecElem>() {
        resolve_vec(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<OverbraceElem>() {
        resolve_overbrace(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<BinomElem>() {
        resolve_binom(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<OverbracketElem>() {
        resolve_overbracket(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<UnderparenElem>() {
        resolve_underparen(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<OverparenElem>() {
        resolve_overparen(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<UndershellElem>() {
        resolve_undershell(elem, ctx, styles)?;
    } else if let Some(elem) = elem.to_packed::<OvershellElem>() {
        resolve_overshell(elem, ctx, styles)?;
    } else if let Some(body) =
        (ctx.engine.library.routines.html_mathml_body)(elem, styles)
    {
        resolve_mathml(elem, body, ctx, styles)?;
    } else {
        let locator = ctx.locator.next(&elem.span());
        ctx.push(ExternalItem::create(elem, styles, locator));
    }
    Ok(())
}

/// Resolves a MathML HTML element.
fn resolve_mathml<'a, 'v, 'e>(
    elem: &'a Content,
    body: Option<&'a Content>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let body = if styles.get(TargetElem::target).is_html() {
        body.map(|body| ctx.resolve_into_item(body, styles)).transpose()?
    } else {
        None
    };
    ctx.push(MathmlItem::create(elem, body, styles));
    Ok(())
}

/// Resolves horizontal spacing.
fn resolve_h(
    elem: &Packed<HElem>,
    ctx: &mut MathResolver,
    styles: StyleChain,
) -> SourceResult<()> {
    if let Spacing::Rel(rel) = elem.amount
        && rel.rel.is_zero()
    {
        ctx.push(MathItem::Spacing(
            rel.abs,
            styles.resolve(TextElem::size),
            elem.weak.get(styles),
        ));
    }
    Ok(())
}

/// Resolves text content.
fn resolve_text<'a, 'v, 'e>(
    elem: &Packed<TextElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let variant = styles.get(EquationElem::variant);
    let bold = styles.get(EquationElem::bold);
    // Disable auto-italic.
    let italic = styles.get(EquationElem::italic).or(Some(false));

    // Create item with correct styles and properties.
    let local_styles = ctx.store_chain(styles).chain(&*TEXT_BASE_LOCAL_STYLES);
    let mut create_item = |text: &str| {
        let num = text.chars().all(|c| c.is_ascii_digit() || c == '.');
        let styled_text: EcoString = text
            .chars()
            .flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
            .collect();
        if num {
            NumberItem::create(styled_text, styles, elem.span())
        } else {
            TextItem::create(
                styled_text,
                local_styles,
                elem.span(),
                ctx.locator.next(&elem.span()),
            )
        }
    };

    // Strip a single trailing newline.
    let mut lines = split_newlines(&elem.text);
    lines.pop_if(|x| x.is_empty());

    let item = if let [text] = lines.as_slice() {
        create_item(text)
    } else {
        let rows = lines
            .into_iter()
            .map(|line| AlignedRow::new(vec![create_item(line)]))
            .collect();
        MultilineItem::create(rows, styles).with_multiline_centering()
    };

    ctx.push(item);
    Ok(())
}

/// Resolves a symbol element into glyph items.
///
/// Each grapheme cluster in the symbol becomes a separate glyph item.
fn resolve_symbol<'a, 'v, 'e>(
    elem: &'a Packed<SymbolElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let variant = styles.get(EquationElem::variant);
    let bold = styles.get(EquationElem::bold);
    let italic = styles.get(EquationElem::italic);
    for cluster in elem.text.graphemes(true) {
        if cluster.chars().all(is_default_ignorable) {
            continue;
        }

        let text: EcoString = cluster
            .chars()
            .flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
            .collect();

        let item = GlyphItem::create(text, styles, elem.span());

        if item.class() == MathClass::Large && item.size().unwrap() == MathSize::Display {
            let target = Rel::new(Ratio::one(), Abs::zero());
            let stretch = Stretch::new().with_y(StretchInfo::new(target, Em::zero()));
            item.replace_stretch(stretch);
        }

        ctx.push(item);
    }
    Ok(())
}

/// Resolves an accent element.
///
/// The base is resolved in cramped style if the accent is above.
fn resolve_accent<'a, 'v, 'e>(
    elem: &'a Packed<AccentElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let accent = elem.accent;
    let position = if accent.is_bottom() { Position::Below } else { Position::Above };

    let mut new_styles = Styles::new();
    if position == Position::Above {
        new_styles.apply(style_cramped().into());

        // Try to replace the base glyph with its dotless variant.
        if elem.dotless.get(styles) {
            new_styles.apply(style_dtls().into());
        }
    }

    let base_styles = ctx.chain_styles(styles, new_styles);
    let base = ctx.resolve_into_item(&elem.base, base_styles)?;

    let accent = ctx.store(SymbolElem::packed(accent.0).spanned(elem.span()));
    let mut accent = ctx.resolve_into_item(accent, styles)?;
    accent.set_class(MathClass::Diacritic);

    let width = elem.size.resolve(styles);
    accent.set_stretch(Stretch::new().with_x(StretchInfo::new(width, ACCENT_SHORT_FALL)));

    ctx.push(AccentItem::create(
        base,
        accent,
        position,
        elem.dotless.get(styles),
        false,
        styles,
    ));
    Ok(())
}

/// Resolves an attach element.
///
/// Deals with primes, merges nested attachements, and decides the final
/// positioning based on limits/scripts.
fn resolve_attach<'a, 'v, 'e>(
    elem: &'a Packed<AttachElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let outer_attachments = &mut [const { AttachmentList::End }; 6];
    let item = resolve_inner_attach(elem, outer_attachments, false, ctx, styles)?;
    ctx.push(item);
    Ok(())
}

/// This is a mutable stack-allocated linked list used for attachment merging.
///
/// Consider: `attach(attach(attach(a, t: 1), b: 2), t: 3, b: 4)`.
/// We would like this to become `attach(attach(a, t: 1, b: 2), t: 3, b: 4)`.
///
/// However if we only consider attachments in adjacent levels, we won't merge
/// `b:4` inward because `b:2` is already present (but we will merge `t:3` in).
/// But at the next step `b:2` would merge inwards, leaving us with the sorry
/// state of `attach(attach(attach(a, t: 1, b: 2), t: 3), b: 4)` with `t:3` and
/// `b:4` separated, despite originally starting at the same level!
///
/// So for correct handling we need a proper list of attachments, for which I am
/// using a stack-allocated linked list to avoid allocations and because it's
/// relatively straightforward.
///
/// Unfortunately, this does require the complication of interior mutability,
/// i.e. the `Cell` type, due to lifetime variance. See the linked section of
/// "Learning Rust With Entirely Too Many Linked Lists" for more explanation:
/// <https://rust-unofficial.github.io/too-many-lists/infinity-stack-allocated.html>
enum AttachmentList<'a> {
    Node {
        /// We use `Option<Content>` instead of just `Content` to allow for the
        /// "unmerging" operation used by primes in tr position.
        data: Cell<Option<Content>>,
        outer: &'a AttachmentList<'a>,
    },
    End,
}

impl<'a> AttachmentList<'a> {
    fn inner(content: Option<Content>, outer: &'a Self) -> Self {
        fn merge_inward(list: &AttachmentList) -> Option<Content> {
            match list {
                AttachmentList::Node { data, outer } => data.replace(merge_inward(outer)),
                AttachmentList::End => None,
            }
        }
        let data = content.or_else(|| merge_inward(outer));
        AttachmentList::Node { data: Cell::new(data), outer }
    }

    fn unmerge(mut self: &Self, content: Content) {
        let mut data = Some(content);
        while data.is_some()
            && let AttachmentList::Node { data: outer_data, outer } = self
        {
            data = outer_data.replace(data);
            self = outer;
        }
        assert!(data.is_none(), "only called if we previously merged inward");
    }
}

/// Recursively resolve the base of an `AttachElem`, merging outer attachments
/// inwards.
fn resolve_inner_attach<'a, 'v, 'e>(
    elem: &'a Packed<AttachElem>,
    outer_attachments: &[AttachmentList; 6],
    outer_t_inside_tr: bool,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<MathItem<'a>> {
    // Lifetime-extend the super/subscript styles.
    let bumped_styles = ctx.store_chain(styles);
    let sup_style = ctx.store_styles(style_for_superscript(styles));
    let sup_style_chain = bumped_styles.chain(sup_style);
    let sub_style = ctx.store_styles(style_for_subscript(styles));
    let sub_style_chain = bumped_styles.chain(sub_style);

    // Get the merged attachments without resolving them to items yet.
    let (no_inner_tr, t_inside_tr);
    let attachments = {
        let [o_t, o_b, o_tl, o_tr, o_bl, o_br] = outer_attachments;

        let tl = AttachmentList::inner(elem.tl.get_cloned(sup_style_chain), o_tl);
        // We remember the status of t and tr for correct prime handling below.
        let elem_t = elem.t.get_cloned(sup_style_chain);
        let elem_tr = elem.tr.get_cloned(sup_style_chain);
        no_inner_tr = elem_tr.is_none();
        t_inside_tr = no_inner_tr && (elem_t.is_some() || outer_t_inside_tr);
        let t = AttachmentList::inner(elem_t, o_t);
        let tr = AttachmentList::inner(elem_tr, o_tr);

        let b = AttachmentList::inner(elem.b.get_cloned(sub_style_chain), o_b);
        let bl = AttachmentList::inner(elem.bl.get_cloned(sub_style_chain), o_bl);
        let br = AttachmentList::inner(elem.br.get_cloned(sub_style_chain), o_br);

        [t, b, tl, tr, bl, br]
    };

    // Extract from a nested EquationElem.
    let mut base_elem = &elem.base;
    while let Some(equation) = base_elem.to_packed::<EquationElem>() {
        base_elem = &equation.body;
    }

    // Resolve the base and recursively merge outer attachments inwards.
    let base = if let Some(base_elem) = base_elem.to_packed::<AttachElem>() {
        resolve_inner_attach(base_elem, &attachments, t_inside_tr, ctx, styles)?
    } else {
        ctx.resolve_into_item(base_elem, styles)?
    };

    // We can now take our inner attachments back out of the list.
    let [t, b, tl, tr, bl, br] = attachments.map(|a| match a {
        AttachmentList::Node { data: content, outer: _ } => content.into_inner(),
        AttachmentList::End => None,
    });

    // If we're not actually attaching anything, just return the base.
    if [&t, &b, &tl, &tr, &bl, &br].iter().all(|a| a.is_none()) {
        return Ok(base);
    }

    // Apply limits, potentially joining top-right primes with the top attachment.
    let limits = base.limits().active(styles);
    let (t, tr) = match (t, tr) {
        (Some(t), Some(tr)) if !limits => {
            let primed = tr.is::<PrimesElem>();
            if primed && no_inner_tr && t_inside_tr {
                // If we brought a primed tr inward, yield it back to the outer
                // attachment to avoid inverting the order with `tr + t`.
                // And note that `t + tr` would just look bad.
                outer_attachments[3].unmerge(tr);
                (None, Some(t))
            } else if primed {
                // This makes `x'^2` look better than `x^'^2`.
                (None, Some(tr + t))
            } else {
                (Some(t), Some(tr))
            }
        }
        (Some(t), None) if !limits => (None, Some(t)),
        (t, tr) => (t, tr),
    };
    let (b, br) = if limits || br.is_some() { (b, br) } else { (None, b) };

    // Finally, resolve the attachments themselves from content to items.
    macro_rules! layout {
        ($content:ident, $style_chain:ident) => {
            $content
                .map(|elem| ctx.resolve_into_item(ctx.store(elem), $style_chain))
                .transpose()
        };
    }

    let top = layout!(t, sup_style_chain)?;
    let bottom = layout!(b, sub_style_chain)?;
    let top_left = layout!(tl, sup_style_chain)?;
    let bottom_left = layout!(bl, sub_style_chain)?;
    let top_right = layout!(tr, sup_style_chain)?;
    let bottom_right = layout!(br, sub_style_chain)?;

    Ok(ScriptsItem::create(
        base,
        top,
        bottom,
        top_left,
        bottom_left,
        top_right,
        bottom_right,
        styles,
    ))
}

/// Resolves grouped primes.
fn resolve_primes<'a, 'v, 'e>(
    elem: &'a Packed<PrimesElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    match elem.count {
        count @ 1..=4 => {
            let c = match count {
                1 => '′',
                2 => '″',
                3 => '‴',
                4 => '⁗',
                _ => unreachable!(),
            };
            let f = ctx.resolve_into_item(
                ctx.store(SymbolElem::packed(c).spanned(elem.span())),
                styles,
            )?;
            ctx.push(f);
        }
        count => {
            // Custom amount of primes
            ctx.push(PrimesItem::create(count, styles));
        }
    }
    Ok(())
}

/// Resolves a scripts element.
fn resolve_scripts<'a, 'v, 'e>(
    elem: &'a Packed<ScriptsElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let mut item = ctx.resolve_into_item(&elem.body, styles)?;
    item.set_limits(Limits::Never);
    ctx.push(item);
    Ok(())
}

/// Resolves a limits element.
fn resolve_limits<'a, 'v, 'e>(
    elem: &'a Packed<LimitsElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let mut item = ctx.resolve_into_item(&elem.body, styles)?;
    let limits = if elem.inline.get(styles) { Limits::Always } else { Limits::Display };
    item.set_limits(limits);
    ctx.push(item);
    Ok(())
}

/// Resolves a stretch element.
fn resolve_stretch<'a, 'v, 'e>(
    elem: &'a Packed<StretchElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let item = ctx.resolve_into_item(&elem.body, styles)?;
    let size = elem.size.get(styles);
    let font_size = styles.resolve(TextElem::size);
    item.update_stretch(StretchInfo::from_size(size, Em::zero(), font_size));
    ctx.push(item);
    Ok(())
}

/// Resolves a cancel element.
fn resolve_cancel<'a, 'v, 'e>(
    elem: &'a Packed<CancelElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let body = ctx.resolve_into_item(&elem.body, styles)?;

    let length = elem.length.resolve(styles);
    let stroke = elem.stroke.resolve(styles).unwrap_or(FixedStroke {
        paint: styles.get_ref(TextElem::fill).as_decoration(),
        ..Default::default()
    });

    let invert = elem.inverted.get(styles);
    let cross = elem.cross.get(styles);
    let angle = elem.angle.get_ref(styles);
    let invert_first_line = !cross && invert;

    ctx.push(CancelItem::create(
        body,
        length,
        stroke,
        cross,
        invert_first_line,
        angle.clone(),
        styles,
        elem.span(),
    ));
    Ok(())
}

/// Resolves a fraction element.
fn resolve_frac<'a, 'v, 'e>(
    elem: &'a Packed<FracElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    match elem.style.get(styles) {
        FracStyle::Skewed => {
            resolve_skewed_frac(ctx, styles, &elem.num, &elem.denom, elem.span())
        }
        FracStyle::Horizontal => resolve_horizontal_frac(
            ctx,
            styles,
            &elem.num,
            &elem.denom,
            elem.span(),
            elem.num_deparenthesized.get(styles),
            elem.denom_deparenthesized.get(styles),
        ),
        FracStyle::Vertical => resolve_vertical_frac_like(
            ctx,
            styles,
            &elem.num,
            std::slice::from_ref(&elem.denom),
            false,
            elem.span(),
        ),
    }
}

/// Resolves a binomial element.
fn resolve_binom<'a, 'v, 'e>(
    elem: &'a Packed<BinomElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_vertical_frac_like(ctx, styles, &elem.upper, &elem.lower, true, elem.span())
}

/// Resolve a vertical fraction or binomial.
fn resolve_vertical_frac_like<'a, 'v, 'e>(
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
    num: &'a Content,
    denom: &[Content],
    binom: bool,
    span: Span,
) -> SourceResult<()> {
    let num_style = ctx.store_styles(style_for_numerator(styles));
    let denom_style = ctx.store_styles(style_for_denominator(styles));
    let bumped_styles = ctx.store_chain(styles);

    let numerator = ctx.resolve_into_item(num, bumped_styles.chain(num_style))?;

    let denominator = ctx.resolve_into_item(
        ctx.store(Content::sequence(
            // Add a comma between each element.
            denom
                .iter()
                .flat_map(|a| [SymbolElem::packed(',').spanned(span), a.clone()])
                .skip(1),
        )),
        bumped_styles.chain(denom_style),
    )?;

    let frac =
        FractionItem::create(numerator, denominator, !binom, FRAC_PADDING, styles, span);

    if binom {
        let stretch =
            Stretch::new().with_y(StretchInfo::new(Rel::one(), DELIM_SHORT_FALL));
        let open = ctx.resolve_into_item(
            ctx.store(SymbolElem::packed('(').spanned(span)),
            styles,
        )?;
        open.set_stretch(stretch);
        let close = ctx.resolve_into_item(
            ctx.store(SymbolElem::packed(')').spanned(span)),
            styles,
        )?;
        close.set_stretch(stretch);
        ctx.push(FencedItem::create(Some(open), Some(close), frac, false, styles, span));
    } else {
        ctx.push(frac);
    }

    Ok(())
}

// Resolve a horizontal (inline) fraction.
fn resolve_horizontal_frac<'a, 'v, 'e>(
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
    num: &'a Content,
    denom: &'a Content,
    span: Span,
    num_deparen: bool,
    denom_deparen: bool,
) -> SourceResult<()> {
    let num = if num_deparen {
        ctx.store(
            LrElem::new(Content::sequence(vec![
                SymbolElem::packed('('),
                num.clone(),
                SymbolElem::packed(')'),
            ]))
            .pack(),
        )
    } else {
        num
    };
    let num = ctx.resolve_into_item(num, styles)?;
    ctx.push(num);

    let mut slash =
        ctx.resolve_into_item(ctx.store(SymbolElem::packed('/').spanned(span)), styles)?;
    slash.set_class(MathClass::Binary);
    slash.set_lspace(Some(Em::zero()));
    slash.set_rspace(Some(Em::zero()));
    ctx.push(slash);

    let denom = if denom_deparen {
        ctx.store(
            LrElem::new(Content::sequence(vec![
                SymbolElem::packed('('),
                denom.clone(),
                SymbolElem::packed(')'),
            ]))
            .pack(),
        )
    } else {
        denom
    };
    let denom = ctx.resolve_into_item(denom, styles)?;
    ctx.push(denom);

    Ok(())
}

/// Resolves a skewed (inline) fraction.
fn resolve_skewed_frac<'a, 'v, 'e>(
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
    num: &'a Content,
    denom: &'a Content,
    span: Span,
) -> SourceResult<()> {
    let num_style = ctx.store_styles(style_for_numerator(styles));
    let denom_style = ctx.store_styles(style_for_denominator(styles));
    let bumped_styles = ctx.store_chain(styles);

    let numerator = ctx.resolve_into_item(num, bumped_styles.chain(num_style))?;
    let denominator = ctx.resolve_into_item(denom, bumped_styles.chain(denom_style))?;

    let slash = ctx.resolve_into_item(
        ctx.store(SymbolElem::packed('\u{2044}').spanned(span)),
        styles,
    )?;
    slash.set_stretch(
        Stretch::new().with_y(StretchInfo::new(Rel::one(), DELIM_SHORT_FALL)),
    );

    ctx.push(SkewedFractionItem::create(numerator, denominator, slash, styles, span));

    Ok(())
}

/// Resolves a left/right element.
fn resolve_lr<'a, 'v, 'e>(
    elem: &'a Packed<LrElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    // Extract from an EquationElem.
    let mut body = &elem.body;
    if let Some(equation) = body.to_packed::<EquationElem>() {
        body = &equation.body;
    }

    // Extract implicit LrElem.
    if let Some(lr) = body.to_packed::<LrElem>()
        && lr.size.get(styles).is_one()
    {
        body = &lr.body;
    }

    let start = ctx.resolve_into_items(body, styles)?;

    // Ignore leading and trailing ignorant items.
    let (start_idx, end_idx) =
        ctx.items[start..].split_prefix_suffix(|f| f.is_ignorant());
    let inner_range = (start + start_idx)..(start + end_idx);
    let inner_items = &mut ctx.items[inner_range.clone()];

    let size = elem.size.get(styles);
    let font_size = styles.resolve(TextElem::size);
    let stretch =
        Stretch::new().with_y(StretchInfo::from_size(size, DELIM_SHORT_FALL, font_size));

    let scale_if_delimiter = |item: &mut MathItem, apply: Option<MathClass>| {
        if matches!(
            item.class(),
            MathClass::Opening | MathClass::Closing | MathClass::Fence
        ) {
            item.set_stretch(stretch);
            if let Some(class) = apply {
                item.set_class(class);
            }
        }
    };

    // Scale up items at both ends.
    match inner_items {
        [one] => {
            if let RawMathItem::Item(one) = one {
                if let MathItem::Component(MathComponent {
                    kind: MathKind::Fenced(fenced),
                    ..
                }) = one
                {
                    if let Some(open) = &fenced.open {
                        open.set_stretch(stretch);
                    }
                    if let Some(close) = &fenced.close {
                        close.set_stretch(stretch);
                    }
                    for item in fenced.body.as_slice() {
                        if item.mid_stretched() == Some(true) {
                            item.set_stretch(stretch);
                        }
                    }
                } else {
                    let mut info =
                        StretchInfo::new(size.abs.at(font_size).into(), DELIM_SHORT_FALL);
                    if !size.is_one() {
                        info.requested_target = Some(size);
                    }
                    one.set_y_stretch(info);
                }
            }
            return Ok(());
        }
        [first, .., last] => {
            if let RawMathItem::Item(first) = first {
                scale_if_delimiter(first, Some(MathClass::Opening));
            }
            if let RawMathItem::Item(last) = last {
                scale_if_delimiter(last, Some(MathClass::Closing));
            }
        }
        [] => {}
    }

    // Handle MathItem::Glyph items that should be scaled up.
    for item in inner_items.iter_mut() {
        if let RawMathItem::Item(item) = item
            && item.mid_stretched() == Some(false)
        {
            item.set_mid_stretched(Some(true));
            item.set_stretch(stretch);
        }
    }

    let mut inner_items: Vec<_> = ctx.items.drain(inner_range).collect();

    // Remove weak Spacing immediately after the opening or immediately
    // before the closing.
    let mut index = 0;
    let len = inner_items.len();
    let opening_exists = inner_items.first().is_some_and(
        |f| matches!(f, RawMathItem::Item(item) if item.class() == MathClass::Opening),
    );
    let closing_exists = inner_items.last().is_some_and(
        |f| matches!(f, RawMathItem::Item(item) if item.class() == MathClass::Closing),
    );
    inner_items.retain(|item| {
        let discard = (index == 1 && opening_exists
            || index + 2 == len && closing_exists)
            && matches!(item, RawMathItem::Item(MathItem::Spacing(_, _, true)));
        index += 1;
        !discard
    });

    let open = opening_exists.then(|| inner_items.remove(0).into_item().unwrap());
    let close = closing_exists.then(|| inner_items.pop().unwrap().into_item().unwrap());

    let insert_pos = start + start_idx;
    match process_group(inner_items, styles, close.is_some(), false) {
        GroupResult::Multiline(rows) => {
            let items = expand_multiline_fence(rows, open, close, styles, elem.span());
            ctx.items.splice(insert_pos..insert_pos, items);
        }
        GroupResult::Flat(items) => {
            let body = GroupItem::create(items, styles);
            let item = FencedItem::create(open, close, body, true, styles, elem.span());
            ctx.items.insert(insert_pos, item.into());
        }
    }

    Ok(())
}

/// Resolves a middle element (in a left/right element).
fn resolve_mid<'a, 'v, 'e>(
    elem: &'a Packed<MidElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let start = ctx.resolve_into_items(&elem.body, styles)?;
    for item in &mut ctx.items[start..] {
        if let RawMathItem::Item(item) = item {
            item.set_mid_stretched(Some(false));
            item.set_class(MathClass::Relation);
        }
    }
    Ok(())
}

/// Resolves a vector element.
fn resolve_vec<'a, 'v, 'e>(
    elem: &'a Packed<VecElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let span = elem.span();

    let rows: Vec<Vec<&Content>> =
        elem.children.iter().map(|child| vec![child]).collect();
    let cells = resolve_cells(
        ctx,
        styles,
        rows,
        span,
        elem.align.resolve(styles),
        LeftRightAlternator::Right,
        None,
        Axes::with_y(elem.gap.resolve(styles)),
        "elements",
    )?;

    let delim = elem.delim.get(styles);
    resolve_delimiters(ctx, styles, cells, delim.open(), delim.close(), span)
}

/// Resolves a matrix element.
fn resolve_mat<'a, 'v, 'e>(
    elem: &'a Packed<MatElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let span = elem.span();

    let rows: Vec<Vec<&Content>> =
        elem.rows.iter().map(|row| row.iter().collect()).collect();
    let nrows = rows.len();
    let ncols = rows.first().map_or(0, |row| row.len());

    let augment = elem.augment.resolve(styles);
    if let Some(aug) = &augment {
        for &offset in &aug.hline.0 {
            if offset > nrows as isize || offset.unsigned_abs() > nrows {
                bail!(
                    span,
                    "cannot draw a horizontal line at offset {offset} \
                     in a matrix with {nrows} rows",
                );
            }
        }

        for &offset in &aug.vline.0 {
            if offset > ncols as isize || offset.unsigned_abs() > ncols {
                bail!(
                    span,
                    "cannot draw a vertical line at offset {offset} \
                     in a matrix with {ncols} columns",
                );
            }
        }
    }

    let cells = resolve_cells(
        ctx,
        styles,
        rows,
        span,
        elem.align.resolve(styles),
        LeftRightAlternator::Right,
        augment,
        Axes::new(elem.column_gap.resolve(styles), elem.row_gap.resolve(styles)),
        "cells",
    )?;

    let delim = elem.delim.get(styles);
    resolve_delimiters(ctx, styles, cells, delim.open(), delim.close(), span)
}

/// Resolves a cases element.
fn resolve_cases<'a, 'v, 'e>(
    elem: &'a Packed<CasesElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let span = elem.span();

    let rows: Vec<Vec<&Content>> =
        elem.children.iter().map(|child| vec![child]).collect();
    let cells = resolve_cells(
        ctx,
        styles,
        rows,
        span,
        FixedAlignment::Start,
        LeftRightAlternator::None,
        None,
        Axes::with_y(elem.gap.resolve(styles)),
        "branches",
    )?;

    let delim = elem.delim.get(styles);
    let (open, close) = if elem.reverse.get(styles) {
        (None, delim.close())
    } else {
        (delim.open(), None)
    };
    resolve_delimiters(ctx, styles, cells, open, close, span)
}

/// Resolves the inner contents of a matrix, vector, or cases.
///
/// The contents of the cells are resolved in denominator style.
#[allow(clippy::too_many_arguments)]
fn resolve_cells<'a, 'v, 'e>(
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
    rows: Vec<Vec<&'a Content>>,
    span: Span,
    align: FixedAlignment,
    alternator: LeftRightAlternator,
    augment: Option<Augment<Abs>>,
    gap: Axes<Rel<Abs>>,
    children: &str,
) -> SourceResult<MathItem<'a>> {
    let cell_styles = ctx.chain_styles(styles, style_for_denominator(styles));

    let mut cells = Vec::with_capacity(rows.len());
    for row in rows {
        let mut resolved_row = Vec::with_capacity(row.len());
        for cell in row {
            let start = ctx.resolve_into_items(cell, cell_styles)?;
            let processed = process_table_cell(ctx.items.drain(start..), cell_styles);

            // We strip linebreaks in the cells as we can't differentiate
            // alignment points for the whole body from ones for a specific
            // cell, and multiline cells don't quite make sense at the moment.
            if processed.had_linebreaks {
                ctx.engine.sink.warn(warning!(
                   cell.span(),
                   "linebreaks are ignored in {}", children;
                   hint: "use commas instead to separate each line";
                ));
            }

            resolved_row.push(processed.sub_columns);
        }

        cells.push(resolved_row);
    }

    // Pad sub-columns so that every row has the same length.
    let ncols = cells.first().map_or(0, |row| row.len());
    for c in 0..ncols {
        let max = cells.iter().map(|row| row[c].len()).max().unwrap_or_default();
        for row in &mut cells {
            row[c].pad_to(max, cell_styles);
        }
    }

    Ok(TableItem::create(cells, gap, augment, align, alternator, styles, span))
}

/// Resolves the delimiters around the body of a vector, matrix, or cases.
fn resolve_delimiters<'a, 'v, 'e>(
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
    cells: MathItem<'a>,
    left: Option<char>,
    right: Option<char>,
    span: Span,
) -> SourceResult<()> {
    let target = Rel::new(Ratio::new(1.1), Abs::zero());
    let stretch = Stretch::new().with_y(StretchInfo::new(target, DELIM_SHORT_FALL));
    let open = left
        .map(|c| {
            ctx.resolve_into_item(ctx.store(SymbolElem::packed(c).spanned(span)), styles)
        })
        .transpose()?
        .inspect(|x| x.set_stretch(stretch));
    let close = right
        .map(|c| {
            ctx.resolve_into_item(ctx.store(SymbolElem::packed(c).spanned(span)), styles)
        })
        .transpose()?
        .inspect(|x| x.set_stretch(stretch));

    ctx.push(FencedItem::create(open, close, cells, false, styles, span));
    Ok(())
}

/// Resolves a class element.
fn resolve_class<'a, 'v, 'e>(
    elem: &'a Packed<ClassElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let styles =
        ctx.chain_styles(styles, EquationElem::class.set(Some(elem.class)).wrap());
    let mut item = ctx.resolve_into_item(&elem.body, styles)?;
    item.set_class(elem.class);
    item.set_limits(Limits::for_class(elem.class));
    ctx.push(item);
    Ok(())
}

/// Resolves an operator element.
fn resolve_op<'a, 'v, 'e>(
    elem: &'a Packed<OpElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let mut item = ctx.resolve_into_item(&elem.text, styles)?;
    item.set_class(MathClass::Large);
    item.set_limits(if elem.limits.get(styles) {
        Limits::Display
    } else {
        Limits::Never
    });
    ctx.push(item);
    Ok(())
}

/// Resolves a root (radical) element.
///
/// The radicand is resolved in cramped style, and the index in
/// scriptscript size and cramped style.
fn resolve_root<'a, 'v, 'e>(
    elem: &'a Packed<RootElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let cramped_styles = ctx.store_chain(ctx.chain_styles(styles, style_cramped()));
    let radicand = ctx
        .resolve_into_item(&elem.radicand, *cramped_styles)?
        .with_multiline_centering();
    let index = {
        let sscript =
            ctx.store_styles(EquationElem::size.set(MathSize::ScriptScript).wrap());
        elem.index
            .get_ref(styles)
            .as_ref()
            .map(|elem| ctx.resolve_into_item(elem, cramped_styles.chain(sscript)))
            .transpose()?
    };
    let sqrt = ctx.resolve_into_item(
        ctx.store(SymbolElem::packed('√').spanned(elem.span())),
        styles,
    )?;
    sqrt.set_stretch(Stretch::new().with_y(StretchInfo::new(Rel::one(), Em::zero())));
    ctx.push(RadicalItem::create(radicand, index, sqrt, styles, elem.span()));
    Ok(())
}

/// Resolves an under line element.
fn resolve_underline<'a, 'v, 'e>(
    elem: &'a Packed<UnderlineElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let base = ctx.resolve_into_item(&elem.body, styles)?;
    ctx.push(LineItem::create(base, Position::Below, styles, elem.span()));
    Ok(())
}

/// Resolves an over line element.
///
/// The base is resolved in cramped style.
fn resolve_overline<'a, 'v, 'e>(
    elem: &'a Packed<OverlineElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    let cramped_styles = ctx.chain_styles(styles, style_cramped());
    let base = ctx.resolve_into_item(&elem.body, cramped_styles)?;
    ctx.push(LineItem::create(base, Position::Above, styles, elem.span()));
    Ok(())
}

/// Resolves an unde rbrace element.
fn resolve_underbrace<'a, 'v, 'e>(
    elem: &'a Packed<UnderbraceElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⏟',
        Position::Below,
        elem.span(),
    )
}

/// Resolves an over brace element.
fn resolve_overbrace<'a, 'v, 'e>(
    elem: &'a Packed<OverbraceElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⏞',
        Position::Above,
        elem.span(),
    )
}

/// Resolves an under bracket element.
fn resolve_underbracket<'a, 'v, 'e>(
    elem: &'a Packed<UnderbracketElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⎵',
        Position::Below,
        elem.span(),
    )
}

/// Resolves an over bracket element.
fn resolve_overbracket<'a, 'v, 'e>(
    elem: &'a Packed<OverbracketElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⎴',
        Position::Above,
        elem.span(),
    )
}

/// Resolves an under parenthesis element.
fn resolve_underparen<'a, 'v, 'e>(
    elem: &'a Packed<UnderparenElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⏝',
        Position::Below,
        elem.span(),
    )
}

/// Resolves an over parenthesis element.
fn resolve_overparen<'a, 'v, 'e>(
    elem: &'a Packed<OverparenElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⏜',
        Position::Above,
        elem.span(),
    )
}

/// Resolves an under shell element.
fn resolve_undershell<'a, 'v, 'e>(
    elem: &'a Packed<UndershellElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⏡',
        Position::Below,
        elem.span(),
    )
}

/// Resolves an over shell element.
fn resolve_overshell<'a, 'v, 'e>(
    elem: &'a Packed<OvershellElem>,
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
) -> SourceResult<()> {
    resolve_underoverspreader(
        ctx,
        styles,
        &elem.body,
        elem.annotation.get_ref(styles),
        '⏠',
        Position::Above,
        elem.span(),
    )
}

/// Resolves an over- or underbrace-like object.
///
/// It is resolved as an accent, nested inside a scripts item if there is an
/// annotation.
fn resolve_underoverspreader<'a, 'v, 'e>(
    ctx: &mut MathResolver<'a, 'v, 'e>,
    styles: StyleChain<'a>,
    body: &'a Content,
    annotation: &'a Option<Content>,
    c: char,
    position: Position,
    span: Span,
) -> SourceResult<()> {
    let base = ctx.resolve_into_item(body, styles)?;

    let accent = ctx.store(SymbolElem::packed(c).spanned(span));
    let mut accent = ctx.resolve_into_item(accent, styles)?;
    accent.set_class(MathClass::Diacritic);
    accent.set_stretch(Stretch::new().with_x(StretchInfo::new(Rel::one(), Em::zero())));

    let base = AccentItem::create(base, accent, position, false, true, styles);

    let Some(annotation) = annotation else {
        ctx.push(base);
        return Ok(());
    };

    let base = match position {
        Position::Below => {
            let under_styles = ctx.chain_styles(styles, style_for_subscript(styles));
            let annotation = ctx.resolve_into_item(annotation, under_styles)?;
            ScriptsItem::create(
                base,
                None,
                Some(annotation),
                None,
                None,
                None,
                None,
                styles,
            )
        }
        Position::Above => {
            let over_styles = ctx.chain_styles(styles, style_for_superscript(styles));
            let annotation = ctx.resolve_into_item(annotation, over_styles)?;
            ScriptsItem::create(
                base,
                Some(annotation),
                None,
                None,
                None,
                None,
                None,
                styles,
            )
        }
    };

    ctx.push(base);
    Ok(())
}
