//! Core AAML parser and runtime.
//!
//! [`AAML`] is the main entry point for parsing `.aam` configuration files.
//! It supports:
//! - Key-value assignments (`key = value`)
//! - Directives: `@import`, `@derive`, `@schema`, `@type`
//! - Runtime type validation via registered or built-in types
//! - Schema-based struct validation with [`AAML::apply_schema`]

use crate::commands::schema::SchemaDef;
use crate::commands::{self, Command};
use crate::error::{AamlError, ErrorDiagnostics};
use crate::types::list::ListType;
use crate::types::{Type, resolve_builtin};
use std::collections::HashMap;
use std::fs;
use std::ops::{Add, AddAssign};
use std::path::Path;
use std::sync::Arc;

mod lookup;
pub mod parsing;
mod validation;

#[cfg(feature = "serde")]
pub mod serialize;

#[cfg(feature = "perf-hash")]
type Hasher = ahash::RandomState;

#[cfg(not(feature = "perf-hash"))]
type Hasher = std::collections::hash_map::RandomState;

type AamlString = Box<str>;

/// The main AAML parser and configuration store.
///
/// Holds a flat key-value map, registered type definitions, command handlers,
/// and schema definitions. All directives (`@derive`, `@schema`, etc.) are
/// processed at parse time.
///
/// # Example
/// ```no_run
/// use aam_rs::aaml::AAML;
///
/// let cfg = AAML::parse("host = localhost\nport = 8080").unwrap();
/// assert_eq!(cfg.find_obj("host").unwrap().as_str(), "localhost");
/// ```
#[deprecated(since = "2.0.0", note = "please use AAM and Pipeline instead")]
pub struct AAML {
    map: HashMap<AamlString, AamlString, Hasher>,
    commands: HashMap<String, Arc<dyn Command>>,
    types: HashMap<String, Box<dyn Type>>,
    schemas: HashMap<String, SchemaDef>,
}

impl std::fmt::Debug for AAML {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AAML")
            .field("map", &self.map)
            .field("commands_count", &self.commands.len())
            .finish()
    }
}

impl AAML {
    /// Creates a new empty [`AAML`] instance with all default commands registered.
    pub fn new() -> AAML {
        let mut instance = AAML {
            map: HashMap::with_hasher(Hasher::new()),
            commands: HashMap::new(),
            types: HashMap::new(),
            schemas: HashMap::new(),
        };
        instance.register_default_commands();
        instance
    }

    pub fn get_schemas(&self) -> &HashMap<String, SchemaDef> {
        &self.schemas
    }

    /// Creates a new [`AAML`] instance pre-allocated for `capacity` key-value entries.
    pub fn with_capacity(capacity: usize) -> AAML {
        let mut instance = AAML {
            map: HashMap::with_capacity_and_hasher(capacity, Hasher::default()),
            commands: HashMap::new(),
            types: HashMap::new(),
            schemas: HashMap::new(),
        };
        instance.register_default_commands();
        instance
    }

    // ── Internal accessors used by commands ──────────────────────────────────

    pub(crate) fn get_schemas_mut(&mut self) -> &mut HashMap<String, SchemaDef> {
        &mut self.schemas
    }

    pub fn get_schema(&self, name: &str) -> Option<&SchemaDef> {
        self.schemas.get(name)
    }

    pub(crate) fn get_map_mut(&mut self) -> &mut HashMap<AamlString, AamlString, Hasher> {
        &mut self.map
    }

    // ── Accessors for pipeline/executer ──────────────────────────────────

    /// Returns a reference to the command map (for internal pipeline use)
    #[allow(dead_code)]
    pub(crate) fn get_commands(&self) -> &HashMap<String, Arc<dyn Command>> {
        &self.commands
    }

    /// Returns a copy of the current map (for pipeline output)
    #[allow(dead_code)]
    pub(crate) fn get_map_copy(&self) -> HashMap<AamlString, AamlString, Hasher> {
        self.map.clone()
    }

    // ── Type registry ────────────────────────────────────────────────────────

