//! Deterministic native grammar tables and bounded shift/reduce parsing.

#![allow(clippy::inherent_to_string_shadow_display)]
#![allow(clippy::inherent_to_string)]
#![allow(clippy::missing_fields_in_debug)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::too_many_lines)]

use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::fmt;
use std::fmt::Write as _;
use std::io::Write;
use std::sync::{Arc, Mutex, Weak};

use libremetaverse_types::compat::{
    Hashtable, ICollection, IDictionaryEnumerator, Object, StreamReader,
};

use crate::lexer::{
    Diagnostic, DiagnosticCategory, DiagnosticSeverity, ObjectList, SYMBOL, SourceLineInfo, TOKEN,
};
use crate::{Builder, CsReader, Error, Func, Lexer, Serialiser, SymbolsGen};

/// Maximum number of parser states accepted from one grammar.
pub const MAX_PARSER_STATES: usize = 65_536;
/// Maximum live stack depth of the parser.
pub const MAX_PARSER_STACK: usize = 1_048_576;
/// Maximum number of shift/reduce/recovery operations in one parse.
pub const MAX_PARSER_STEPS: usize = 16_777_216;
/// Compatibility limit used by the reference error recovery loop.
pub const MAX_RECOVERY_ERRORS: usize = 1_000;
/// Conventional yacc error terminal number.
pub const ERROR_TOKEN: i32 = 0;

/// Associativity used to resolve equal-precedence shift/reduce conflicts.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Associativity {
    Left,
    Right,
    Nonassoc,
}

/// One grammar symbol supplied to [`Grammar`].
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GrammarSymbol {
    pub name: String,
    pub number: i32,
    pub terminal: bool,
}

/// One grammar production, numbered in insertion order.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GrammarProduction {
    pub lhs: i32,
    pub rhs: Vec<i32>,
    pub precedence: Option<i32>,
}

/// Deterministic grammar input for the native table builder.
#[derive(Clone, Debug)]
pub struct Grammar {
    start: i32,
    eof: i32,
    symbols: BTreeMap<i32, GrammarSymbol>,
    productions: Vec<GrammarProduction>,
    precedence: BTreeMap<i32, (i32, Associativity)>,
    literals: BTreeMap<String, i32>,
}

impl Grammar {
    /// Creates a grammar with explicit start and end-of-input symbols.
    pub fn new(start: i32, eof: i32) -> Result<Self, Error> {
        if start == eof || start == ERROR_TOKEN {
            return Err(Error::Argument);
        }
        Ok(Self {
            start,
            eof,
            symbols: BTreeMap::new(),
            productions: Vec::new(),
            precedence: BTreeMap::new(),
            literals: BTreeMap::new(),
        })
    }

    /// Adds a terminal or nonterminal. Numbers are stable parser identities.
    pub fn add_symbol(
        &mut self,
        name: impl Into<String>,
        number: i32,
        terminal: bool,
    ) -> Result<(), Error> {
        let name = name.into();
        if name.is_empty() || self.symbols.contains_key(&number) {
            return Err(Error::Argument);
        }
        self.symbols.insert(
            number,
            GrammarSymbol {
                name,
                number,
                terminal,
            },
        );
        Ok(())
    }

    /// Associates lexer literal text with a terminal number.
    pub fn add_literal(&mut self, text: impl Into<String>, terminal: i32) -> Result<(), Error> {
        let text = text.into();
        if text.is_empty()
            || !self
                .symbols
                .get(&terminal)
                .is_some_and(|symbol| symbol.terminal)
        {
            return Err(Error::Argument);
        }
        self.literals.insert(text, terminal);
        Ok(())
    }

    /// Adds a precedence declaration for a terminal.
    pub fn set_precedence(
        &mut self,
        terminal: i32,
        level: i32,
        associativity: Associativity,
    ) -> Result<(), Error> {
        if level <= 0
            || !self
                .symbols
                .get(&terminal)
                .is_some_and(|symbol| symbol.terminal)
        {
            return Err(Error::Argument);
        }
        self.precedence.insert(terminal, (level, associativity));
        Ok(())
    }

    /// Adds a production and returns its stable zero-based production number.
    pub fn add_production(&mut self, lhs: i32, rhs: Vec<i32>) -> Result<usize, Error> {
        self.add_production_with_precedence(lhs, rhs, None)
    }

    /// Adds a production with an explicit precedence terminal override.
    pub fn add_production_with_precedence(
        &mut self,
        lhs: i32,
        rhs: Vec<i32>,
        precedence: Option<i32>,
    ) -> Result<usize, Error> {
        if self.symbols.get(&lhs).is_none_or(|symbol| symbol.terminal)
            || rhs.iter().any(|symbol| !self.symbols.contains_key(symbol))
            || precedence.is_some_and(|symbol| !self.precedence.contains_key(&symbol))
        {
            return Err(Error::Argument);
        }
        let number = self.productions.len();
        self.productions.push(GrammarProduction {
            lhs,
            rhs,
            precedence,
        });
        Ok(number)
    }

    /// Builds canonical LR(0) item sets with SLR lookahead propagation.
    pub fn build(self) -> Result<YyParser, Error> {
        ParserMachine::build(self).map(YyParser::from_machine)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum TableAction {
    Shift(usize),
    Reduce(usize),
    Accept,
    Reject,
}

/// One table conflict and the deterministic compatibility decision.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParserConflict {
    pub state: usize,
    pub lookahead: i32,
    pub existing: String,
    pub incoming: String,
    pub chosen: String,
}

#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Item {
    production: usize,
    dot: usize,
}

#[derive(Clone, Debug)]
struct RuntimeProduction {
    lhs: i32,
    rhs: Vec<i32>,
    precedence: Option<(i32, Associativity)>,
}

#[derive(Clone, Debug)]
struct ParserMachine {
    start: i32,
    eof: i32,
    symbols: BTreeMap<i32, GrammarSymbol>,
    literals: BTreeMap<String, i32>,
    productions: Vec<RuntimeProduction>,
    actions: BTreeMap<(usize, i32), TableAction>,
    gotos: BTreeMap<(usize, i32), usize>,
    conflicts: Vec<ParserConflict>,
    states: Vec<BTreeSet<Item>>,
}

impl ParserMachine {
    fn build(grammar: Grammar) -> Result<Self, Error> {
        if grammar
            .symbols
            .get(&grammar.start)
            .is_none_or(|symbol| symbol.terminal)
            || grammar
                .symbols
                .get(&grammar.eof)
                .is_none_or(|symbol| !symbol.terminal)
            || grammar.productions.is_empty()
            || !grammar
                .productions
                .iter()
                .any(|production| production.lhs == grammar.start)
        {
            return Err(Error::Argument);
        }

        if grammar.symbols.len() >= MAX_PARSER_STATES
            || grammar.productions.len() >= MAX_PARSER_STACK
        {
            return Err(Error::Parse {
                position: grammar.symbols.len().max(grammar.productions.len()),
                context: "parser grammar exceeds the table-input limit",
            });
        }
        let augmented = (0..=grammar.symbols.len())
            .filter_map(|offset| i32::try_from(offset).ok())
            .filter_map(|offset| i32::MIN.checked_add(offset))
            .find(|value| *value != ERROR_TOKEN && !grammar.symbols.contains_key(value))
            .ok_or(Error::IndexOutOfRange)?;
        let mut productions = vec![RuntimeProduction {
            lhs: augmented,
            rhs: vec![grammar.start],
            precedence: None,
        }];
        productions.extend(grammar.productions.iter().map(|production| {
            let terminal = production.precedence.or_else(|| {
                production.rhs.iter().rev().copied().find(|symbol| {
                    grammar.symbols.get(symbol).is_some_and(|value| {
                        value.terminal && grammar.precedence.contains_key(symbol)
                    })
                })
            });
            RuntimeProduction {
                lhs: production.lhs,
                rhs: production.rhs.clone(),
                precedence: terminal.and_then(|symbol| grammar.precedence.get(&symbol).copied()),
            }
        }));

        let by_lhs = Self::productions_by_lhs(&productions);
        let first = Self::first_sets(&grammar.symbols, &productions);
        let nullable = Self::nullable(&grammar.symbols, &productions);
        let initial = Self::closure(
            BTreeSet::from([Item {
                production: 0,
                dot: 0,
            }]),
            &productions,
            &by_lhs,
        );
        let mut states = vec![initial];
        let mut state_ids = BTreeMap::from([(states[0].clone(), 0usize)]);
        let mut transitions = BTreeMap::new();
        let mut queue = VecDeque::from([0usize]);
        while let Some(state) = queue.pop_front() {
            let symbols = states[state]
                .iter()
                .filter_map(|item| productions[item.production].rhs.get(item.dot).copied())
                .collect::<BTreeSet<_>>();
            for symbol in symbols {
                let target = Self::goto(&states[state], symbol, &productions, &by_lhs);
                if target.is_empty() {
                    continue;
                }
                let target_state = if let Some(existing) = state_ids.get(&target) {
                    *existing
                } else {
                    if states.len() >= MAX_PARSER_STATES {
                        return Err(Error::Parse {
                            position: states.len(),
                            context: "parser grammar exceeds the state limit",
                        });
                    }
                    let index = states.len();
                    state_ids.insert(target.clone(), index);
                    states.push(target);
                    queue.push_back(index);
                    index
                };
                transitions.insert((state, symbol), target_state);
            }
        }

        let lookaheads = Self::lalr_lookaheads(
            &states,
            &transitions,
            &productions,
            &by_lhs,
            &first,
            &nullable,
            grammar.eof,
        )?;

        let mut machine = Self {
            start: grammar.start,
            eof: grammar.eof,
            symbols: grammar.symbols,
            literals: grammar.literals,
            productions,
            actions: BTreeMap::new(),
            gotos: BTreeMap::new(),
            conflicts: Vec::new(),
            states,
        };
        for ((state, symbol), target) in transitions {
            if machine
                .symbols
                .get(&symbol)
                .is_some_and(|value| value.terminal)
            {
                machine.insert_action(
                    state,
                    symbol,
                    TableAction::Shift(target),
                    &grammar.precedence,
                );
            } else {
                machine.gotos.insert((state, symbol), target);
            }
        }
        for state in 0..machine.states.len() {
            let items = machine.states[state].clone();
            for item in items {
                let production = &machine.productions[item.production];
                if item.dot != production.rhs.len() {
                    continue;
                }
                if item.production == 0 {
                    machine.insert_action(
                        state,
                        machine.eof,
                        TableAction::Accept,
                        &grammar.precedence,
                    );
                    continue;
                }
                if let Some(item_lookaheads) = lookaheads.get(&(state, item)) {
                    for lookahead in item_lookaheads {
                        machine.insert_action(
                            state,
                            *lookahead,
                            TableAction::Reduce(item.production - 1),
                            &grammar.precedence,
                        );
                    }
                }
            }
        }
        Ok(machine)
    }

