//! Identifier parsing for Fermented `UnrealScript`.
//!
//! Provides shared routines for parsing both regular and qualified identifiers,
//! e.g. `KFChar.ZombieClot`.

use crate::arena::{self, ArenaVec};
use crate::ast::{IdentifierToken, QualifiedIdentifier, QualifiedIdentifierRef};
use crate::lexer::{self, Token, TokenSpan};
use crate::parser::{ParseErrorKind, ParseResult, Parser, ResultRecoveryExt, diagnostic_labels};

impl<'src, 'arena> Parser<'src, 'arena> {
    /// Parses an identifier.
    ///
    /// On failure (unexpected end-of-file or a token that cannot be used as an
    /// identifier), produces `invalid_identifier_error_kind`.
    pub(crate) fn parse_identifier(
        &mut self,
        invalid_identifier_error_kind: ParseErrorKind,
    ) -> ParseResult<'src, 'arena, IdentifierToken> {
        let (token, token_position) =
            self.require_token_and_position(invalid_identifier_error_kind)?;
        let identifier = Parser::identifier_token_from_token(token, token_position)
            .ok_or_else(|| self.make_error_at_last_consumed(invalid_identifier_error_kind))?;
        self.advance();
        Ok(identifier)
    }

    /// Returns an [`IdentifierToken`] for `token` if it is valid as an
    /// identifier name.
    ///
    /// This helper performs only token-to-identifier validation/wrapping;
    /// it does not consume input from the parser.
    pub(crate) fn identifier_token_from_token(
        token: Token,
        token_position: lexer::TokenPosition,
    ) -> Option<IdentifierToken> {
        token
            .is_valid_identifier_name()
            .then_some(IdentifierToken(token_position))
    }

    /// Parses an identifier path, optionally followed by dot-separated
    /// segments.
    ///
    /// Accepts both a single identifier, such as `KFChar`, and
    /// a qualified path, such as `KFChar.ZombieClot`.
    ///
    /// Produces `invalid_identifier_error_kind` when the first identifier or
    /// a segment after `.` is missing or invalid.
    pub(crate) fn parse_qualified_identifier(
        &mut self,
        invalid_identifier_error_kind: ParseErrorKind,
    ) -> ParseResult<'src, 'arena, QualifiedIdentifierRef<'arena>> {
        let head = self.parse_identifier(invalid_identifier_error_kind)?;
        let mut tail = None;

        let span_start = head.0;
        let mut span_end = span_start;
        while let Some((Token::Period, dot_position)) = self.peek_token_and_position() {
            self.advance(); // '.'
            let next_segment = match self
                .parse_identifier(invalid_identifier_error_kind)
                .widen_error_span_from(head.0)
            {
                Ok(next_segment) => next_segment,
                Err(error) => {
                    return Err(error
                        .related_token(diagnostic_labels::QUALIFIED_IDENTIFIER_DOT, dot_position));
                }
            };
            span_end = next_segment.0;

            let tail_vec = tail.get_or_insert_with(|| ArenaVec::new_in(self.arena));
            tail_vec.push(next_segment);
        }

        Ok(arena::ArenaNode::new_in(
            QualifiedIdentifier { head, tail },
            TokenSpan::range(span_start, span_end),
            self.arena,
        ))
    }
}
