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

use std::path::Path;

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

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

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

// Post-interpretation graph-format exporters (DOT, Mermaid, PlantUML) that operate on the public
// `Network` value. Reached through `Network::to_dot()`/`to_mermaid()`/`to_plantuml()`, not exposed
// as a module.
mod format;

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

/// Common modules used throughout the code base.
pub mod common {
    /// Shared `Display` formatting helpers.
    pub(crate) mod fmt;

    /// 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, WalkEvent};
pub use parser::{ParseError, ParseErrorKind, ast::PortDirection};
pub use scanner::{ScanError, ScanErrorKind};
pub use session::{FileId, ImportPolicy, Session};

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

#[doc(hidden)]
pub use interpreter::InterpretWarning;
#[doc(hidden)]
pub use parser::{Parser, ast::Stmt};
#[doc(hidden)]
pub use scanner::{Scanner, token::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,
    DanglingConnection,
}

impl From<InterpretWarning> for Warning {
    fn from(w: InterpretWarning) -> Self {
        use interpreter::InterpretWarningKind;
        Warning {
            kind: match w.kind() {
                InterpretWarningKind::UnusedValue => WarningKind::UnusedValue,
                InterpretWarningKind::DanglingConnection { .. } => WarningKind::DanglingConnection,
            },
            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.
    ///
    /// # Panics
    ///
    /// Does not panic under normal conditions; the internal `unwrap` is an
    /// invariant that cannot be violated when the session is used correctly.
    ///
    /// # Errors
    ///
    /// Returns a `NexusError` if scanning, parsing, or interpretation fails.
    ///
    pub fn run(source: &str) -> Result<(Network, Vec<Warning>), NexusError> {
        let mut s = Session::new();
        let f = s.load_string(source, None);
        let network = s.evaluate(f)?;
        let warnings = s.into_warnings().into_iter().map(Warning::from).collect();
        Ok((network, warnings))
    }

    /// Run a Nexus program from a file path.
    ///
    /// Accepts any path-like argument (`&str`, `String`, `&Path`, `PathBuf`, ...).
    ///
    /// # Panics
    ///
    /// Does not panic under normal conditions; the internal `unwrap` is an
    /// invariant that cannot be violated when the session is used correctly.
    ///
    /// # 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 mut s = Session::new();
        let f = s.load_file(path)?;
        let network = s.evaluate(f)?;
        let warnings = s.into_warnings().into_iter().map(Warning::from).collect();
        Ok((network, warnings))
    }
}