    fn productions_by_lhs(productions: &[RuntimeProduction]) -> BTreeMap<i32, Vec<usize>> {
        let mut result = BTreeMap::<i32, Vec<usize>>::new();
        for (index, production) in productions.iter().enumerate() {
            result.entry(production.lhs).or_default().push(index);
        }
        result
    }

    fn nullable(
        _symbols: &BTreeMap<i32, GrammarSymbol>,
        productions: &[RuntimeProduction],
    ) -> BTreeSet<i32> {
        let mut nullable = BTreeSet::new();
        loop {
            let before = nullable.len();
            for production in productions {
                if production
                    .rhs
                    .iter()
                    .all(|symbol| nullable.contains(symbol))
                {
                    nullable.insert(production.lhs);
                }
            }
            if nullable.len() == before {
                break;
            }
        }
        nullable
    }

    fn first_sets(
        symbols: &BTreeMap<i32, GrammarSymbol>,
        productions: &[RuntimeProduction],
    ) -> BTreeMap<i32, BTreeSet<i32>> {
        let mut first = symbols
            .iter()
            .map(|(number, symbol)| {
                let set = if symbol.terminal {
                    BTreeSet::from([*number])
                } else {
                    BTreeSet::new()
                };
                (*number, set)
            })
            .collect::<BTreeMap<_, _>>();
        let nullable = Self::nullable(symbols, productions);
        loop {
            let snapshot = first.clone();
            for production in productions {
                for symbol in &production.rhs {
                    if let Some(values) = snapshot.get(symbol) {
                        first.entry(production.lhs).or_default().extend(values);
                    }
                    if !nullable.contains(symbol) {
                        break;
                    }
                }
            }
            if first == snapshot {
                break;
            }
        }
        first
    }

    fn lalr_lookaheads(
        states: &[BTreeSet<Item>],
        transitions: &BTreeMap<(usize, i32), usize>,
        productions: &[RuntimeProduction],
        by_lhs: &BTreeMap<i32, Vec<usize>>,
        first: &BTreeMap<i32, BTreeSet<i32>>,
        nullable: &BTreeSet<i32>,
        eof: i32,
    ) -> Result<BTreeMap<(usize, Item), BTreeSet<i32>>, Error> {
        let initial = Item {
            production: 0,
            dot: 0,
        };
        let mut lookaheads = BTreeMap::from([((0, initial), BTreeSet::from([eof]))]);
        let mut passes = 0usize;
        loop {
            passes = passes.checked_add(1).ok_or(Error::IndexOutOfRange)?;
            if passes > MAX_PARSER_STEPS {
                return Err(Error::Parse {
                    position: passes,
                    context: "parser lookahead propagation exceeded the step limit",
                });
            }
            let snapshot = lookaheads.clone();
            for (state, items) in states.iter().enumerate() {
                for item in items {
                    let current = snapshot.get(&(state, *item)).cloned().unwrap_or_default();
                    if current.is_empty() {
                        continue;
                    }
                    let production = &productions[item.production];
                    let Some(symbol) = production.rhs.get(item.dot) else {
                        continue;
                    };

                    if let Some(target) = transitions.get(&(state, *symbol)) {
                        lookaheads
                            .entry((
                                *target,
                                Item {
                                    production: item.production,
                                    dot: item.dot + 1,
                                },
                            ))
                            .or_default()
                            .extend(&current);
                    }

                    let Some(indices) = by_lhs.get(symbol) else {
                        continue;
                    };
                    let rest = &production.rhs[item.dot + 1..];
                    let mut closure_lookaheads = BTreeSet::new();
                    for following in rest {
                        if let Some(values) = first.get(following) {
                            closure_lookaheads.extend(values);
                        }
                        if !nullable.contains(following) {
                            break;
                        }
                    }
                    if rest.iter().all(|following| nullable.contains(following)) {
                        closure_lookaheads.extend(&current);
                    }
                    for production in indices {
                        lookaheads
                            .entry((
                                state,
                                Item {
                                    production: *production,
                                    dot: 0,
                                },
                            ))
                            .or_default()
                            .extend(&closure_lookaheads);
                    }
                }
            }
            if lookaheads == snapshot {
                break;
            }
        }
        Ok(lookaheads)
    }

    fn closure(
        mut items: BTreeSet<Item>,
        productions: &[RuntimeProduction],
        by_lhs: &BTreeMap<i32, Vec<usize>>,
    ) -> BTreeSet<Item> {
        loop {
            let before = items.len();
            let snapshot = items.clone();
            for item in snapshot {
                let Some(symbol) = productions[item.production].rhs.get(item.dot) else {
                    continue;
                };
                if let Some(indices) = by_lhs.get(symbol) {
                    items.extend(indices.iter().map(|production| Item {
                        production: *production,
                        dot: 0,
                    }));
                }
            }
            if items.len() == before {
                return items;
            }
        }
    }

    fn goto(
        items: &BTreeSet<Item>,
        symbol: i32,
        productions: &[RuntimeProduction],
        by_lhs: &BTreeMap<i32, Vec<usize>>,
    ) -> BTreeSet<Item> {
        let advanced = items
            .iter()
            .filter(|item| productions[item.production].rhs.get(item.dot) == Some(&symbol))
            .map(|item| Item {
                production: item.production,
                dot: item.dot + 1,
            })
            .collect::<BTreeSet<_>>();
        Self::closure(advanced, productions, by_lhs)
    }

    fn insert_action(
        &mut self,
        state: usize,
        lookahead: i32,
        incoming: TableAction,
        precedence: &BTreeMap<i32, (i32, Associativity)>,
    ) {
        let key = (state, lookahead);
        let Some(existing) = self.actions.get(&key).copied() else {
            self.actions.insert(key, incoming);
            return;
        };
        if existing == incoming {
            return;
        }
        let chosen = match (existing, incoming) {
            (TableAction::Shift(_), TableAction::Reduce(production)) => {
                self.resolve_shift_reduce(existing, production, lookahead, precedence)
            }
            (TableAction::Reduce(production), TableAction::Shift(_)) => {
                self.resolve_shift_reduce(incoming, production, lookahead, precedence)
            }
            (TableAction::Reduce(left), TableAction::Reduce(right)) => {
                TableAction::Reduce(left.min(right))
            }
            (TableAction::Accept, _) | (_, TableAction::Accept) => TableAction::Accept,
            _ => existing,
        };
        self.conflicts.push(ParserConflict {
            state,
            lookahead,
            existing: Self::action_text(existing),
            incoming: Self::action_text(incoming),
            chosen: Self::action_text(chosen),
        });
        self.actions.insert(key, chosen);
    }

    fn resolve_shift_reduce(
        &self,
        shift: TableAction,
        production: usize,
        lookahead: i32,
        precedence: &BTreeMap<i32, (i32, Associativity)>,
    ) -> TableAction {
        let token_precedence = precedence.get(&lookahead).copied();
        let production_precedence = self.productions[production + 1].precedence;
        match (token_precedence, production_precedence) {
            (Some((token, associativity)), Some((reduce, _))) if token == reduce => {
                match associativity {
                    Associativity::Left => TableAction::Reduce(production),
                    Associativity::Right => shift,
                    Associativity::Nonassoc => TableAction::Reject,
                }
            }
            (Some((token, _)), Some((reduce, _))) if reduce > token => {
                TableAction::Reduce(production)
            }
            _ => shift,
        }
    }

    fn action_text(action: TableAction) -> String {
        match action {
            TableAction::Shift(state) => format!("shift {state}"),
            TableAction::Reduce(production) => format!("reduce {production}"),
            TableAction::Accept => "accept".to_owned(),
            TableAction::Reject => "reject".to_owned(),
        }
    }

    fn stable_description(&self) -> String {
        let mut lines = vec![format!(
            "LSLPARSER 1 start {} eof {} states {} productions {}",
            self.start,
            self.eof,
            self.states.len(),
            self.productions.len().saturating_sub(1)
        )];
        for ((state, token), action) in &self.actions {
            lines.push(format!(
                "action {state} {token} {}",
                Self::action_text(*action)
            ));
        }
        for ((state, symbol), target) in &self.gotos {
            lines.push(format!("goto {state} {symbol} {target}"));
        }
        for (index, production) in self.productions.iter().skip(1).enumerate() {
            let rhs = production
                .rhs
                .iter()
                .map(i32::to_string)
                .collect::<Vec<_>>()
                .join(" ");
            lines.push(format!("production {index} {} -> {rhs}", production.lhs));
        }
        lines.join("\n")
    }
}

/// Immutable syntax tree produced by the native parser.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseTree {
    pub name: String,
    pub number: i32,
    pub text: Option<String>,
    pub start: i32,
    pub end: i32,
    pub children: Vec<ParseTree>,
}

impl ParseTree {
    fn token(token: &TOKEN, number: i32) -> Self {
        Self {
            name: token.yyname(),
            number,
            text: Some(token.yytext()),
            start: token.pos,
            end: token.end,
            children: Vec::new(),
        }
    }

    fn error(position: i32) -> Self {
        Self {
            name: "Error".to_owned(),
            number: ERROR_TOKEN,
            text: None,
            start: position,
            end: position,
            children: Vec::new(),
        }
    }
}

/// Mapped C# symbol categories.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
pub enum CSymbolSymType {
    Unknown = 0,
    Terminal = 1,
    Nonterminal = 2,
    Nodesymbol = 3,
    Oldaction = 4,
    Simpleaction = 5,
    Eofsymbol = 6,
}

#[derive(Clone, Debug)]
struct SymbolRef {
    name: String,
    number: i32,
    symbol_type: CSymbolSymType,
    productions: Weak<Mutex<Vec<Arc<Mutex<ProductionRecord>>>>>,
}

impl SymbolRef {
    fn is_nullable(&self, visiting: &mut BTreeSet<(i32, String)>) -> bool {
        if matches!(
            self.symbol_type,
            CSymbolSymType::Oldaction | CSymbolSymType::Simpleaction
        ) {
            return true;
        }
        if self.symbol_type != CSymbolSymType::Nonterminal {
            return false;
        }
        let key = (self.number, self.name.clone());
        if !visiting.insert(key.clone()) {
            return false;
        }
        let nullable = self
            .productions
            .upgrade()
            .and_then(|productions| {
                productions
                    .lock()
                    .ok()
                    .map(|values| values.iter().cloned().collect::<Vec<_>>())
            })
            .is_some_and(|productions| {
                productions.iter().any(|production| {
                    production.lock().ok().is_some_and(|value| {
                        value.rhs.iter().all(|symbol| symbol.is_nullable(visiting))
                    })
                })
            });
        visiting.remove(&key);
        nullable
    }

