use crate::model::{
    MayaHybridTopLevelReport, MayaPromotionCandidate, MayaPromotionDecider,
    MayaPromotionDiagnostic, MayaPromotionError, MayaPromotionKind, MayaPromotionOptions,
    MayaPromotionPolicy, MayaTopLevelCommand, MayaTopLevelFacts, MayaTopLevelItem,
    NoopPromotionDecider,
};
use crate::normalize::{
    LightParseLike, command_payload_span, normalize_light_command,
    normalized_invoke_lookup_from_source, raw_item_from_light_word,
    raw_item_from_shell_word_with_source, take_matching_normalized,
};
use crate::registry::OverlayRegistry;
use crate::specialize::specialize_command;
use crate::validate::validate_maya_command;
use mel_ast::{Expr, InvokeSurface, Item, Stmt};
use mel_parser::{
    LightCommandSurface, LightItem, LightParse, SharedLightParse,
    parse_source_view_range_with_options,
};
use mel_sema::{CommandRegistry, EmptyCommandRegistry};

pub fn collect_top_level_facts_hybrid(
    parse: &LightParse,
) -> Result<MayaTopLevelFacts, MayaPromotionError> {
    collect_top_level_facts_hybrid_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        &MayaPromotionOptions::default(),
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_shared(
    parse: &SharedLightParse,
) -> Result<MayaTopLevelFacts, MayaPromotionError> {
    collect_top_level_facts_hybrid_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        &MayaPromotionOptions::default(),
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_with_decider<D>(
    parse: &LightParse,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_decider(
        parse,
        &EmptyCommandRegistry,
        options,
        decider,
    )
}

pub fn collect_top_level_facts_hybrid_shared_with_decider<D>(
    parse: &SharedLightParse,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        options,
        decider,
    )
}

pub fn collect_top_level_facts_hybrid_with_registry<R>(
    parse: &LightParse,
    registry: &R,
    policy: MayaPromotionPolicy,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_options(
        parse,
        registry,
        &MayaPromotionOptions {
            policy,
            ..MayaPromotionOptions::default()
        },
    )
}

pub fn collect_top_level_facts_hybrid_shared_with_registry<R>(
    parse: &SharedLightParse,
    registry: &R,
    policy: MayaPromotionPolicy,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    collect_top_level_facts_hybrid_shared_with_registry_and_options(
        parse,
        registry,
        &MayaPromotionOptions {
            policy,
            ..MayaPromotionOptions::default()
        },
    )
}

pub fn collect_top_level_facts_hybrid_with_registry_and_options<R>(
    parse: &LightParse,
    registry: &R,
    options: &MayaPromotionOptions,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_decider(
        parse,
        registry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_shared_with_registry_and_options<R>(
    parse: &SharedLightParse,
    registry: &R,
    options: &MayaPromotionOptions,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_decider_impl(
        parse,
        registry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_with_registry_and_decider<R, D>(
    parse: &LightParse,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_decider_impl(parse, registry, options, decider)
}

pub fn collect_top_level_facts_hybrid_shared_with_registry_and_decider<R, D>(
    parse: &SharedLightParse,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_with_registry_and_decider_impl(parse, registry, options, decider)
}

fn collect_top_level_facts_hybrid_with_registry_and_decider_impl<R, D, P>(
    parse: &P,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelFacts, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
    P: LightParseLike,
{
    let overlay = OverlayRegistry::new(registry);
    let mut items = Vec::new();

    for item in &parse.light_source().items {
        match item {
            LightItem::Proc(proc_def) => items.push(MayaTopLevelItem::Proc {
                name: proc_def
                    .name_range
                    .map(|range| parse.source_slice(range).to_owned())
                    .unwrap_or_default(),
                is_global: proc_def.is_global,
                span: proc_def.span,
            }),
            LightItem::Command(command) => items.push(MayaTopLevelItem::Command(Box::new(
                promote_or_synthesize_light_command(parse, command, &overlay, options, decider)?,
            ))),
            LightItem::Other { span } => items.push(MayaTopLevelItem::Other { span: *span }),
        }
    }

    Ok(MayaTopLevelFacts { items })
}

pub fn collect_top_level_facts_hybrid_report(
    parse: &LightParse,
    options: &MayaPromotionOptions,
) -> MayaHybridTopLevelReport {
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_report_shared(
    parse: &SharedLightParse,
    options: &MayaPromotionOptions,
) -> MayaHybridTopLevelReport {
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_report_with_decider<D>(
    parse: &LightParse,
    options: &MayaPromotionOptions,
    decider: &D,
) -> MayaHybridTopLevelReport
where
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        options,
        decider,
    )
}

pub fn collect_top_level_facts_hybrid_report_shared_with_decider<D>(
    parse: &SharedLightParse,
    options: &MayaPromotionOptions,
    decider: &D,
) -> MayaHybridTopLevelReport
where
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse,
        &EmptyCommandRegistry,
        options,
        decider,
    )
}

pub fn collect_top_level_facts_hybrid_report_with_registry<R>(
    parse: &LightParse,
    registry: &R,
    options: &MayaPromotionOptions,
) -> MayaHybridTopLevelReport
where
    R: CommandRegistry + ?Sized,
{
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse,
        registry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_report_shared_with_registry<R>(
    parse: &SharedLightParse,
    registry: &R,
    options: &MayaPromotionOptions,
) -> MayaHybridTopLevelReport
where
    R: CommandRegistry + ?Sized,
{
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse,
        registry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn collect_top_level_facts_hybrid_report_with_registry_and_decider<R, D>(
    parse: &LightParse,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> MayaHybridTopLevelReport
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse, registry, options, decider,
    )
}

pub fn collect_top_level_facts_hybrid_report_shared_with_registry_and_decider<R, D>(
    parse: &SharedLightParse,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> MayaHybridTopLevelReport
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    collect_top_level_facts_hybrid_report_with_registry_and_decider_impl(
        parse, registry, options, decider,
    )
}

fn collect_top_level_facts_hybrid_report_with_registry_and_decider_impl<R, D, P>(
    parse: &P,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> MayaHybridTopLevelReport
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
    P: LightParseLike,
{
    let overlay = OverlayRegistry::new(registry);
    let mut facts = MayaTopLevelFacts::default();
    let mut promotion_diagnostics = Vec::new();
    let mut validation_diagnostics = Vec::new();

    for item in &parse.light_source().items {
        match item {
            LightItem::Proc(proc_def) => facts.items.push(MayaTopLevelItem::Proc {
                name: proc_def
                    .name_range
                    .map(|range| parse.source_slice(range).to_owned())
                    .unwrap_or_default(),
                is_global: proc_def.is_global,
                span: proc_def.span,
            }),
            LightItem::Command(command) => {
                let command = promote_or_synthesize_light_command_report(
                    parse,
                    command,
                    &overlay,
                    options,
                    decider,
                    &mut promotion_diagnostics,
                );
                validation_diagnostics.extend(validate_maya_command(parse.source_view(), &command));
                facts
                    .items
                    .push(MayaTopLevelItem::Command(Box::new(command)));
            }
            LightItem::Other { span } => facts.items.push(MayaTopLevelItem::Other { span: *span }),
        }
    }

    MayaHybridTopLevelReport {
        facts,
        promotion_diagnostics,
        validation_diagnostics,
    }
}

pub fn promote_light_top_level_command_with_registry<R>(
    parse: &LightParse,
    command: &LightCommandSurface,
    registry: &R,
    policy: MayaPromotionPolicy,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_light_top_level_command_with_registry_and_options(
        parse,
        command,
        registry,
        &MayaPromotionOptions {
            policy,
            ..MayaPromotionOptions::default()
        },
    )
}

pub fn promote_light_top_level_command_shared_with_registry<R>(
    parse: &SharedLightParse,
    command: &LightCommandSurface,
    registry: &R,
    policy: MayaPromotionPolicy,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_light_top_level_command_shared_with_registry_and_options(
        parse,
        command,
        registry,
        &MayaPromotionOptions {
            policy,
            ..MayaPromotionOptions::default()
        },
    )
}

pub fn promote_light_top_level_command_with_registry_and_options<R>(
    parse: &LightParse,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_light_top_level_command_with_registry_and_decider(
        parse,
        command,
        registry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn promote_light_top_level_command_shared_with_registry_and_options<R>(
    parse: &SharedLightParse,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_light_top_level_command_with_registry_and_decider_impl(
        parse,
        command,
        registry,
        options,
        &NoopPromotionDecider,
    )
}

pub fn promote_light_top_level_command_with_registry_and_decider<R, D>(
    parse: &LightParse,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    promote_light_top_level_command_with_registry_and_decider_impl(
        parse, command, registry, options, decider,
    )
}

pub fn promote_light_top_level_command_shared_with_registry_and_decider<R, D>(
    parse: &SharedLightParse,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    promote_light_top_level_command_with_registry_and_decider_impl(
        parse, command, registry, options, decider,
    )
}

fn promote_light_top_level_command_with_registry_and_decider_impl<R, D, P>(
    parse: &P,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
    P: LightParseLike,
{
    let head = parse.source_slice(command.head_range).to_owned();
    let canonical_name = registry.lookup(&head).map(|schema| schema.name.clone());
    let attempt = promotion_attempt_kind(
        command,
        canonical_name.as_deref(),
        &head,
        &options.policy,
        decider,
    );
    match attempt {
        Some(MayaPromotionKind::CustomDeciderPromoted) => {
            promote_custom_decider_command_with_registry(parse, command, registry, options)
        }
        Some(kind @ MayaPromotionKind::OpaqueTailPromoted) => {
            promote_opaque_command_with_registry(parse, command, registry, options, kind)
        }
        Some(MayaPromotionKind::PolicyPromoted) => {
            promote_policy_command_with_registry(parse, command, registry, options)
        }
        Some(MayaPromotionKind::FullParse | MayaPromotionKind::LightSynthesized) | None => {
            Ok(build_nonopaque_top_level_command_with_registry(
                parse,
                command,
                registry,
                MayaPromotionKind::LightSynthesized,
            ))
        }
    }
}

fn promote_or_synthesize_light_command<R, D>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    let head = parse.source_slice(command.head_range).to_owned();
    let canonical_name = registry.lookup(&head).map(|schema| schema.name.clone());
    let attempted_kind = promotion_attempt_kind(
        command,
        canonical_name.as_deref(),
        &head,
        &options.policy,
        decider,
    );
    let Some(_) = attempted_kind else {
        return Ok(build_nonopaque_top_level_command_with_registry(
            parse,
            command,
            registry,
            MayaPromotionKind::LightSynthesized,
        ));
    };

    promote_light_top_level_command_with_registry_and_decider_impl(
        parse, command, registry, options, decider,
    )
}

fn promote_or_synthesize_light_command_report<R, D>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    decider: &D,
    diagnostics: &mut Vec<MayaPromotionDiagnostic>,
) -> MayaTopLevelCommand
where
    R: CommandRegistry + ?Sized,
    D: MayaPromotionDecider + ?Sized,
{
    let head = parse.source_slice(command.head_range).to_owned();
    let canonical_name = registry.lookup(&head).map(|schema| schema.name.clone());
    let attempted_kind = promotion_attempt_kind(
        command,
        canonical_name.as_deref(),
        &head,
        &options.policy,
        decider,
    );
    let Some(attempted_kind) = attempted_kind else {
        return build_nonopaque_top_level_command_with_registry(
            parse,
            command,
            registry,
            MayaPromotionKind::LightSynthesized,
        );
    };

    match promote_light_top_level_command_with_registry_and_decider_impl(
        parse, command, registry, options, decider,
    ) {
        Ok(command) => command,
        Err(error) => {
            diagnostics.push(MayaPromotionDiagnostic {
                command_span: error.command_span,
                head: error.head.clone(),
                attempted_kind,
                message: error.message,
            });
            build_nonopaque_top_level_command_with_registry(
                parse,
                command,
                registry,
                MayaPromotionKind::LightSynthesized,
            )
        }
    }
}

fn promotion_attempt_kind<D>(
    command: &LightCommandSurface,
    canonical_name: Option<&str>,
    raw_head: &str,
    policy: &MayaPromotionPolicy,
    decider: &D,
) -> Option<MayaPromotionKind>
where
    D: MayaPromotionDecider + ?Sized,
{
    if command.opaque_tail.is_some() {
        return Some(match policy {
            MayaPromotionPolicy::OpaqueTailOnly => MayaPromotionKind::OpaqueTailPromoted,
            MayaPromotionPolicy::Always => MayaPromotionKind::PolicyPromoted,
            MayaPromotionPolicy::ByCommandName(names) => {
                if names
                    .iter()
                    .any(|name| Some(name.as_str()) == canonical_name || name == raw_head)
                {
                    MayaPromotionKind::PolicyPromoted
                } else {
                    MayaPromotionKind::OpaqueTailPromoted
                }
            }
        });
    }

    match policy {
        MayaPromotionPolicy::OpaqueTailOnly => None,
        MayaPromotionPolicy::Always => Some(MayaPromotionKind::PolicyPromoted),
        MayaPromotionPolicy::ByCommandName(names) => names
            .iter()
            .find(|name| Some(name.as_str()) == canonical_name || *name == raw_head)
            .map(|_| MayaPromotionKind::PolicyPromoted),
    }
    .or_else(|| {
        decider
            .should_promote(MayaPromotionCandidate {
                command,
                raw_head,
                canonical_name,
            })
            .then_some(MayaPromotionKind::CustomDeciderPromoted)
    })
}

fn promote_custom_decider_command_with_registry<R>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_parsed_command_with_registry(
        parse,
        command,
        registry,
        options,
        MayaPromotionKind::CustomDeciderPromoted,
    )
}

fn build_nonopaque_top_level_command_with_registry<R>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    promotion_kind: MayaPromotionKind,
) -> MayaTopLevelCommand
where
    R: CommandRegistry + ?Sized,
{
    let head = parse.source_slice(command.head_range).to_owned();
    let raw_items = command
        .words
        .iter()
        .map(|word| raw_item_from_light_word(parse, word))
        .collect::<Vec<_>>();
    let promoted_span = command_payload_span(command.head_range, &raw_items);
    let normalized = registry.lookup(&head).map(|schema| {
        normalize_light_command(
            parse,
            &head,
            command.head_range,
            promoted_span,
            schema,
            &raw_items,
        )
    });
    let specialized = specialize_command(
        parse.source_view(),
        &head,
        promoted_span,
        normalized.as_ref(),
        &raw_items,
    );

    MayaTopLevelCommand {
        head,
        captured: command.captured,
        raw_items,
        normalized,
        specialized,
        promotion_kind,
        span: promoted_span,
    }
}

fn promote_opaque_command_with_registry<R>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    promotion_kind: MayaPromotionKind,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_parsed_command_with_registry(parse, command, registry, options, promotion_kind)
}

fn promote_policy_command_with_registry<R>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    promote_parsed_command_with_registry(
        parse,
        command,
        registry,
        options,
        MayaPromotionKind::PolicyPromoted,
    )
}

fn promote_parsed_command_with_registry<R>(
    parse: &impl LightParseLike,
    command: &LightCommandSurface,
    registry: &R,
    options: &MayaPromotionOptions,
    promotion_kind: MayaPromotionKind,
) -> Result<MayaTopLevelCommand, MayaPromotionError>
where
    R: CommandRegistry + ?Sized,
{
    let head = parse.source_slice(command.head_range).to_owned();
    let slice = parse_source_view_range_with_options(
        parse.source_view(),
        command.span,
        options.parse_options,
    );
    if !slice.lex_errors.is_empty() || !slice.errors.is_empty() {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command did not parse cleanly".to_owned(),
        });
    }

    let overlay = OverlayRegistry::new(registry);
    let analysis = mel_sema::analyze_with_registry(&slice.syntax, parse.source_view(), &overlay);
    let mut remaining_normalized =
        normalized_invoke_lookup_from_source(parse.source_view(), analysis.normalized_invokes);

    let Some(item) = slice.syntax.items.first() else {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command slice was empty".to_owned(),
        });
    };
    if slice.syntax.items.len() != 1 {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command slice contained multiple top-level items".to_owned(),
        });
    }

    let Item::Stmt(stmt) = item else {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command slice was not a statement".to_owned(),
        });
    };
    let Stmt::Expr { expr, .. } = &**stmt else {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command slice was not an invoke statement".to_owned(),
        });
    };
    let Expr::Invoke(invoke) = expr else {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command slice was not an invoke statement".to_owned(),
        });
    };
    let InvokeSurface::ShellLike {
        head_range,
        words,
        captured,
    } = &invoke.surface
    else {
        return Err(MayaPromotionError {
            command_span: command.span,
            head: Some(head),
            message: "promoted command slice was not shell-like".to_owned(),
        });
    };

    let promoted_head = parse.source_slice(*head_range).to_owned();
    let normalized = take_matching_normalized(&mut remaining_normalized, *head_range, invoke.range);
    let raw_items = words
        .iter()
        .map(|word| raw_item_from_shell_word_with_source(parse.source_view(), word))
        .collect::<Vec<_>>();
    let specialized = specialize_command(
        parse.source_view(),
        &promoted_head,
        invoke.range,
        normalized.as_ref(),
        &raw_items,
    );

    Ok(MayaTopLevelCommand {
        head: promoted_head,
        captured: *captured,
        raw_items,
        normalized,
        specialized,
        promotion_kind,
        span: invoke.range,
    })
}
