//! Tests for frontier items and partial parse behavior.

use super::*;
use crate::logic::grammar::Grammar;
use crate::logic::parse::Task;

#[test]
fn items_at_eof_go_to_frontier() {
    let grammar = Grammar::load("Start ::= 'x' 'y'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    parser.set_input("x").unwrap();

    let start_nt = parser.grammar.nt_index("Start").unwrap();
    parser.seed_for_test(start_nt, 0, 0);

    while let Some(task) = parser.tables.agenda.pop_front() {
        match task {
            Task::Process(item) => parser.process_for_test(item).unwrap(),
            Task::Complete(c) => parser.complete_for_test(c).unwrap(),
        }
    }

    assert!(
        !parser.tables.frontier.is_empty(),
        "item expecting 'y' at EOF should land in the frontier"
    );
    assert!(
        parser.tables.frontier.iter().any(|item| item.dot == 1),
        "frontier item should have dot=1 (consumed 'x', needs 'y')"
    );
}

#[test]
fn item_at_beginning_of_input_does_not_go_to_frontier() {
    let grammar = Grammar::load("Start ::= 'x' 'y'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    parser.set_input("").unwrap();

    let start_nt = parser.grammar.nt_index("Start").unwrap();
    parser.seed_for_test(start_nt, 0, 0);

    while let Some(task) = parser.tables.agenda.pop_front() {
        match task {
            Task::Process(item) => parser.process_for_test(item).unwrap(),
            Task::Complete(c) => parser.complete_for_test(c).unwrap(),
        }
    }

    assert!(
        !parser.tables.frontier.is_empty(),
        "even an un-started item goes to frontier at EOF"
    );
    assert!(
        parser.tables.frontier.iter().any(|item| item.dot == 0),
        "frontier item should have dot=0 (nothing consumed)"
    );
}

#[test]
fn partial_input_produces_partial_node() {
    let grammar = Grammar::load("Start ::= 'x' 'y'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    let ast = parser.parse("x", 0).unwrap();
    assert!(!ast.is_complete());
    assert!(!ast.is_empty());
}

#[test]
fn empty_input_against_non_nullable_grammar_produces_partial_node() {
    let grammar = Grammar::load("Start ::= 'a'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    let ast = parser.parse("", 0).unwrap();
    assert!(!ast.is_complete());
    assert!(!ast.is_empty());
}

#[test]
fn partial_node_has_partial_status() {
    let grammar = Grammar::load("Start ::= 'x' 'y'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    let ast = parser.parse("x", 0).unwrap();

    assert!(!ast.is_empty());
    let arena = ast.arena();
    for &root_id in ast.root_ids() {
        let node = arena.node(root_id).unwrap();
        assert_eq!(node.status, crate::logic::parse::NodeStatus::Partial);
    }
}

#[test]
fn frontier_item_cascades_completion_to_parent_waiter() {
    let grammar = Grammar::load("A ::= 'x' 'y'\nStart ::= A 'z'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    let ast = parser.parse("x", 0).unwrap();

    assert!(!ast.is_empty());
    assert!(!ast.is_complete());
}

#[test]
fn full_input_has_no_frontier_items() {
    let grammar = Grammar::load("Start ::= 'a' 'b'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    parser.set_input("a b").unwrap();

    let start_nt = parser.grammar.nt_index("Start").unwrap();
    parser.seed_for_test(start_nt, 0, 0);

    while let Some(task) = parser.tables.agenda.pop_front() {
        match task {
            Task::Process(item) => parser.process_for_test(item).unwrap(),
            Task::Complete(c) => parser.complete_for_test(c).unwrap(),
        }
    }

    assert!(
        parser
            .tables
            .frontier
            .iter()
            .all(|item| item.pos == parser.segs().len()),
        "any remaining frontier items should all be at the end of input"
    );
}

#[test]
fn partial_node_has_concrete_type() {
    let grammar = Grammar::load("Start ::= 'a' 'b'").unwrap();
    let mut parser = TypedParser::new(grammar, StubTyping);
    let ast = parser.parse("a", 0).unwrap();

    assert!(!ast.is_empty());
    let arena = ast.arena();
    for &root_id in ast.root_ids() {
        let node = arena.node(root_id).unwrap();
        assert_eq!(node.status, crate::logic::parse::NodeStatus::Partial);
    }
}