    fn to_symbol(&self) -> CSymbol {
        let mut symbol = CSymbol::placeholder();
        symbol.text.clone_from(&self.name);
        symbol.m_yynum = self.number;
        symbol.m_symtype = self.symbol_type;
        symbol.m_defined = true;
        if let Some(productions) = self.productions.upgrade() {
            symbol.productions = productions;
        }
        symbol
    }
}

#[derive(Debug)]
struct ProductionRecord {
    number: i32,
    actions_only: bool,
    precedence: i32,
    rhs: Vec<SymbolRef>,
}

/// Grammar symbol metadata used by production and precedence construction.
#[derive(Clone)]
pub struct CSymbol {
    pub m_defined: bool,
    pub m_emitted: bool,
    pub m_first: SymbolSet,
    pub m_follow: SymbolSet,
    pub m_initialisation: String,
    pub m_parser: Option<Arc<SymbolsGen>>,
    pub m_prec: Option<Box<Precedence>>,
    pub m_prod: Option<Box<Production>>,
    pub m_prods: ObjectList,
    pub m_ref_symbol: Option<Box<CSymbol>>,
    pub m_symtype: CSymbolSymType,
    pub m_yynum: i32,
    text: String,
    productions: Arc<Mutex<Vec<Arc<Mutex<ProductionRecord>>>>>,
}

impl fmt::Debug for CSymbol {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("CSymbol")
            .field("text", &self.text)
            .field("number", &self.m_yynum)
            .field("type", &self.m_symtype)
            .field(
                "productions",
                &self.productions.lock().map_or(0, |values| values.len()),
            )
            .finish()
    }
}

impl CSymbol {
    fn placeholder() -> Self {
        Self {
            m_defined: false,
            m_emitted: false,
            m_first: SymbolSet::empty(),
            m_follow: SymbolSet::empty(),
            m_initialisation: String::new(),
            m_parser: None,
            m_prec: None,
            m_prod: None,
            m_prods: ObjectList::default(),
            m_ref_symbol: None,
            m_symtype: CSymbolSymType::Unknown,
            m_yynum: -1,
            text: String::new(),
            productions: Arc::new(Mutex::new(Vec::new())),
        }
    }

    /// Constructs symbol metadata without generator state.
    pub fn native(
        name: impl Into<String>,
        number: i32,
        symbol_type: CSymbolSymType,
    ) -> Result<Self, Error> {
        let name = name.into();
        if name.is_empty() || number < ERROR_TOKEN {
            return Err(Error::Argument);
        }
        let mut symbol = Self::placeholder();
        symbol.text = name;
        symbol.m_yynum = number;
        symbol.m_symtype = symbol_type;
        symbol.m_defined = true;
        if matches!(
            symbol_type,
            CSymbolSymType::Terminal | CSymbolSymType::Eofsymbol
        ) {
            symbol.m_first.add_in(symbol.clone())?;
        }
        Ok(symbol)
    }

    pub fn new_with_lexer(lexer: Lexer) -> Result<Self, Error> {
        let mut symbol = Self::placeholder();
        symbol.text.clone_from(&lexer.yytext);
        Ok(symbol)
    }

    pub fn new_with_symbols_gen(symbols: SymbolsGen) -> Result<Self, Error> {
        let mut symbol = Self::placeholder();
        symbol.m_parser = Some(Arc::new(symbols));
        Ok(symbol)
    }

    pub fn add_follow(&mut self, map: SymbolSet) -> Result<bool, Error> {
        let before = self.m_follow.count();
        self.m_follow.add_with_symbol_set(map)?;
        Ok(self.m_follow.count() != before)
    }

    pub fn add_start_items(&self, mut state: ParseState, follows: SymbolSet) -> Result<(), Error> {
        for production in self.registered_productions()? {
            let mut item = ProdItem::new_with_production_int32(production, 0)?;
            item.follow = Some(follows.clone());
            state.maybe_add(item)?;
        }
        Ok(())
    }

    pub fn is_nullable(&self) -> Result<bool, Error> {
        Ok(self.symbol_ref().is_nullable(&mut BTreeSet::new()))
    }

    pub fn is_terminal(&self) -> Result<bool, Error> {
        Ok(matches!(
            self.m_symtype,
            CSymbolSymType::Terminal | CSymbolSymType::Eofsymbol
        ))
    }

    pub fn matches(&self, value: String) -> Result<bool, Error> {
        Ok(self.text == value)
    }

    pub fn resolve(&self) -> Result<Self, Error> {
        Ok(self.clone())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    pub fn shift_precedence(
        &self,
        production: Production,
        _state: ParseState,
    ) -> Result<PrecedencePrecType, Error> {
        let Some(precedence) = self.m_prec.as_deref() else {
            return Ok(PrecedencePrecType::Left);
        };
        if precedence.m_type == PrecedencePrecType::Nonassoc {
            return Ok(PrecedencePrecType::Nonassoc);
        }
        let comparison =
            Precedence::check_with_c_symbol_production_int32(self.clone(), production, 0)?;
        match comparison.cmp(&0) {
            std::cmp::Ordering::Greater => Ok(PrecedencePrecType::Left),
            std::cmp::Ordering::Less => Ok(PrecedencePrecType::Right),
            std::cmp::Ordering::Equal => Ok(precedence.m_type),
        }
    }

    pub fn type_str(&self) -> Result<String, Error> {
        Ok(self.text.clone())
    }

    #[must_use]
    pub fn yytext(&self) -> &str {
        &self.text
    }

    fn symbol_ref(&self) -> SymbolRef {
        SymbolRef {
            name: self.text.clone(),
            number: self.m_yynum,
            symbol_type: self.m_symtype,
            productions: Arc::downgrade(&self.productions),
        }
    }

    fn register_production(
        &mut self,
        production: Arc<Mutex<ProductionRecord>>,
    ) -> Result<(), Error> {
        let mut productions = self
            .productions
            .lock()
            .map_err(|_| Error::InvalidOperation)?;
        if !productions
            .iter()
            .any(|candidate| Arc::ptr_eq(candidate, &production))
        {
            productions.push(production);
        }
        Ok(())
    }

    fn registered_productions(&self) -> Result<Vec<Production>, Error> {
        self.productions
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .iter()
            .map(|record| Production::from_record(self.clone(), record.clone()))
            .collect()
    }
}

/// Deduplicated deterministic grammar-symbol set.
#[derive(Clone)]
pub struct SymbolSet {
    pub m_next: Option<Box<SymbolSet>>,
    pub m_symbols: Option<Arc<SymbolsGen>>,
    members: Arc<Mutex<BTreeMap<(i32, String), SymbolRef>>>,
}

impl fmt::Debug for SymbolSet {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let members = self.members.lock().map_err(|_| fmt::Error)?;
        formatter.debug_set().entries(members.keys()).finish()
    }
}

impl SymbolSet {
    fn empty() -> Self {
        Self {
            m_next: None,
            m_symbols: None,
            members: Arc::new(Mutex::new(BTreeMap::new())),
        }
    }

    pub(crate) fn compatibility_empty() -> Self {
        Self::empty()
    }

    /// Creates an empty set independent of generator state.
    #[must_use]
    pub fn native() -> Self {
        Self::empty()
    }

    pub fn new_with_symbol_set(set: SymbolSet) -> Result<Self, Error> {
        Ok(set)
    }

    pub fn new_with_symbols_gen(symbols: SymbolsGen) -> Result<Self, Error> {
        Ok(Self {
            m_symbols: Some(Arc::new(symbols)),
            ..Self::empty()
        })
    }

    pub fn add_with_symbol_set(&mut self, set: SymbolSet) -> Result<(), Error> {
        let symbols = set.values()?;
        for symbol in symbols {
            self.add_in(symbol)?;
        }
        Ok(())
    }

    pub fn add_in(&mut self, symbol: CSymbol) -> Result<(), Error> {
        self.members
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .insert((symbol.m_yynum, symbol.text.clone()), symbol.symbol_ref());
        Ok(())
    }

    pub fn check_in(&mut self, symbol: CSymbol) -> Result<bool, Error> {
        let key = (symbol.m_yynum, symbol.text.clone());
        let mut members = self.members.lock().map_err(|_| Error::InvalidOperation)?;
        if members.contains_key(&key) {
            return Ok(false);
        }
        members.insert(key, symbol.symbol_ref());
        Ok(true)
    }

    pub fn contains(&self, symbol: CSymbol) -> Result<bool, Error> {
        Ok(self
            .members
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .contains_key(&(symbol.m_yynum, symbol.text.clone())))
    }

    pub fn get_enumerator(&self) -> Result<IDictionaryEnumerator, Error> {
        Ok(IDictionaryEnumerator)
    }

    pub fn print(&self) -> Result<(), Error> {
        println!(
            "[{}]",
            self.members
                .lock()
                .map_err(|_| Error::InvalidOperation)?
                .values()
                .map(|symbol| symbol.name.replace('\n', "\\n"))
                .collect::<Vec<_>>()
                .join(",")
        );
        Ok(())
    }

    pub fn resolve(&self) -> Result<Self, Error> {
        Ok(self.clone())
    }

    pub fn add_with_symbol_set_symbol_set(mut left: SymbolSet, right: SymbolSet) -> SymbolSet {
        let _ = left.add_with_symbol_set(right);
        left
    }

    #[must_use]
    pub fn count(&self) -> i32 {
        self.members.lock().map_or(0, |members| {
            i32::try_from(members.len()).unwrap_or(i32::MAX)
        })
    }

    #[must_use]
    pub const fn keys(&self) -> ICollection {
        ICollection
    }

    pub fn values(&self) -> Result<Vec<CSymbol>, Error> {
        Ok(self
            .members
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .values()
            .map(SymbolRef::to_symbol)
            .collect())
    }
}

/// Mapped precedence declaration kinds.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(i32)]
pub enum PrecedencePrecType {
    Left = 0,
    Right = 1,
    Nonassoc = 2,
    Before = 3,
    After = 4,
}

/// Linked compatibility precedence declaration.
#[derive(Clone, Debug)]
pub struct Precedence {
    pub m_next: Option<Box<Precedence>>,
    pub m_prec: i32,
    pub m_type: PrecedencePrecType,
}

