//! Resolution engine for Storybook DSL
//!
//! This module takes parsed AST and:
//! - Builds a name table mapping qualified paths to declarations
//! - Resolves cross-references between entities
//! - Handles `use` statements
//! - Validates semantic constraints
//! - Produces rich error diagnostics
#![allow(unused_assignments)] // False positives in error enum fields used by thiserror

pub mod convert;
pub mod links;
pub mod merge;
pub mod names;
pub mod references;
pub mod types;
pub mod validate;

#[cfg(test)]
mod prop_tests;

#[cfg(test)]
mod integration_tests;

#[cfg(test)]
#[path = "links_prop_tests.rs"]
mod links_prop_tests;

#[cfg(test)]
#[path = "merge_prop_tests.rs"]
mod merge_prop_tests;

#[cfg(test)]
#[path = "validate_prop_tests.rs"]
mod validate_prop_tests;

#[cfg(test)]
#[path = "convert_prop_tests.rs"]
mod convert_prop_tests;

#[cfg(test)]
#[path = "convert_integration_tests.rs"]
mod convert_integration_tests;

use miette::Diagnostic;
pub use names::{
    DeclKind,
    NameTable,
    QualifiedPath,
};
pub use references::{
    find_all_references,
    Reference,
    ReferenceContext,
};
use thiserror::Error;
pub use types::ResolvedFile;

use crate::syntax::ast::File;

/// Errors that can occur during resolution
#[derive(Error, Debug, Diagnostic)]
pub enum ResolveError {
    #[error("Name '{name}' not found")]
    NameNotFound {
        #[allow(dead_code)]
        #[allow(unused_assignments)]
        name: String,
        #[help]
        suggestion: Option<String>,
    },

    #[error("Duplicate definition of '{name}'")]
    #[diagnostic(help("Each name must be unique within a file. You've defined '{name}' more than once. Either rename one of them or remove the duplicate definition. If you meant to extend or override the first definition, use template inheritance instead."))]
    DuplicateDefinition {
        #[allow(dead_code)]
        #[allow(unused_assignments)]
        name: String,
        #[label("first defined here")]
        first_location: (usize, usize),
        #[label("redefined here")]
        second_location: (usize, usize),
    },

    #[error("Circular dependency detected")]
    CircularDependency {
        #[help]
        cycle: String,
    },

    #[error("Invalid field access: {message}")]
    #[diagnostic(help("You're trying to access a field that doesn't exist or isn't accessible in this context. Check the field name for typos and make sure it's defined on the object you're accessing. In relationships, use 'self' for your own fields and 'other' for the other participant's fields."))]
    InvalidFieldAccess { message: String },

    #[error("Type mismatch: {message}")]
    #[diagnostic(help("The value you provided doesn't match the expected type. For example, you might be using a number where text is expected, or vice versa. Check that your value matches what's expected: numbers for ages, decimals (0.0-1.0) for traits, strings in quotes for text, true/false for yes/no values."))]
    TypeMismatch { message: String },

    #[error("Validation error: {message}")]
    ValidationError {
        message: String,
        #[help]
        help: Option<String>,
    },

    #[error("Life arc '{life_arc}': state '{state}' has transition to unknown state '{target}'")]
    #[diagnostic(help("Make sure the target state '{target}' is defined in the same life_arc block. Available states: {available_states}"))]
    UnknownLifeArcState {
        life_arc: String,
        state: String,
        target: String,
        available_states: String,
    },

    #[error("Trait '{field}' has value {value} which is outside valid range [{min}, {max}]")]
    #[diagnostic(help("Normalized traits like '{field}' must have values between {min} and {max}. Try adjusting the value to fit within this range."))]
    TraitOutOfRange {
        field: String,
        value: String,
        min: f64,
        max: f64,
    },

    #[error("Schedule blocks '{block1}' and '{block2}' overlap")]
    #[diagnostic(help("Schedule blocks cannot overlap in time. Make sure each block ends before the next one begins. Block '{block1}' ends at {end1}, but '{block2}' starts at {start2}."))]
    ScheduleOverlap {
        block1: String,
        block2: String,
        end1: String,
        start2: String,
    },

    #[error("Behavior tree '{tree}': unknown action '{action}'")]
    #[diagnostic(help("The action '{action}' is not defined in the action registry. Make sure to define all custom actions, or check for typos."))]
    UnknownBehaviorAction { tree: String, action: String },

    #[error("Found {count} errors")]
    #[diagnostic(help("Multiple errors were found during validation. Each error is listed below with its own help message. Fix them one by one, or use the hints to address common patterns."))]
    MultipleErrors {
        count: usize,
        #[related]
        errors: Vec<ResolveError>,
    },
}

/// Result type for resolution operations
pub type Result<T> = std::result::Result<T, ResolveError>;

/// Collects multiple errors instead of failing fast
///
/// This allows the compiler to continue validating and report all errors at
/// once, giving users a complete picture of what needs to be fixed.
#[derive(Debug, Default)]
pub struct ErrorCollector {
    errors: Vec<ResolveError>,
}

impl ErrorCollector {
    /// Create a new error collector
    pub fn new() -> Self {
        Self { errors: Vec::new() }
    }

    /// Add an error to the collection
    pub fn add(&mut self, error: ResolveError) {
        // Don't add MultipleErrors to avoid nesting
        match error {
            | ResolveError::MultipleErrors { mut errors, .. } => {
                self.errors.append(&mut errors);
            },
            | _ => {
                self.errors.push(error);
            },
        }
    }

    /// Check if any errors were collected
    pub fn has_errors(&self) -> bool {
        !self.errors.is_empty()
    }

    /// Get the number of errors collected
    pub fn len(&self) -> usize {
        self.errors.len()
    }

    /// Check if no errors have been collected
    pub fn is_empty(&self) -> bool {
        self.errors.is_empty()
    }

    /// Convert to a Result, returning Ok if no errors or Err with all errors
    pub fn into_result<T>(self, ok_value: T) -> Result<T> {
        if self.errors.is_empty() {
            Ok(ok_value)
        } else if self.errors.len() == 1 {
            // Single error - return it directly
            Err(self.errors.into_iter().next().unwrap())
        } else {
            // Multiple errors - wrap them
            Err(ResolveError::MultipleErrors {
                count: self.errors.len(),
                errors: self.errors,
            })
        }
    }

    /// Add a Result to the collector, extracting any error
    pub fn add_result<T>(&mut self, result: Result<T>) -> Option<T> {
        match result {
            | Ok(value) => Some(value),
            | Err(e) => {
                self.add(e);
                None
            },
        }
    }
}

/// Resolve a parsed file into a validated, cross-referenced structure
pub fn resolve_file(file: File) -> Result<ResolvedFile> {
    // Phase 1: Build name table
    let _name_table = NameTable::from_file(&file)?;

    // Phase 2: Resolve cross-references
    // TODO: implement

    // Phase 3: Validate semantics
    // TODO: implement

    // For now, just return a placeholder
    Ok(ResolvedFile {
        declarations: vec![],
    })
}
