//! The program node of the syntax tree.

use getset::Getters;

use crate::{
    base::{
        source_file::{SourceElement, Span},
        Handler,
    },
    lexical::token::{Keyword, KeywordKind, Punctuation, StringLiteral, Token},
    syntax::{
        error::{Error, SyntaxKind, UnexpectedSyntax},
        parser::{Parser, Reading},
    },
};

use super::declaration::Declaration;

/// Program is a collection of declarations preceeded by a namespace selector.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ProgramFile {
    /// The namespace selector.
    #[get = "pub"]
    namespace: Namespace,
    /// The declarations within the program.
    #[get = "pub"]
    declarations: Vec<Declaration>,
}

/// Namespace is a namespace selector.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// Namespace:
///    'namespace' StringLiteral ';' ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
#[allow(missing_docs)]
pub struct Namespace {
    /// The `namespace` keyword.
    #[get = "pub"]
    namespace_keyword: Keyword,
    /// The name of the namespace.
    #[get = "pub"]
    namespace_name: StringLiteral,
    /// The semicolon.
    #[get = "pub"]
    semicolon: Punctuation,
}

impl SourceElement for Namespace {
    fn span(&self) -> Span {
        self.namespace_keyword
            .span()
            .join(&self.semicolon.span())
            .expect("Invalid span")
    }
}

impl Namespace {
    /// Dissolves the namespace into its components.
    #[must_use]
    pub fn dissolve(self) -> (Keyword, StringLiteral, Punctuation) {
        (self.namespace_keyword, self.namespace_name, self.semicolon)
    }

    /// Validates a namespace string.
    #[must_use]
    pub fn validate_str(namespace: &str) -> bool {
        const VALID_CHARS: &str = "0123456789abcdefghijklmnopqrstuvwxyz_-.";

        namespace.chars().all(|c| VALID_CHARS.contains(c))
    }
}

impl<'a> Parser<'a> {
    /// Parses a [`ProgramFile`].
    pub fn parse_program(&mut self, handler: &impl Handler<Error>) -> Option<ProgramFile> {
        let namespace = match self.stop_at_significant() {
            Reading::Atomic(Token::Keyword(namespace_keyword))
                if namespace_keyword.keyword == KeywordKind::Namespace =>
            {
                // eat the keyword
                self.forward();

                let namespace_name = self
                    .parse_string_literal(handler)
                    .and_then(|name| Namespace::validate_str(name.str_content()).then_some(name))?;

                let semicolon = self.parse_punctuation(';', true, handler)?;

                Some(Namespace {
                    namespace_keyword,
                    namespace_name,
                    semicolon,
                })
            }
            unexpected => {
                handler.receive(
                    UnexpectedSyntax {
                        expected: SyntaxKind::Keyword(KeywordKind::Namespace),
                        found: unexpected.into_token(),
                    }
                    .into(),
                );
                None
            }
        }?;

        let mut declarations = Vec::new();

        while !self.is_exhausted() {
            let result = self.parse_declaration(handler);

            #[allow(clippy::option_if_let_else)]
            if let Some(x) = result {
                declarations.push(x);
            } else {
                self.stop_at(|reading| {
                    matches!(
                        reading,
                        Reading::IntoDelimited(x) if x.punctuation == '{'
                    )
                });

                self.next_token();
            }
        }

        Some(ProgramFile {
            namespace,
            declarations,
        })
    }
}