impl Precedence {
    pub fn new(
        type_: PrecedencePrecType,
        precedence: i32,
        next: Precedence,
    ) -> Result<Self, Error> {
        if precedence < 0 {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_next: Some(Box::new(next)),
            m_prec: precedence,
            m_type: type_,
        })
    }

    /// Constructs the first declaration in a linked precedence list.
    pub fn first(type_: PrecedencePrecType, precedence: i32) -> Result<Self, Error> {
        if precedence < 0 {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_next: None,
            m_prec: precedence,
            m_type: type_,
        })
    }

    pub fn check_with_c_symbol_production_int32(
        symbol: CSymbol,
        production: Production,
        depth: i32,
    ) -> Result<i32, Error> {
        if depth < 0 {
            return Err(Error::Argument);
        }
        let Some(precedence) = symbol.m_prec.as_deref() else {
            return Ok(0);
        };
        let after = Self::check_type(Some(precedence), PrecedencePrecType::After);
        let infix = Self::check_type(Some(precedence), PrecedencePrecType::Left);
        Ok(after.max(infix) - production.m_prec)
    }

    pub fn check_with_precedence_prec_type_int32(
        precedence: Precedence,
        type_: PrecedencePrecType,
        depth: i32,
    ) -> Result<i32, Error> {
        if depth < 0 {
            return Err(Error::Argument);
        }
        Ok(Self::check_exact(Some(&precedence), type_))
    }

    pub fn check_with_production(mut production: Production) -> Result<(), Error> {
        let mut count = production.rhs.len();
        while count > 1
            && matches!(
                production.rhs[count - 1].m_symtype,
                CSymbolSymType::Oldaction | CSymbolSymType::Simpleaction
            )
        {
            count -= 1;
        }
        match count {
            2 if production.rhs[0].m_yynum == production.m_lhs.m_yynum => {
                let precedence = Self::check_exact(
                    production.rhs[1].m_prec.as_deref(),
                    PrecedencePrecType::After,
                );
                production.set_precedence(precedence)?;
            }
            2 if production.rhs[1].m_yynum == production.m_lhs.m_yynum => {
                let precedence = Self::check_exact(
                    production.rhs[0].m_prec.as_deref(),
                    PrecedencePrecType::Before,
                );
                production.set_precedence(precedence)?;
            }
            3 if production.rhs[2].m_yynum == production.m_lhs.m_yynum => {
                let precedence = Self::check_type(
                    production.rhs[1].m_prec.as_deref(),
                    PrecedencePrecType::Left,
                );
                production.set_precedence(precedence)?;
            }
            _ => {}
        }
        Ok(())
    }

    fn check_exact(mut value: Option<&Precedence>, type_: PrecedencePrecType) -> i32 {
        while let Some(precedence) = value {
            if precedence.m_type == type_ {
                return precedence.m_prec;
            }
            value = precedence.m_next.as_deref();
        }
        0
    }

    fn check_type(mut value: Option<&Precedence>, type_: PrecedencePrecType) -> i32 {
        while let Some(precedence) = value {
            if precedence.m_type == type_
                || (precedence.m_type as i32 <= PrecedencePrecType::Nonassoc as i32
                    && type_ as i32 <= PrecedencePrecType::Nonassoc as i32)
            {
                return precedence.m_prec;
            }
            value = precedence.m_next.as_deref();
        }
        0
    }
}

/// Compatibility production with a typed native right-hand side.
#[derive(Clone)]
pub struct Production {
    pub m_actions_only: bool,
    pub m_alias: Hashtable,
    pub m_lhs: CSymbol,
    pub m_pno: i32,
    pub m_prec: i32,
    pub m_rhs: ObjectList,
    rhs: Vec<CSymbol>,
    record: Arc<Mutex<ProductionRecord>>,
}

impl fmt::Debug for Production {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Production")
            .field("number", &self.m_pno)
            .field("lhs", &self.m_lhs)
            .field("rhs", &self.rhs)
            .field("precedence", &self.m_prec)
            .field("actions_only", &self.m_actions_only)
            .field("aliases", &self.m_alias)
            .field("mapped_rhs", &self.m_rhs)
            .finish()
    }
}

impl Production {
    pub fn new_with_symbols_gen(symbols: SymbolsGen) -> Result<Self, Error> {
        let number = symbols.pno;
        let mut lhs = CSymbol::placeholder();
        lhs.m_parser = Some(Arc::new(symbols));
        Self::empty(number, lhs)
    }

    pub fn new_with_symbols_gen_c_symbol(symbols: SymbolsGen, lhs: CSymbol) -> Result<Self, Error> {
        Self::empty(symbols.pno, lhs)
    }

    /// Creates a production independent of the generator facade.
    pub fn native(number: i32, lhs: CSymbol, rhs: Vec<CSymbol>) -> Result<Self, Error> {
        if number < 0 || lhs.is_terminal()? {
            return Err(Error::Argument);
        }
        let mut result = Self::empty(number, lhs)?;
        for symbol in rhs {
            result.add_to_rhs(symbol)?;
        }
        Ok(result)
    }

    fn empty(number: i32, mut lhs: CSymbol) -> Result<Self, Error> {
        let record = Arc::new(Mutex::new(ProductionRecord {
            number,
            actions_only: true,
            precedence: 0,
            rhs: Vec::new(),
        }));
        lhs.register_production(record.clone())?;
        Ok(Self {
            m_actions_only: true,
            m_alias: Hashtable::default(),
            m_lhs: lhs,
            m_pno: number,
            m_prec: 0,
            m_rhs: ObjectList::default(),
            rhs: Vec::new(),
            record,
        })
    }

    fn from_record(lhs: CSymbol, record: Arc<Mutex<ProductionRecord>>) -> Result<Self, Error> {
        let value = record.lock().map_err(|_| Error::InvalidOperation)?;
        let rhs = value
            .rhs
            .iter()
            .map(SymbolRef::to_symbol)
            .collect::<Vec<_>>();
        let mut mapped_rhs = ObjectList::default();
        for symbol in &rhs {
            let mut metadata = std::collections::HashMap::new();
            metadata.insert("name".to_owned(), Object::String(symbol.text.clone()));
            metadata.insert("number".to_owned(), Object::Integer(symbol.m_yynum));
            mapped_rhs.add(Object::Map(metadata))?;
        }
        Ok(Self {
            m_actions_only: value.actions_only,
            m_alias: Hashtable::default(),
            m_lhs: lhs,
            m_pno: value.number,
            m_prec: value.precedence,
            m_rhs: mapped_rhs,
            rhs,
            record: record.clone(),
        })
    }

    pub fn add_first(&self, symbol: CSymbol, position: i32) -> Result<(), Error> {
        let position = usize::try_from(position).map_err(|_| Error::Argument)?;
        if position > self.rhs.len() {
            return Err(Error::IndexOutOfRange);
        }
        let mut merged = symbol.m_follow.clone();
        for item in &self.rhs[position..] {
            merged.add_with_symbol_set(item.m_first.clone())?;
            if !item.is_nullable()? {
                break;
            }
        }
        Ok(())
    }

    pub fn add_to_rhs(&mut self, symbol: CSymbol) -> Result<(), Error> {
        self.m_actions_only &= matches!(
            symbol.m_symtype,
            CSymbolSymType::Oldaction | CSymbolSymType::Simpleaction
        );
        let mut metadata = std::collections::HashMap::new();
        metadata.insert("name".to_owned(), Object::String(symbol.text.clone()));
        metadata.insert("number".to_owned(), Object::Integer(symbol.m_yynum));
        self.m_rhs.add(Object::Map(metadata))?;
        self.rhs.push(symbol);
        let mut record = self.record.lock().map_err(|_| Error::InvalidOperation)?;
        record.actions_only = self.m_actions_only;
        record.rhs = self.rhs.iter().map(CSymbol::symbol_ref).collect();
        Ok(())
    }

    pub fn could_be_empty(&self, position: i32) -> Result<bool, Error> {
        let position = usize::try_from(position).map_err(|_| Error::Argument)?;
        if position > self.rhs.len() {
            return Err(Error::IndexOutOfRange);
        }
        for symbol in &self.rhs[position..] {
            if !symbol.is_nullable()? {
                return Ok(false);
            }
        }
        Ok(true)
    }

    pub fn prefix(&self, length: i32) -> Result<Vec<CSymbol>, Error> {
        let length = usize::try_from(length).map_err(|_| Error::Argument)?;
        if length > self.rhs.len() {
            return Err(Error::IndexOutOfRange);
        }
        Ok(self.rhs[..length].to_vec())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    pub fn stack_ref(&self, text: &mut String, _character: i32, index: i32) -> Result<(), Error> {
        let index = usize::try_from(index.checked_sub(1).ok_or(Error::Argument)?)
            .map_err(|_| Error::Argument)?;
        let symbol = self.rhs.get(index).ok_or(Error::IndexOutOfRange)?;
        let depth = self
            .rhs
            .len()
            .checked_sub(index + 2)
            .ok_or(Error::IndexOutOfRange)?;
        write!(
            text,
            "\n\t(({})(yyq.StackAt({depth}).m_value))\n\t",
            symbol.type_str()?
        )
        .map_err(|_| Error::InvalidOperation)?;
        Ok(())
    }

    fn set_precedence(&mut self, precedence: i32) -> Result<(), Error> {
        self.m_prec = precedence;
        self.record
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .precedence = precedence;
        Ok(())
    }
}

/// One dotted production used while constructing LR item sets.
#[derive(Clone, Debug)]
pub struct ProdItem {
    pub m_done: bool,
    pub m_pos: i32,
    pub m_prod: Production,
    follow: Option<SymbolSet>,
}

impl ProdItem {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            m_done: false,
            m_pos: 0,
            m_prod: Production::empty(0, CSymbol::placeholder())?,
            follow: None,
        })
    }

    pub fn new_with_production_int32(production: Production, position: i32) -> Result<Self, Error> {
        if position < 0
            || usize::try_from(position).map_or(true, |value| value > production.rhs.len())
        {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_done: false,
            m_pos: position,
            m_prod: production,
            follow: None,
        })
    }

    pub fn first_of_rest(&self, symbols: SymbolsGen) -> Result<SymbolSet, Error> {
        if let Some(follow) = &self.follow {
            return Ok(follow.clone());
        }
        let mut result = SymbolSet::new_with_symbols_gen(symbols)?;
        let start = usize::try_from(self.m_pos + 1).map_err(|_| Error::Argument)?;
        let mut nullable = true;
        for symbol in self.m_prod.rhs.get(start..).unwrap_or_default() {
            result.add_with_symbol_set(symbol.m_first.clone())?;
            if !symbol.is_nullable()? {
                nullable = false;
                break;
            }
        }
        if nullable {
            result.add_with_symbol_set(self.m_prod.m_lhs.m_follow.clone())?;
        }
        Ok(result)
    }

    pub fn is_reducing_action(&self) -> Result<bool, Error> {
        let position = usize::try_from(self.m_pos).map_err(|_| Error::Argument)?;
        Ok(position + 1 == self.m_prod.rhs.len()
            && self.m_prod.rhs.get(position).is_some_and(|symbol| {
                matches!(
                    symbol.m_symtype,
                    CSymbolSymType::Oldaction | CSymbolSymType::Simpleaction
                )
            }))
    }

    pub fn next(&self) -> Result<CSymbol, Error> {
        let position = usize::try_from(self.m_pos).map_err(|_| Error::Argument)?;
        self.m_prod
            .rhs
            .get(position)
            .cloned()
            .ok_or(Error::IndexOutOfRange)
    }

    pub fn print(&self) -> Result<(), Error> {
        let mut rhs = Vec::new();
        for (index, symbol) in self.m_prod.rhs.iter().enumerate() {
            if index == usize::try_from(self.m_pos).unwrap_or(usize::MAX) {
                rhs.push("_".to_owned());
            }
            rhs.push(symbol.text.replace('\n', "\\n"));
        }
        if usize::try_from(self.m_pos).ok() == Some(self.m_prod.rhs.len()) {
            rhs.push("_".to_owned());
        }
        println!(
            "{} {} : {}",
            self.m_prod.m_pno,
            self.m_prod.m_lhs.text,
            rhs.join(" ")
        );
        Ok(())
    }
}

