#![allow(clippy::result_large_err)] // Matches the existing policy in interpreter/mod.rs; boxing all error variants is a pain.

use std::path::Path;

/// Module group for lexing/scanner-related items.
///
/// Exposed as a `pub mod` so tests can access internal types like `scanner::cursor::Cursor`
/// by path. The scanner API itself is re-exported at the crate root.
pub mod scanner;

/// Module group for parser-related items.
///
/// Exposed as a `pub mod` so tests can access internal types like `parser::cursor::Cursor`
/// by path. The parser API itself is re-exported at the crate root.
pub mod parser;

// Not exposed as a public module; all needed items are re-exported individually below.
mod interpreter;

/// Common modules used throughout the code base.
pub mod common {
    /// Nexus-wide error type.
    pub mod error;

    /// Source code location representation.
    pub mod location;

    /// Source code aggregate representation.
    pub mod source;

    pub use error::NexusError;
    pub use location::Location;
    pub use source::Source;
}

pub use common::*;
pub use interpreter::{Connection, InterpretError, Network, Node, Port, PortRef};
pub use parser::{ParseError, ParseErrorKind, ast::PortDirection};
pub use scanner::{ScanError, ScanErrorKind};

// ── Not part of the intended public API; visible to the binary and tests ──────

#[doc(hidden)]
pub use interpreter::{InterpretErrorKind, InterpretWarning, Interpreter};
#[doc(hidden)]
pub use parser::{
    Parser,
    ast::{Ast, Expr, Stmt},
};
#[doc(hidden)]
pub use scanner::{
    Scanner,
    token::{Token, TokenKind, Tokens},
};

/// Unique identifier for a node instance within a network.
pub type NodeId = u64;

// ── Public warning types ──────────────────────────────────────────────────────

/// A warning produced during program evaluation.
#[derive(Clone, Debug, PartialEq)]
pub struct Warning {
    pub kind: WarningKind,
    pub message: String,
}

/// Warning kind.
#[derive(Clone, Debug, PartialEq)]
pub enum WarningKind {
    UnusedValue,
}

impl From<InterpretWarning> for Warning {
    fn from(w: InterpretWarning) -> Self {
        use interpreter::InterpretWarningKind;
        Warning {
            kind: match w.kind() {
                InterpretWarningKind::UnusedValue => WarningKind::UnusedValue,
            },
            message: w.to_string(),
        }
    }
}

// ── Public entry point ────────────────────────────────────────────────────────

/// High-level entry point for running Nexus programs.
pub struct Nexus;

impl Nexus {
    /// Run a Nexus program from source code.
    ///
    /// # Errors
    ///
    /// Returns a `NexusError` if scanning, parsing, or interpretation fails.
    ///
    pub fn run(source: &str) -> Result<(Network, Vec<Warning>), NexusError> {
        let source = Source::new(source);
        let tokens = Scanner::new(source).scan()?;
        let ast = Parser::new(source, tokens).parse()?;
        let (network, warnings) = Interpreter::new(source, ast).eval()?;
        Ok((network, warnings.into_iter().map(Warning::from).collect()))
    }

    /// Run a Nexus program from a file path.
    ///
    /// Accepts any path-like argument (`&str`, `String`, `&Path`, `PathBuf`, ...).
    ///
    /// # Errors
    ///
    /// Returns a `NexusError` if the file cannot be read, or if scanning, parsing, or
    /// interpretation fails.
    ///
    pub fn run_file(path: impl AsRef<Path>) -> Result<(Network, Vec<Warning>), NexusError> {
        let path = path.as_ref();
        let source_code = std::fs::read_to_string(path)?;
        let name = path.display().to_string();
        let source = Source::new_with_name(&source_code, &name);
        let tokens = Scanner::new(source).scan()?;
        let ast = Parser::new(source, tokens).parse()?;
        let (network, warnings) = Interpreter::new(source, ast).eval()?;
        Ok((network, warnings.into_iter().map(Warning::from).collect()))
    }
}