    /// Registers a custom command handler.
    pub fn register_command<C: Command + 'static>(&mut self, command: C) {
        self.commands
            .insert(command.name().to_string(), Arc::new(command));
    }

    /// Registers a named type definition for use in schema field validation.
    pub fn register_type<T: Type + 'static>(&mut self, name: String, type_def: T) {
        self.types.insert(name, Box::new(type_def));
    }

    /// Returns the type handler registered under `name`, or `None`.
    pub fn get_type(&self, name: &str) -> Option<&dyn Type> {
        self.types.get(name).map(|b| b.as_ref())
    }

    /// Removes the type registered under `name`.
    pub fn unregister_type(&mut self, name: &str) {
        self.types.remove(name);
    }

    /// Validates `value` against a type registered under `type_name`.
    pub fn check_type(&self, type_name: &str, value: &str) -> Result<(), AamlError> {
        self.types
            .get(type_name)
            .ok_or_else(|| AamlError::NotFound {
                key: type_name.to_string(),
                context: "type registry".to_string(),
                diagnostics: Some(ErrorDiagnostics::new(
                    "Type not found",
                    format!("Type '{}' is not registered", type_name),
                    "Check your @type directives or use a built-in type",
                )),
            })?
            .validate(value, self)
    }

    /// Validates `value` against the type registered as `type_name`, also
    /// resolving built-in primitive types and module paths.
    pub fn validate_value(&self, type_name: &str, value: &str) -> Result<(), AamlError> {
        let make_err = |e: AamlError| AamlError::InvalidType {
            type_name: type_name.to_string(),
            details: e.to_string(),
            provided: value.to_string(),
            diagnostics: None,
        };

        if let Some(type_def) = self.types.get(type_name) {
            return type_def.validate(value, self).map_err(make_err);
        }

        resolve_builtin(type_name)
            .map_err(|_| AamlError::NotFound {
                key: type_name.to_string(),
                context: "builtin type registry".to_string(),
                diagnostics: Some(ErrorDiagnostics::new(
                    "Type not found",
                    format!(
                        "Type '{}' is neither registered nor a built-in type",
                        type_name
                    ),
                    "Check the type name or register a custom type with @type",
                )),
            })?
            .validate(value, self)
            .map_err(make_err)
    }

    // ── Parsing ──────────────────────────────────────────────────────────────

    /// Parses AAML content from a string, merging it into this instance.
    ///
    /// Multi-line directives (e.g. a `@schema` body spread across several lines)
    /// are accumulated until the opening `{` is matched by a closing `}`.
    pub fn merge_content(&mut self, content: &str) -> Result<(), AamlError> {
        self.map.reserve(content.len() / 40);
        let mut pending: Option<(String, usize)> = None;

        for (i, line) in content.lines().enumerate() {
            let line_num = i + 1;
            if let Some(result) = self.accumulate_or_process(line, line_num, &mut pending)? {
                self.process_line(&result.0, result.1)?;
            }
        }

        if let Some((buf, start)) = pending {
            self.process_line(&buf, start)?;
        }
        Ok(())
    }

    /// Returns a mutable reference to the registered type definitions.
    /// Used by commands like `@type` to add new types at parse time.
    /// The keys are the type names, and the values are boxed trait objects implementing [`Type`].
    pub(crate) fn get_types_mut(&mut self) -> &mut HashMap<String, Box<dyn Type>> {
        &mut self.types
    }

    /// Handles one source line: either appends it to a pending multi-line block
    /// or processes it immediately. Returns `Some((text, line_num))` when a
    /// complete directive has been accumulated and is ready to process.
    fn accumulate_or_process(
        &mut self,
        line: &str,
        line_num: usize,
        pending: &mut Option<(String, usize)>,
    ) -> Result<Option<(String, usize)>, AamlError> {
        if let Some((buf, start)) = pending {
            buf.push(' ');
            buf.push_str(parsing::strip_comment(line).trim());
            if parsing::block_is_complete(buf) {
                let complete = buf.clone();
                let start_line = *start;
                *pending = None;
                return Ok(Some((complete, start_line)));
            }
            return Ok(None);
        }

        let stripped = parsing::strip_comment(line).trim();
        if parsing::needs_accumulation(stripped) {
            *pending = Some((stripped.to_string(), line_num));
            return Ok(None);
        }

        self.process_stripped_line(stripped, line_num)?;
        Ok(None)
    }

    /// Reads a file from disk and merges its content into this instance.
    pub fn merge_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<(), AamlError> {
        let content = fs::read_to_string(file_path)?;
        self.merge_content(&content)
    }

    /// Parses an AAML string and returns a new [`AAML`] instance.
    pub fn parse(content: &str) -> Result<Self, AamlError> {
        let mut aaml = AAML::new();
        aaml.merge_content(content)?;
        Ok(aaml)
    }

    /// Loads an AAML file from disk and returns a new [`AAML`] instance.
    pub fn load<P: AsRef<Path>>(file_path: P) -> Result<Self, AamlError> {
        let content = fs::read_to_string(file_path)?;
        Self::parse(&content)
    }

    /// Strips surrounding `"…"` or `'…'` quotes. Returns the trimmed string unchanged
    /// if it is not quoted.
    pub fn unwrap_quotes(s: &str) -> &str {
        parsing::unwrap_quotes(s)
    }

    pub fn import_schema(&mut self, name: &str, source: &mut AAML) -> Result<(), AamlError> {
        // Already imported (e.g., as a transitive dependency of another schema) — skip.
        if self.get_schemas().contains_key(name) {
            return Ok(());
        }

        let schema =
            source
                .get_schemas_mut()
                .remove(name)
                .ok_or_else(|| AamlError::DirectiveError {
                    directive: "derive".to_string(),
                    message: format!("Schema '{}' not found", name),
                    diagnostics: Some(ErrorDiagnostics::new(
                        "Schema not found for inheritance",
                        format!("Cannot derive from schema '{}': it does not exist", name),
                        "Ensure the schema is defined before using @derive",
                    )),
                })?;

        // Collect field type strings before `schema` is moved into the map.
        let field_type_strings: Vec<String> = schema.fields.values().cloned().collect();

        // Insert the schema first so that circular references do not recurse infinitely.
        self.get_schemas_mut()
            .entry(name.to_string())
            .or_insert(schema);

        for ty_str in &field_type_strings {
            let ty_name = ListType::parse_inner(ty_str).unwrap_or_else(|| ty_str.clone());

            // Try to pull in a registered type alias first.
            if let Some(ty_def) = source.get_types_mut().remove(&ty_name) {
                self.get_types_mut().entry(ty_name).or_insert(ty_def);
                continue;
            }

            // If the inner type name refers to another schema, import it recursively
            // so that nested schema validation (e.g. `list<Author>`) works correctly.
            if source.get_schemas().contains_key(&ty_name)
                && !self.get_schemas().contains_key(&ty_name)
            {
                self.import_schema(&ty_name, source)?;
            }
        }

        Ok(())
    }

    pub fn merge_map_weak(&mut self, other_map: &mut HashMap<AamlString, AamlString, Hasher>) {
        for (k, v) in other_map.drain() {
            self.get_map_mut().entry(k).or_insert(v);
        }
    }

    /// Returns all keys currently stored in the map.
    pub fn keys(&self) -> Vec<&str> {
        self.map.keys().map(|k| k.as_ref()).collect()
    }

    /// Returns all key-value pairs as a `HashMap<String, String>`.
    pub fn to_map(&self) -> HashMap<String, String> {
        self.map
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect()
    }

    // ── Private helpers ──────────────────────────────────────────────────────

    fn register_default_commands(&mut self) {
        self.register_command(commands::import::ImportCommand);
        self.register_command(commands::typecm::TypeCommand);
        self.register_command(commands::schema::SchemaCommand);
        self.register_command(commands::derive::DeriveCommand);
    }

    fn process_line(&mut self, raw_line: &str, line_num: usize) -> Result<(), AamlError> {
        let line = parsing::strip_comment(raw_line).trim();
        self.process_stripped_line(line, line_num)
    }

    fn process_stripped_line(&mut self, line: &str, line_num: usize) -> Result<(), AamlError> {
        if line.is_empty() {
            return Ok(());
        }
        if let Some(rest) = line.strip_prefix('@') {
            return self.process_directive(rest, line_num);
        }
        self.process_assignment(line, line_num)
    }

    fn process_assignment(&mut self, line: &str, line_num: usize) -> Result<(), AamlError> {
        match parsing::parse_assignment(line) {
            Ok((key, value)) => {
                self.validate_against_schemas(key, value)?;
                self.map.insert(Box::from(key), Box::from(value));
                Ok(())
            }
            Err(mut err) => {
                if let AamlError::MalformedLiteral { diagnostics, .. } = &mut err
                    && diagnostics.is_none()
                {
                    *diagnostics = Some(ErrorDiagnostics::new(
                        "Failed to parse assignment",
                        format!("Line {}: '{}'", line_num, line),
                        "Check the format: key = value",
                    ));
                }
                if let AamlError::InvalidValue { diagnostics, .. } = &mut err
                    && diagnostics.is_none()
                {
                    *diagnostics = Some(ErrorDiagnostics::new(
                        "Invalid assignment value",
                        format!("Line {}: '{}'", line_num, line),
                        "Ensure key and value are properly formatted",
                    ));
                }
                Err(err)
            }
        }
    }

    fn process_directive(&mut self, content: &str, line_num: usize) -> Result<(), AamlError> {
        let mut parts = content.splitn(2, char::is_whitespace);
        let command_name = parts.next().unwrap_or("").trim();
        let args = parts.next().unwrap_or("");

        if command_name.is_empty() {
            return Err(AamlError::ParseError {
                line: line_num,
                content: content.to_string(),
                details: "Empty directive".to_string(),
                diagnostics: Some(ErrorDiagnostics::new(
                    "Empty directive",
                    "Directive name is missing after '@'",
                    "Use format: @directive_name args",
                )),
            });
        }

        let command = self.commands.get(command_name).cloned();
        match command {
            Some(cmd) => cmd.execute(self, args),
            None => Err(AamlError::ParseError {
                line: line_num,
                content: content.to_string(),
                details: format!("Unknown directive: @{}", command_name),
                diagnostics: Some(ErrorDiagnostics::new(
                    "Unknown directive",
                    format!("Directive '@{}' is not recognized", command_name),
                    "Known directives: @import, @derive, @schema, @type",
                )),
            }),
        }
    }
}

impl Add for AAML {
    type Output = Self;

    fn add(mut self, rhs: Self) -> Self {
        self.map.reserve(rhs.map.len());
        self.map.extend(rhs.map);
        self.types.extend(rhs.types);
        self
    }
}

impl AddAssign for AAML {
    fn add_assign(&mut self, rhs: Self) {
        self.map.reserve(rhs.map.len());
        self.map.extend(rhs.map);
        self.types.extend(rhs.types);
    }
}

impl Default for AAML {
    fn default() -> Self {
        Self::new()
    }
}