/// Quoted grammar literal with deterministic C-style escape decoding.
#[derive(Clone, Debug)]
pub struct Literal {
    symbol: CSymbol,
}

impl Literal {
    pub fn new(symbols: SymbolsGen) -> Result<Self, Error> {
        let mut symbol = CSymbol::new_with_symbols_gen(symbols)?;
        symbol.m_symtype = CSymbolSymType::Terminal;
        Ok(Self { symbol })
    }

    /// Creates and decodes a quoted literal without generator state.
    pub fn native(text: &str, number: i32) -> Result<Self, Error> {
        let decoded = decode_grammar_literal(text)?;
        Ok(Self {
            symbol: CSymbol::native(decoded, number, CSymbolSymType::Terminal)?,
        })
    }

    pub fn could_start(&self, nonterminal: CSymbol) -> Result<bool, Error> {
        nonterminal.m_first.contains(self.symbol.clone())
    }

    pub fn resolve(&self) -> Result<CSymbol, Error> {
        Ok(self.symbol.clone())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    pub fn type_str(&self) -> Result<String, Error> {
        Ok("TOKEN".to_owned())
    }
}

fn decode_grammar_literal(text: &str) -> Result<String, Error> {
    let units = text.chars().collect::<Vec<_>>();
    if units.len() < 2 || units.first() != units.last() || !matches!(units[0], '\'' | '"') {
        return Err(Error::Parse {
            position: 0,
            context: "grammar literal must be quoted",
        });
    }
    let mut output = String::new();
    let mut index = 1;
    while index + 1 < units.len() {
        let mut value = units[index];
        if value == '\\' {
            index += 1;
            if index + 1 >= units.len() {
                return Err(Error::Parse {
                    position: index,
                    context: "grammar literal has a trailing escape",
                });
            }
            value = units[index];
            if ('0'..='7').contains(&value) {
                let start = index;
                let mut number = 0u32;
                let mut digits = 0;
                while index + 1 < units.len() && digits < 3 && ('0'..='7').contains(&units[index]) {
                    number = number * 8 + units[index].to_digit(8).unwrap_or(0);
                    index += 1;
                    digits += 1;
                }
                let character = char::from_u32(number).ok_or(Error::Parse {
                    position: start,
                    context: "grammar literal escape is invalid",
                })?;
                output.push(character);
                continue;
            }
            value = match value {
                'n' => '\n',
                'r' => '\r',
                't' => '\t',
                other => other,
            };
        }
        output.push(value);
        index += 1;
    }
    Ok(output)
}

/// Lightweight mapped parse-state descriptor.
#[derive(Clone)]
pub struct ParseState {
    pub m_accessing_symbol: CSymbol,
    pub m_sgen: Option<Arc<SymbolsGen>>,
    pub m_state: i32,
    pub m_transitions: Hashtable,
    items: Arc<Mutex<BTreeMap<(i32, i32), ProdItem>>>,
}

impl fmt::Debug for ParseState {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("ParseState")
            .field("state", &self.m_state)
            .field("accessing_symbol", &self.m_accessing_symbol)
            .field(
                "items",
                &self
                    .items
                    .lock()
                    .map_or_else(|_| Vec::new(), |items| items.keys().copied().collect()),
            )
            .finish()
    }
}

impl ParseState {
    fn numbered(state: i32) -> Self {
        Self {
            m_accessing_symbol: CSymbol::placeholder(),
            m_sgen: None,
            m_state: state,
            m_transitions: Hashtable::default(),
            items: Arc::new(Mutex::new(BTreeMap::new())),
        }
    }

    pub(crate) fn compatibility_numbered(state: i32) -> Self {
        Self::numbered(state)
    }

    pub fn new(symbols: SymbolsGen, accessing: CSymbol) -> Result<Self, Error> {
        Ok(Self {
            m_accessing_symbol: accessing,
            m_sgen: Some(Arc::new(symbols)),
            ..Self::numbered(0)
        })
    }

    pub fn accessor(&self, symbols: Vec<CSymbol>) -> Result<bool, Error> {
        Ok(symbols
            .last()
            .is_some_and(|symbol| symbol.m_yynum == self.m_accessing_symbol.m_yynum))
    }

    pub fn add_entries(&self) -> Result<(), Error> {
        Err(Error::InvalidOperation)
    }

    pub fn check_closure(&mut self, item: ProdItem) -> Result<(), Error> {
        self.maybe_add(item)
    }

    pub fn check_exists(&self) -> Result<Self, Error> {
        Ok(self.clone())
    }

    pub fn closure(&mut self) -> Result<(), Error> {
        let mut pending = self
            .items
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .values()
            .cloned()
            .collect::<VecDeque<_>>();
        while let Some(item) = pending.pop_front() {
            let Ok(symbol) = item.next() else {
                continue;
            };
            for production in symbol.registered_productions()? {
                let candidate = ProdItem::new_with_production_int32(production, 0)?;
                let key = (candidate.m_prod.m_pno, candidate.m_pos);
                let mut items = self.items.lock().map_err(|_| Error::InvalidOperation)?;
                if let std::collections::btree_map::Entry::Vacant(entry) = items.entry(key) {
                    entry.insert(candidate.clone());
                    pending.push_back(candidate);
                }
            }
        }
        Ok(())
    }

    pub fn get_transition(&self, symbol: CSymbol) -> Result<Transition, Error> {
        let Some(value) = self
            .m_transitions
            .0
            .get(&Object::String(symbol.text.clone()))
        else {
            return Err(Error::InvalidOperation);
        };
        value
            .downcast_ref::<Transition>()
            .cloned()
            .ok_or(Error::InvalidOperation)
    }

    pub fn lookback(&self, production: Production, state: ParseState) -> Result<bool, Error> {
        Ok(self.m_state == state.m_state
            && self
                .items
                .lock()
                .map_err(|_| Error::InvalidOperation)?
                .contains_key(&(
                    production.m_pno,
                    i32::try_from(production.rhs.len()).unwrap_or(i32::MAX),
                )))
    }

    pub fn maybe_add(&mut self, item: ProdItem) -> Result<(), Error> {
        self.items
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .entry((item.m_prod.m_pno, item.m_pos))
            .or_insert(item);
        Ok(())
    }

    pub fn print(&self) -> Result<(), Error> {
        println!("{}", self.stable_text());
        Ok(())
    }

    pub fn print0(&self) -> Result<(), Error> {
        self.print()
    }

    pub fn reduce_states(&self) -> Result<(), Error> {
        Err(Error::InvalidOperation)
    }

    pub fn same_as(&self, other: ParseState) -> Result<bool, Error> {
        let left = self
            .items
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .keys()
            .copied()
            .collect::<Vec<_>>();
        let right = other
            .items
            .lock()
            .map_err(|_| Error::InvalidOperation)?
            .keys()
            .copied()
            .collect::<Vec<_>>();
        Ok(left == right)
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    fn stable_text(&self) -> String {
        let item_count = self.items.lock().map_or(0, |items| items.len());
        format!(
            "state {} items {} transitions {}",
            self.m_state,
            item_count,
            self.m_transitions.0.len()
        )
    }
}

/// Parser table metadata for one symbol.
#[derive(Clone, Debug)]
pub struct ParsingInfo {
    pub m_name: String,
    pub m_parsetable: Hashtable,
    pub m_yynum: i32,
}

impl ParsingInfo {
    pub fn new(name: String, number: i32) -> Result<Self, Error> {
        if name.is_empty() || number < ERROR_TOKEN {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_name: name,
            m_parsetable: Hashtable::default(),
            m_yynum: number,
        })
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }
}

/// Executable parser table corresponding to the mapped `YyParser` concept.
#[derive(Clone)]
pub struct YyParser {
    pub eof_symbol: CSymbol,
    pub special: CSymbol,
    pub arr: Vec<i32>,
    pub erh: crate::ErrorHandler,
    pub literals: Hashtable,
    pub m_accept: ParseState,
    pub m_concrete: bool,
    pub m_start_symbol: CSymbol,
    pub m_states: Hashtable,
    pub symbol_info: Hashtable,
    pub symbols: Hashtable,
    pub types: Hashtable,
    machine: Option<Arc<ParserMachine>>,
}

impl fmt::Debug for YyParser {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("YyParser")
            .field("start", &self.start_symbol())
            .field(
                "states",
                &self
                    .machine
                    .as_ref()
                    .map_or(0, |machine| machine.states.len()),
            )
            .finish()
    }
}

impl YyParser {
    pub fn new() -> Result<Self, Error> {
        Ok(Self::empty())
    }

    fn empty() -> Self {
        Self {
            eof_symbol: CSymbol::native("EOF", 2, CSymbolSymType::Eofsymbol)
                .unwrap_or_else(|_| CSymbol::placeholder()),
            special: CSymbol::native("S'", 0, CSymbolSymType::Nonterminal)
                .unwrap_or_else(|_| CSymbol::placeholder()),
            arr: Vec::new(),
            erh: crate::ErrorHandler::default(),
            literals: Hashtable::default(),
            m_accept: ParseState::numbered(0),
            m_concrete: false,
            m_start_symbol: CSymbol::placeholder(),
            m_states: Hashtable::default(),
            symbol_info: Hashtable::default(),
            symbols: Hashtable::default(),
            types: Hashtable::default(),
            machine: None,
        }
    }

