//! TOML form parsing and loading
//!
//! Handles parsing form definitions from TOML format and loading from files.

use crate::error::Result;
use std::path::{Path, PathBuf};

use super::types::FormDefinition;

/// Get fragment search paths from environment variable
///
/// Reads TYPEDIALOG_FRAGMENT_PATH environment variable (colon-separated on Unix, semicolon on Windows)
/// and returns a vector of PathBuf. Returns empty vector if not set.
fn get_fragment_search_paths() -> Vec<PathBuf> {
    std::env::var("TYPEDIALOG_FRAGMENT_PATH")
        .ok()
        .map(|paths| {
            #[cfg(unix)]
            let separator = ':';
            #[cfg(windows)]
            let separator = ';';

            paths
                .split(separator)
                .filter(|s| !s.is_empty())
                .map(PathBuf::from)
                .collect()
        })
        .unwrap_or_default()
}

/// Resolve fragment path by searching in multiple directories
///
/// Search order:
/// 1. If absolute path, use it directly
/// 2. Try base_dir first (backward compatibility)
/// 3. Try each directory in TYPEDIALOG_FRAGMENT_PATH
///
/// Returns the first existing path, or the base_dir-joined path if none exist (for error reporting)
fn resolve_fragment_path(path: &str, base_dir: &Path) -> PathBuf {
    // If absolute, use directly
    if Path::new(path).is_absolute() {
        return Path::new(path).to_path_buf();
    }

    // Try base_dir first (backward compatibility)
    let base_path = base_dir.join(path);
    if base_path.exists() {
        return base_path;
    }

    // Try each search path
    for search_dir in get_fragment_search_paths() {
        let candidate = search_dir.join(path);
        if candidate.exists() {
            return candidate;
        }
    }

    // Return base_path for error reporting (original behavior)
    base_path
}

/// Resolve constraint interpolations in TOML content
/// Replaces "${constraint.path.to.value}" (with quotes) with actual values from constraints.toml
/// The quotes are removed as part of the replacement, so the value becomes a bare number
pub(super) fn resolve_constraints_in_content(content: &str, base_dir: &Path) -> Result<String> {
    let constraints_path = base_dir.join("constraints.toml");

    // If constraints.toml doesn't exist, return content unchanged
    if !constraints_path.exists() {
        return Ok(content.to_string());
    }

    let constraints_content = std::fs::read_to_string(&constraints_path)?;
    let constraints_table: toml::Table = toml::from_str(&constraints_content).map_err(|e| {
        crate::error::ErrorWrapper::validation_failed(format!(
            "Failed to parse constraints.toml: {}",
            e
        ))
    })?;

    let mut result = content.to_string();

    // Find all "${constraint.*}" patterns (with quotes) by searching
    // Pattern format: max_items = "${constraint.tracker.udp.max_items}"
    while let Some(start_pos) = result.find("\"${constraint.") {
        // Find the closing brace followed by quote sequence: }"
        let search_start = start_pos + 2; // Skip the opening quote
        if let Some(close_brace_pos) = result[search_start..].find("}\"") {
            let close_brace_abs = search_start + close_brace_pos;
            let end_pos = close_brace_abs + 1; // Position of the closing quote

            let pattern = &result[start_pos..=end_pos];
            // Extract path between "${constraint. and }"
            // pattern looks like: "${constraint.tracker.udp.max_items}"
            // We skip the first 14 chars ("${constraint.) and last 2 chars (}")
            let constraint_path = &pattern[14..pattern.len() - 2];

            // Navigate through the table following the path
            let path_parts: Vec<&str> = constraint_path.split('.').collect();
            let mut current: &toml::Value = &toml::Value::Table(constraints_table.clone());
            let mut found = true;

            for part in path_parts {
                if let toml::Value::Table(table) = current {
                    if let Some(next) = table.get(part) {
                        current = next;
                    } else {
                        found = false;
                        break;
                    }
                } else {
                    found = false;
                    break;
                }
            }

            if found {
                if let toml::Value::Integer(n) = current {
                    // Replace the quoted interpolation with just the number (unquoted)
                    // This allows TOML to parse it as a number, not a string
                    let replacement = n.to_string();
                    result.replace_range(start_pos..=end_pos, &replacement);
                    // Continue searching from the current position
                    continue;
                }
            }

            // If not found or not an integer, break to avoid infinite loop
            break;
        } else {
            break; // No closing sequence found
        }
    }

    Ok(result)
}

/// Parse TOML string into a FormDefinition
pub fn parse_toml(content: &str) -> Result<FormDefinition> {
    toml::from_str(content).map_err(|e| e.into())
}

/// Load form from a Nickel (.ncl) file via `nickel export --format json`
///
/// Invokes the `nickel` CLI as a subprocess. If the file contains contract violations
/// or syntax errors, `nickel export` fails and this function returns an error, aborting
/// the caller before any form execution begins.
pub fn load_from_ncl(path: impl AsRef<Path>) -> Result<FormDefinition> {
    use crate::nickel::NickelCli;
    let json = NickelCli::export(path.as_ref())?;
    serde_json::from_value(json).map_err(|e| {
        crate::error::ErrorWrapper::new(format!(
            "Failed to deserialize Nickel export as FormDefinition: {}",
            e
        ))
    })
}

/// Load a form from either a `.ncl` or `.toml` file, detected by extension
///
/// `.ncl` files are loaded via `nickel export` (schema-validated at load time).
/// All other extensions fall back to TOML parsing.
pub fn load_form(path: impl AsRef<Path>) -> Result<FormDefinition> {
    let path = path.as_ref();
    match path.extension().and_then(|e| e.to_str()) {
        Some("ncl") => load_from_ncl(path),
        _ => load_from_file(path),
    }
}

/// Load form from TOML file (returns FormDefinition, doesn't execute)
pub fn load_from_file(path: impl AsRef<Path>) -> Result<FormDefinition> {
    let path_ref = path.as_ref();
    let content = std::fs::read_to_string(path_ref)?;

    // Get the directory of the current file for relative path resolution
    let base_dir = path_ref.parent().unwrap_or_else(|| Path::new("."));

    // Resolve constraint interpolations before parsing
    let resolved_content = resolve_constraints_in_content(&content, base_dir)?;
    parse_toml(&resolved_content)
}

/// Load unified elements from a TOML file with proper path resolution
///
/// Automatically migrates legacy items/fields to the unified elements format.
/// Searches for fragments in base_dir first, then TYPEDIALOG_FRAGMENT_PATH directories.
pub(super) fn load_elements_from_file(
    path: &str,
    base_dir: &Path,
) -> Result<Vec<super::types::FormElement>> {
    let resolved_path = resolve_fragment_path(path, base_dir);
    let content = std::fs::read_to_string(&resolved_path)?;

    // Resolve constraint interpolations before parsing
    let resolved_content = resolve_constraints_in_content(&content, base_dir)?;
    let mut form: FormDefinition = toml::from_str(&resolved_content)?;
    form.migrate_to_elements();
    Ok(form.elements)
}