    fn from_machine(machine: ParserMachine) -> Self {
        let mut parser = Self::empty();
        let start = machine
            .symbols
            .get(&machine.start)
            .cloned()
            .unwrap_or(GrammarSymbol {
                name: "<start>".to_owned(),
                number: machine.start,
                terminal: false,
            });
        parser.m_start_symbol = CSymbol::native(
            start.name.clone(),
            start.number,
            CSymbolSymType::Nonterminal,
        )
        .unwrap_or_else(|_| CSymbol::placeholder());
        let eof = machine
            .symbols
            .get(&machine.eof)
            .cloned()
            .unwrap_or(GrammarSymbol {
                name: "EOF".to_owned(),
                number: machine.eof,
                terminal: true,
            });
        parser.eof_symbol = CSymbol::native(eof.name, eof.number, CSymbolSymType::Eofsymbol)
            .unwrap_or_else(|_| CSymbol::placeholder());
        parser.m_accept = ParseState::numbered(
            machine
                .actions
                .iter()
                .find_map(|((state, token), action)| {
                    (*token == machine.eof && *action == TableAction::Accept)
                        .then_some(i32::try_from(*state).unwrap_or(i32::MAX))
                })
                .unwrap_or(0),
        );
        for symbol in machine.symbols.values() {
            parser.symbols.0.insert(
                Object::String(symbol.name.clone()),
                Object::Integer(symbol.number),
            );
            let mut states = std::collections::HashMap::new();
            for ((state, number), action) in &machine.actions {
                if *number == symbol.number {
                    let mut metadata = std::collections::HashMap::new();
                    metadata.insert("priority".to_owned(), Object::Integer(0));
                    metadata.insert(
                        "action".to_owned(),
                        Object::String(ParserMachine::action_text(*action)),
                    );
                    states.insert(state.to_string(), Object::Map(metadata));
                }
            }
            parser
                .symbol_info
                .0
                .insert(Object::Integer(symbol.number), Object::Map(states));
        }
        for (literal, number) in &machine.literals {
            parser
                .literals
                .0
                .insert(Object::String(literal.clone()), Object::Integer(*number));
        }
        parser.machine = Some(Arc::new(machine));
        parser
    }

    pub fn action(&self, _parser: Parser, symbol: SYMBOL, _action: i32) -> Result<Object, Error> {
        Ok(symbol.m_dollar)
    }

    pub fn class_init(&mut self, _symbols: SymbolsGen) -> Result<(), Error> {
        self.special = CSymbol::native("S'", 0, CSymbolSymType::Nonterminal)?;
        self.eof_symbol = CSymbol::native("EOF", 2, CSymbolSymType::Eofsymbol)?;
        Ok(())
    }

    pub fn emit(&self, mut output: Box<dyn Write + Send>) -> Result<(), Error> {
        let machine = self.machine.as_deref().ok_or(Error::InvalidOperation)?;
        output
            .write_all(machine.stable_description().as_bytes())
            .map_err(|_| Error::InvalidOperation)?;
        output.write_all(b"\n").map_err(|_| Error::InvalidOperation)
    }

    pub fn get_eof(&mut self, _lexer: Lexer) -> Result<(), Error> {
        if self.eof_symbol.m_yynum < 0 {
            let eof = self.machine.as_deref().map_or(2, |machine| machine.eof);
            self.eof_symbol = CSymbol::native("EOF", eof, CSymbolSymType::Eofsymbol)?;
        }
        Ok(())
    }

    pub fn get_parser(&self, _lexer: Lexer) -> Result<(), Error> {
        self.machine
            .as_ref()
            .map(|_| ())
            .ok_or(Error::InvalidOperation)
    }

    pub fn get_symbol_info(&mut self, name: String, number: i32) -> Result<ParsingInfo, Error> {
        let mut info = ParsingInfo::new(name, number)?;
        if let Some(Object::Map(states)) = self.symbol_info.0.get(&Object::Integer(number)) {
            for (state, value) in states {
                info.m_parsetable
                    .0
                    .insert(Object::String(state.clone()), value.clone());
            }
        }
        Ok(info)
    }

    pub fn print_transitions(&self, _function: Func, suffix: String) -> Result<(), Error> {
        let machine = self.machine.as_deref().ok_or(Error::InvalidOperation)?;
        for ((state, symbol), target) in &machine.gotos {
            println!("{state} --{symbol}--> {target}{suffix}");
        }
        Ok(())
    }

    pub fn transitions(&self, _builder: Builder) -> Result<(), Error> {
        Err(Error::InvalidOperation)
    }

    #[must_use]
    pub fn start_symbol(&self) -> String {
        if self.m_start_symbol.text.is_empty() {
            "<null>".to_owned()
        } else {
            self.m_start_symbol.text.clone()
        }
    }

    pub fn set_start_symbol(&mut self, value: String) {
        if let Some(machine) = &self.machine
            && let Some(symbol) = machine
                .symbols
                .values()
                .find(|symbol| symbol.name == value && !symbol.terminal)
            && let Ok(native) = CSymbol::native(
                symbol.name.clone(),
                symbol.number,
                CSymbolSymType::Nonterminal,
            )
        {
            self.m_start_symbol = native;
        }
    }

    pub fn conflicts(&self) -> &[ParserConflict] {
        self.machine
            .as_deref()
            .map_or(&[], |machine| machine.conflicts.as_slice())
    }

    pub(crate) fn lookup_entry(
        &self,
        number: i32,
        state: i32,
    ) -> Result<Option<ParserEntry>, Error> {
        let state = usize::try_from(state).map_err(|_| Error::Argument)?;
        let Some(machine) = &self.machine else {
            return Ok(None);
        };
        Ok(machine
            .actions
            .get(&(state, number))
            .copied()
            .map(ParserEntry::from_table_action))
    }
}

#[derive(Clone, Debug, Default)]
struct ParserRuntime {
    stack: Vec<ParseStackEntry>,
    ungot: Option<SYMBOL>,
    diagnostics: Vec<Diagnostic>,
    recovered: usize,
}

/// Bounded table-driven parser retaining the mapped C# entry points.
#[derive(Clone)]
pub struct Parser {
    pub m_debug: bool,
    pub m_lexer: Lexer,
    pub m_stkdebug: bool,
    pub m_symbols: YyParser,
    runtime: Arc<Mutex<ParserRuntime>>,
}

impl fmt::Debug for Parser {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Parser")
            .field("debug", &self.m_debug)
            .field("stack_debug", &self.m_stkdebug)
            .field("symbols", &self.m_symbols)
            .finish()
    }
}

impl Parser {
    pub fn new(symbols: YyParser, lexer: Lexer) -> Result<Self, Error> {
        if symbols.machine.is_none() {
            return Err(Error::InvalidOperation);
        }
        Ok(Self {
            m_debug: false,
            m_lexer: lexer,
            m_stkdebug: false,
            m_symbols: symbols,
            runtime: Arc::new(Mutex::new(ParserRuntime::default())),
        })
    }

    pub fn error(&mut self, number: i32, symbol: SYMBOL, message: String) -> Result<(), Error> {
        let position = usize::try_from(symbol.pos_with_field).unwrap_or(0);
        let location = self
            .m_lexer
            .source_line_info(symbol.pos_with_field)
            .unwrap_or_else(|_| SourceLineInfo::default());
        self.record(Diagnostic {
            code: number,
            category: DiagnosticCategory::Syntax,
            severity: DiagnosticSeverity::Error,
            message,
            location,
            input: symbol.to_string(),
        })?;
        Err(Error::Parse {
            position,
            context: "parser semantic action reported an error",
        })
    }

    pub fn next_sym(&mut self) -> Result<SYMBOL, Error> {
        if let Some(symbol) = self.runtime_lock()?.ungot.take() {
            return Ok(symbol);
        }
        match self.m_lexer.next_token()? {
            Some(token) => Ok(self.symbol_from_token(token)),
            None => Ok(SYMBOL::parser_symbol(
                self.m_symbols.eof_symbol.text.clone(),
                self.m_symbols.eof_symbol.m_yynum,
                "EOF".to_owned(),
                self.m_lexer.yypos(),
                Object::Undefined,
                ObjectList::default(),
            )),
        }
    }

    pub fn parse_with_cs_reader(&mut self, input: CsReader) -> Result<SYMBOL, Error> {
        self.m_lexer.start_with_cs_reader(input)?;
        self.parse_started()
    }

    pub fn parse_with_stream_reader(&mut self, input: StreamReader) -> Result<SYMBOL, Error> {
        self.m_lexer.start_with_stream_reader(input)?;
        self.parse_started()
    }

    pub fn parse_with_string(&mut self, input: String) -> Result<SYMBOL, Error> {
        self.m_lexer.start_with_string(input)?;
        self.parse_started()
    }

    pub fn stack_at(&self, index: i32) -> Result<ParseStackEntry, Error> {
        let index = usize::try_from(index).map_err(|_| Error::Argument)?;
        let runtime = self.runtime_lock()?;
        let position = runtime
            .stack
            .len()
            .checked_sub(index + 1)
            .ok_or(Error::IndexOutOfRange)?;
        runtime
            .stack
            .get(position)
            .cloned()
            .ok_or(Error::IndexOutOfRange)
    }

    pub fn diagnostics(&self) -> Result<Vec<Diagnostic>, Error> {
        Ok(self.runtime_lock()?.diagnostics.clone())
    }

    fn parse_started(&mut self) -> Result<SYMBOL, Error> {
        let machine = self
            .m_symbols
            .machine
            .clone()
            .ok_or(Error::InvalidOperation)?;
        {
            let mut runtime = self.runtime_lock()?;
            runtime.stack.clear();
            runtime.ungot = None;
            runtime.diagnostics.clear();
            runtime.recovered = 0;
        }
        let mut states = vec![0usize];
        let mut nodes = Vec::<ParseTree>::new();
        let mut token = self.m_lexer.next_token()?;
        let mut steps = 0usize;
        loop {
            steps = steps.checked_add(1).ok_or(Error::IndexOutOfRange)?;
            if steps > MAX_PARSER_STEPS {
                self.record_limit_diagnostic(token.as_ref(), "parser step limit exceeded")?;
                return Err(Error::Parse {
                    position: token
                        .as_ref()
                        .map_or(0, |value| usize::try_from(value.pos).unwrap_or(0)),
                    context: "parser step limit exceeded",
                });
            }
            if states.len() > MAX_PARSER_STACK {
                self.record_limit_diagnostic(token.as_ref(), "parser stack limit exceeded")?;
                return Err(Error::Parse {
                    position: token
                        .as_ref()
                        .map_or(0, |value| usize::try_from(value.pos).unwrap_or(0)),
                    context: "parser stack limit exceeded",
                });
            }
            let raw_number = token.as_ref().map_or(machine.eof, TOKEN::yynum);
            let number = if raw_number == 1 {
                token
                    .as_ref()
                    .and_then(|value| machine.literals.get(&value.yytext()))
                    .copied()
                    .unwrap_or(raw_number)
            } else {
                raw_number
            };
            let state = *states.last().ok_or(Error::InvalidOperation)?;
            match machine.actions.get(&(state, number)).copied() {
                Some(TableAction::Shift(target)) => {
                    let value = token.take().ok_or(Error::InvalidOperation)?;
                    nodes.push(ParseTree::token(&value, number));
                    states.push(target);
                    token = self.m_lexer.next_token()?;
                }
                Some(TableAction::Reduce(production_number)) => {
                    let production = machine
                        .productions
                        .get(production_number + 1)
                        .ok_or(Error::InvalidOperation)?;
                    let count = production.rhs.len();
                    if count >= states.len() || count > nodes.len() {
                        return Err(Error::Parse {
                            position: token
                                .as_ref()
                                .map_or(0, |value| usize::try_from(value.pos).unwrap_or(0)),
                            context: "parser reduction underflow",
                        });
                    }
                    let children = nodes.split_off(nodes.len() - count);
                    states.truncate(states.len() - count);
                    let source = machine
                        .symbols
                        .get(&production.lhs)
                        .ok_or(Error::InvalidOperation)?;
                    let start = children.first().map_or_else(
                        || token.as_ref().map_or(0, |value| value.pos),
                        |value| value.start,
                    );
                    let end = children.last().map_or(start, |value| value.end);
                    let node = ParseTree {
                        name: source.name.clone(),
                        number: source.number,
                        text: None,
                        start,
                        end,
                        children,
                    };
                    let base = *states.last().ok_or(Error::InvalidOperation)?;
                    let target = machine.gotos.get(&(base, production.lhs)).copied().ok_or(
                        Error::Parse {
                            position: usize::try_from(start).unwrap_or(0),
                            context: "parser goto table is incomplete",
                        },
                    )?;
                    nodes.push(node);
                    states.push(target);
                }
                Some(TableAction::Accept) => {
                    let tree = nodes.pop().ok_or(Error::InvalidOperation)?;
                    let recovered = self.runtime_lock()?.recovered;
                    let name = if recovered == 0 {
                        tree.name.clone()
                    } else {
                        "recoveredError".to_owned()
                    };
                    let mut kids = ObjectList::default();
                    for child in &tree.children {
                        kids.add(Object::opaque(child.clone()))?;
                    }
                    return Ok(SYMBOL::parser_symbol(
                        name,
                        tree.number,
                        String::new(),
                        tree.start,
                        Object::opaque(tree),
                        kids,
                    ));
                }
                Some(TableAction::Reject) | None => {
                    if !self.recover(&machine, &mut states, &mut nodes, &mut token)? {
                        return Err(Error::Parse {
                            position: token
                                .as_ref()
                                .map_or(0, |value| usize::try_from(value.pos).unwrap_or(0)),
                            context: "syntax error",
                        });
                    }
                }
            }
        }
    }

    fn recover(
        &mut self,
        machine: &ParserMachine,
        states: &mut Vec<usize>,
        nodes: &mut Vec<ParseTree>,
        token: &mut Option<TOKEN>,
    ) -> Result<bool, Error> {
        let position = token
            .as_ref()
            .map_or(self.m_lexer.yypos(), |value| value.pos);
        let input = token
            .as_ref()
            .map_or_else(|| "EOF".to_owned(), TOKEN::yytext);
        let location = self
            .m_lexer
            .source_line_info(position)
            .unwrap_or_else(|_| SourceLineInfo::default());
        self.record(Diagnostic {
            code: 13,
            category: DiagnosticCategory::Syntax,
            severity: DiagnosticSeverity::Error,
            message: "syntax error".to_owned(),
            location,
            input,
        })?;
        {
            let mut runtime = self.runtime_lock()?;
            runtime.recovered += 1;
            if runtime.recovered > MAX_RECOVERY_ERRORS {
                return Ok(false);
            }
        }

        let recovery_state = loop {
            let state = *states.last().ok_or(Error::InvalidOperation)?;
            if let Some(TableAction::Shift(target)) = machine.actions.get(&(state, ERROR_TOKEN)) {
                break Some(*target);
            }
            if states.len() <= 1 {
                break None;
            }
            states.pop();
            nodes.pop();
        };
        let Some(recovery_state) = recovery_state else {
            return Ok(false);
        };
        nodes.push(ParseTree::error(position));
        states.push(recovery_state);
        loop {
            let state = *states.last().ok_or(Error::InvalidOperation)?;
            let raw_number = token.as_ref().map_or(machine.eof, TOKEN::yynum);
            let number = if raw_number == 1 {
                token
                    .as_ref()
                    .and_then(|value| machine.literals.get(&value.yytext()))
                    .copied()
                    .unwrap_or(raw_number)
            } else {
                raw_number
            };
            if machine
                .actions
                .get(&(state, number))
                .is_some_and(|action| *action != TableAction::Reject)
            {
                let location = self
                    .m_lexer
                    .source_line_info(position)
                    .unwrap_or_else(|_| SourceLineInfo::default());
                self.record(Diagnostic {
                    code: 13,
                    category: DiagnosticCategory::ParserRecovery,
                    severity: DiagnosticSeverity::Warning,
                    message: "parser recovered at a valid lookahead".to_owned(),
                    location,
                    input: token
                        .as_ref()
                        .map_or_else(|| "EOF".to_owned(), TOKEN::yytext),
                })?;
                return Ok(true);
            }
            if token.is_none() {
                return Ok(false);
            }
            *token = self.m_lexer.next_token()?;
        }
    }

    fn symbol_from_token(&self, token: TOKEN) -> SYMBOL {
        let number = if token.yynum() == 1 {
            self.m_symbols
                .machine
                .as_deref()
                .and_then(|machine| machine.literals.get(&token.yytext()))
                .copied()
                .unwrap_or(1)
        } else {
            token.yynum()
        };
        SYMBOL::parser_symbol(
            token.yyname(),
            number,
            token.yytext(),
            token.pos,
            token.yylval(),
            ObjectList::default(),
        )
    }

    fn record(&mut self, diagnostic: Diagnostic) -> Result<(), Error> {
        self.m_symbols.erh.push(diagnostic.clone())?;
        self.runtime_lock()?.diagnostics.push(diagnostic);
        Ok(())
    }

    fn record_limit_diagnostic(
        &mut self,
        token: Option<&TOKEN>,
        message: &str,
    ) -> Result<(), Error> {
        let position = token.map_or(self.m_lexer.yypos(), |value| value.pos);
        let location = self
            .m_lexer
            .source_line_info(position)
            .unwrap_or_else(|_| SourceLineInfo::default());
        self.record(Diagnostic {
            code: 14,
            category: DiagnosticCategory::ParserStackLimit,
            severity: DiagnosticSeverity::Fatal,
            message: message.to_owned(),
            location,
            input: token.map_or_else(|| "EOF".to_owned(), TOKEN::yytext),
        })
    }

    fn runtime_lock(&self) -> Result<std::sync::MutexGuard<'_, ParserRuntime>, Error> {
        self.runtime.lock().map_err(|_| Error::InvalidOperation)
    }
}

/// Mapped parser stack entry. Parser clones share their runtime stack.
#[derive(Clone, Debug)]
pub struct ParseStackEntry {
    pub m_state: i32,
    pub m_value: SYMBOL,
    pub yyps: Parser,
}

impl ParseStackEntry {
    pub fn new_with_parser(parser: Parser) -> Result<Self, Error> {
        Ok(Self {
            m_state: 0,
            m_value: SYMBOL::parser_symbol(
                "Null".to_owned(),
                -1,
                String::new(),
                0,
                Object::Undefined,
                ObjectList::default(),
            ),
            yyps: parser,
        })
    }

    pub fn new_with_parser_int32_symbol(
        parser: Parser,
        state: i32,
        value: SYMBOL,
    ) -> Result<Self, Error> {
        if state < 0 {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_state: state,
            m_value: value,
            yyps: parser,
        })
    }
}

/// Semantic action metadata attached to a shift or reduction.
#[derive(Clone, Debug)]
pub struct ParserAction {
    pub m_len: i32,
    pub m_sym: CSymbol,
}

impl ParserAction {
    pub fn new(_symbols: SymbolsGen) -> Result<Self, Error> {
        Ok(Self {
            m_len: 0,
            m_sym: CSymbol::placeholder(),
        })
    }

    pub fn native(symbol: CSymbol, length: i32) -> Result<Self, Error> {
        if length < 0 {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_len: length,
            m_sym: symbol,
        })
    }

    pub fn act_num(&self) -> Result<i32, Error> {
        Ok(0)
    }

    pub fn action(&self, parser: Parser) -> Result<SYMBOL, Error> {
        let source = if self.m_len == 0 {
            None
        } else {
            parser
                .stack_at(self.m_len - 1)
                .ok()
                .map(|entry| entry.m_value)
        };
        Ok(SYMBOL::parser_symbol(
            self.m_sym.text.clone(),
            self.m_sym.m_yynum,
            String::new(),
            source.as_ref().map_or(0, |symbol| symbol.pos_with_field),
            source.map_or(Object::Undefined, |symbol| symbol.m_dollar),
            ObjectList::default(),
        ))
    }

    pub fn is_action(&self) -> Result<bool, Error> {
        Ok(true)
    }

    pub fn print(&self) -> Result<(), Error> {
        print!("{}", self.m_sym.text);
        Ok(())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }
}

/// Executable action kind stored in one parse-table cell.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum EntryKind {
    Error,
    Shift(usize),
    Reduce(usize),
    Accept,
    Reject,
}

/// Base mapped parser entry and native table-cell representation.
#[derive(Clone, Debug)]
pub struct ParserEntry {
    pub m_action: Option<ParserAction>,
    pub m_priority: i32,
    kind: EntryKind,
}

impl Default for ParserEntry {
    fn default() -> Self {
        Self {
            m_action: None,
            m_priority: 0,
            kind: EntryKind::Error,
        }
    }
}

impl ParserEntry {
    fn from_table_action(action: TableAction) -> Self {
        let kind = match action {
            TableAction::Shift(state) => EntryKind::Shift(state),
            TableAction::Reduce(production) => EntryKind::Reduce(production),
            TableAction::Accept => EntryKind::Accept,
            TableAction::Reject => EntryKind::Reject,
        };
        Self {
            m_action: None,
            m_priority: 0,
            kind,
        }
    }

    pub fn is_reduce(&self) -> Result<bool, Error> {
        Ok(matches!(self.kind, EntryKind::Reduce(_)))
    }

    pub fn pass_(&self, top: &mut ParseStackEntry) -> Result<(), Error> {
        match self.kind {
            EntryKind::Shift(state) => {
                top.yyps.runtime_lock()?.stack.push(top.clone());
                top.m_state = i32::try_from(state).map_err(|_| Error::IndexOutOfRange)?;
                top.m_value = top.yyps.next_sym()?;
                Ok(())
            }
            EntryKind::Accept => Ok(()),
            EntryKind::Reduce(_) | EntryKind::Reject | EntryKind::Error => {
                Err(Error::InvalidOperation)
            }
        }
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    #[must_use]
    pub fn str(&self) -> String {
        match self.kind {
            EntryKind::Error => String::new(),
            EntryKind::Shift(state) => format!("shift {state}"),
            EntryKind::Reduce(production) => format!("reduce {production}"),
            EntryKind::Accept => "accept".to_owned(),
            EntryKind::Reject => "nonassociative conflict".to_owned(),
        }
    }
}

/// Mapped shift entry.
#[derive(Clone, Debug)]
pub struct ParserShift {
    pub m_next: Option<Box<ParseState>>,
    action: Option<ParserAction>,
}

impl ParserShift {
    pub fn new_with_constructor() -> Result<Self, Error> {
        Ok(Self {
            m_next: None,
            action: None,
        })
    }

    pub fn new_with_parser_action_parse_state(
        action: ParserAction,
        next: ParseState,
    ) -> Result<Self, Error> {
        Ok(Self {
            m_next: Some(Box::new(next)),
            action: Some(action),
        })
    }

    pub fn pass_(&self, top: &mut ParseStackEntry) -> Result<(), Error> {
        let target = self
            .m_next
            .as_deref()
            .ok_or(Error::InvalidOperation)?
            .m_state;
        if let Some(action) = &self.action {
            let symbol = action.action(top.yyps.clone())?;
            top.yyps.runtime_lock()?.stack.push(ParseStackEntry {
                m_state: top.m_state,
                m_value: symbol,
                yyps: top.yyps.clone(),
            });
            top.m_state = target;
        } else {
            top.yyps.runtime_lock()?.stack.push(top.clone());
            top.m_state = target;
            top.m_value = top.yyps.next_sym()?;
        }
        Ok(())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    #[must_use]
    pub fn str(&self) -> String {
        self.m_next.as_deref().map_or_else(
            || "?? null shift".to_owned(),
            |state| format!("shift {}", state.m_state),
        )
    }
}

/// Mapped reduction entry.
#[derive(Clone, Debug)]
pub struct ParserReduce {
    pub m_depth: i32,
    pub m_look_ahead: SymbolSet,
    pub m_prod: Production,
    action: ParserAction,
}

impl ParserReduce {
    pub fn new(action: ParserAction, depth: i32, production: Production) -> Result<Self, Error> {
        if depth < 0 {
            return Err(Error::Argument);
        }
        Ok(Self {
            m_depth: depth,
            m_look_ahead: SymbolSet::empty(),
            m_prod: production,
            action,
        })
    }

    pub fn build_lookback(&mut self, transition: Transition) -> Result<(), Error> {
        self.m_look_ahead = Transition::follow(transition)?;
        Ok(())
    }

    pub fn is_reduce(&self) -> Result<bool, Error> {
        Ok(true)
    }

    pub fn pass_(&self, top: &mut ParseStackEntry) -> Result<(), Error> {
        let symbol = self.action.action(top.yyps.clone())?;
        let depth = usize::try_from(self.m_depth).map_err(|_| Error::Argument)?;
        let mut runtime = top.yyps.runtime_lock()?;
        if depth > runtime.stack.len() {
            return Err(Error::Parse {
                position: usize::try_from(top.m_value.pos_with_field).unwrap_or(0),
                context: "parser reduction underflow",
            });
        }
        runtime.ungot = Some(top.m_value.clone());
        for _ in 0..depth {
            if let Some(entry) = runtime.stack.pop() {
                top.m_state = entry.m_state;
            }
        }
        top.m_value = symbol;
        Ok(())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    #[must_use]
    pub fn str(&self) -> String {
        format!("reduce {}", self.m_prod.m_pno)
    }
}

/// Old-style numbered semantic action.
#[derive(Clone, Debug)]
pub struct ParserOldAction {
    pub m_action: i32,
    base: ParserAction,
}

impl ParserOldAction {
    pub fn new(symbols: SymbolsGen) -> Result<Self, Error> {
        let action = symbols.action;
        Ok(Self {
            m_action: action,
            base: ParserAction::new(symbols)?,
        })
    }

    pub fn act_num(&self) -> Result<i32, Error> {
        Ok(self.m_action)
    }

    pub fn action(&self, parser: Parser) -> Result<SYMBOL, Error> {
        let mut symbol = self.base.action(parser.clone())?;
        let value = parser
            .m_symbols
            .clone()
            .action(parser, symbol.clone(), self.m_action)?;
        if !matches!(value, Object::Undefined) {
            symbol.m_dollar = value;
        }
        Ok(symbol)
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }
}

/// Simple grammar action that only constructs its declared symbol.
#[derive(Clone, Debug)]
pub struct ParserSimpleAction {
    base: ParserAction,
}

impl ParserSimpleAction {
    pub fn new(symbols: SymbolsGen) -> Result<Self, Error> {
        Ok(Self {
            base: ParserAction::new(symbols)?,
        })
    }

    pub fn print(&self) -> Result<(), Error> {
        print!(" %{}", self.base.m_sym.text);
        Ok(())
    }

    pub fn serialise(o: Object, _serializer: Serialiser) -> Result<Object, Error> {
        let _ = o;
        Err(Error::InvalidOperation)
    }

    pub fn type_str(&self) -> Result<String, Error> {
        Ok(self.base.m_sym.text.clone())
    }
}

#[derive(Clone, Debug)]
struct TransitionCore {
    direct_reads: SymbolSet,
    reads: Hashtable,
    includes: Hashtable,
    follow: SymbolSet,
}

/// Mapped LALR transition relations backed by shared native relation state.
#[derive(Clone, Debug)]
pub struct Transition {
    pub m_a: CSymbol,
    pub m_next: ParserShift,
    pub m_ps: Box<ParseState>,
    pub m_reduce: Hashtable,
    pub m_tno: i32,
    core: Arc<Mutex<TransitionCore>>,
}

impl Transition {
    pub fn new(state: ParseState, symbol: CSymbol) -> Result<Self, Error> {
        let number = state.m_state;
        Ok(Self {
            m_a: symbol,
            m_next: ParserShift::new_with_constructor()?,
            m_ps: Box::new(state),
            m_reduce: Hashtable::default(),
            m_tno: number,
            core: Arc::new(Mutex::new(TransitionCore {
                direct_reads: SymbolSet::empty(),
                reads: Hashtable::default(),
                includes: Hashtable::default(),
                follow: SymbolSet::empty(),
            })),
        })
    }

    pub fn add_to_follow(transition: Transition, set: SymbolSet) -> Result<(), Error> {
        transition.core_lock()?.follow.add_with_symbol_set(set)
    }

    pub fn add_to_read(transition: Transition, set: SymbolSet) -> Result<(), Error> {
        transition
            .core_lock()?
            .direct_reads
            .add_with_symbol_set(set)
    }

    pub fn build_dr(transition: Transition) -> Result<(), Error> {
        let symbol = transition.m_a.clone();
        if symbol.is_terminal()? {
            transition.core_lock()?.direct_reads.add_in(symbol)?;
        }
        Ok(())
    }

    pub fn build_includes(transition: Transition) -> Result<(), Error> {
        let key = Object::Integer(transition.m_tno);
        transition
            .core_lock()?
            .includes
            .0
            .insert(key, Object::Boolean(true));
        Ok(())
    }

    pub fn build_la(transition: Transition) -> Result<(), Error> {
        let direct = transition.core_lock()?.direct_reads.clone();
        transition.core_lock()?.follow.add_with_symbol_set(direct)
    }

    pub fn build_lookback(transition: Transition) -> Result<(), Error> {
        Self::build_includes(transition)
    }

    pub fn build_parse_table(transition: Transition) -> Result<(), Error> {
        let _ = transition;
        Err(Error::InvalidOperation)
    }

    pub fn build_reads(transition: Transition) -> Result<(), Error> {
        let key = Object::Integer(transition.m_tno);
        transition
            .core_lock()?
            .reads
            .0
            .insert(key, Object::Boolean(true));
        Ok(())
    }

    pub fn dr(transition: Transition) -> Result<SymbolSet, Error> {
        Ok(transition.core_lock()?.direct_reads.clone())
    }

    pub fn final_(transition: Transition) -> Result<(), Error> {
        Self::build_la(transition)
    }

    pub fn follow(transition: Transition) -> Result<SymbolSet, Error> {
        Ok(transition.core_lock()?.follow.clone())
    }

    pub fn print(&self, set: SymbolSet, suffix: String) -> Result<(), Error> {
        println!(
            "transition {} {} -> [{}]{}",
            self.m_tno,
            self.m_a.text,
            set.values()?
                .iter()
                .map(|symbol| symbol.text.as_str())
                .collect::<Vec<_>>()
                .join(","),
            suffix
        );
        Ok(())
    }

    pub fn print0(&self) -> Result<(), Error> {
        self.print(self.core_lock()?.follow.clone(), String::new())
    }

    pub fn read(transition: Transition) -> Result<SymbolSet, Error> {
        Self::dr(transition)
    }

    pub fn includes(transition: Transition) -> Result<Hashtable, Error> {
        Ok(transition.core_lock()?.includes.clone())
    }

    pub fn reads(transition: Transition) -> Result<Hashtable, Error> {
        Ok(transition.core_lock()?.reads.clone())
    }

    fn core_lock(&self) -> Result<std::sync::MutexGuard<'_, TransitionCore>, Error> {
        self.core.lock().map_err(|_| Error::InvalidOperation)
    }
}

/// Syntax error symbol preserving the state and offending symbol.
#[derive(Clone, Debug)]
pub struct LslError {
    pub state: i32,
    pub sym: SYMBOL,
}

impl LslError {
    pub fn new_with_parser(parser: Parser) -> Result<Self, Error> {
        Ok(Self {
            state: 0,
            sym: SYMBOL::new_with_parser(parser)?,
        })
    }

    pub fn new_with_parser_parse_stack_entry(
        _parser: Parser,
        entry: ParseStackEntry,
    ) -> Result<Self, Error> {
        Ok(Self {
            state: entry.m_state,
            sym: entry.m_value,
        })
    }

    #[must_use]
    pub fn to_string(&self) -> String {
        if self.sym.yyname() == "TOKEN" {
            format!(
                "syntax error occurred in state {} on input token {}",
                self.state,
                self.sym.to_string()
            )
        } else if self.sym.yyname().is_empty() {
            format!("syntax error occurred in state {}", self.state)
        } else {
            format!(
                "syntax error occurred in state {} on symbol {}",
                self.state,
                self.sym.yyname()
            )
        }
    }

    #[must_use]
    pub fn yyname(&self) -> String {
        "Error".to_owned()
    }
}

/// Result wrapper returned after successful error recovery.
#[derive(Clone, Debug)]
pub struct RecoveredError {
    error: LslError,
    count: i32,
}

impl RecoveredError {
    pub fn new(parser: Parser, entry: ParseStackEntry) -> Result<Self, Error> {
        let count = parser.m_symbols.erh.counter;
        Ok(Self {
            error: LslError::new_with_parser_parse_stack_entry(parser, entry)?,
            count,
        })
    }

    pub fn concrete_syntax_tree(&self) -> Result<(), Error> {
        println!("{}", self.to_string());
        self.error.sym.concrete_syntax_tree()
    }

    pub fn print(&self) -> Result<(), Error> {
        println!("{}", self.to_string());
        self.error.sym.print()
    }

    #[must_use]
    pub fn to_string(&self) -> String {
        format!("Parse contained {} errors", self.count)
    }
}
