// Metadata pattern extraction module
// Generic metadata pattern extraction for DTO files
//
// Supports pattern format:
// - Single-value: __name__ value
// - Format A: __key1__ val1 __key2__ val2

use std::collections::HashMap;
use std::path::{Path, PathBuf};

use crate::case_transform::{
    CasingConvention, PlaceholderDefinition, PlaceholderMetadata, PlaceholderRegistry,
    ValidationError, detect_word_boundaries, to_camel_case, to_kebab_case, to_lower_snake_case,
    to_lowercase, to_pascal_case, to_screaming_snake_case, to_uppercase,
    validate_placeholder_value_pair,
};

// ============================================================================
// Phase 2: Foundational Data Structures for Multi-Value Placeholders
// ============================================================================

/// T001: Represents a single value instance for a placeholder
/// Contains line number for error reporting and pairing information
#[derive(Debug, Clone, PartialEq)]
pub struct PlaceholderValue {
    pub value: String,
    pub line_number: usize,
    pub paired_with: Option<Vec<String>>,
}

/// T002: Indicates whether a placeholder has single or multiple values
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaceholderArity {
    SingleValue,
    MultiValue,
}

/// T004: Named placeholder with all values, arity, and optional pairing
#[derive(Debug, Clone)]
pub struct Placeholder {
    pub values: Vec<PlaceholderValue>,
    pub arity: PlaceholderArity,
}

/// T005: Collection of all metadata from a DTO file
#[derive(Debug, Clone)]
pub struct DtoMetadata {
    pub placeholders: HashMap<String, Placeholder>,
    pub single_value_map: HashMap<String, String>,
    pub case_registry: PlaceholderRegistry,
}

impl DtoMetadata {
    /// Create empty metadata for a DTO file
    pub fn new(_source_file: PathBuf) -> Self {
        Self {
            placeholders: HashMap::new(),
            single_value_map: HashMap::new(),
            case_registry: PlaceholderRegistry::new(),
        }
    }
}

// ============================================================================
// Phase 3: Multi-Value Metadata Extraction Functions (US1)
// ============================================================================

/// T010: Extract multi-value metadata from DTO file content
/// Detects same placeholder on multiple lines and stores as Vec<PlaceholderValue>
pub fn extract_multi_value_metadata(
    file_path: &Path,
    file_content: &str,
) -> Result<DtoMetadata, MetadataError> {
    use crate::regex_pattern::METADATA_PATTERN;
    use log::debug;

    let mut metadata = DtoMetadata::new(file_path.to_path_buf());
    let mut line_number = 1;

    // Track placeholder occurrences: name -> Vec<PlaceholderValue>
    let mut placeholder_values: HashMap<String, Vec<PlaceholderValue>> = HashMap::new();

    for line in file_content.lines() {
        // Extract metadata using METADATA_PATTERN (Format A: __name__ value)
        for captures in METADATA_PATTERN.captures_iter(line) {
            // Get pattern name (group 1)
            if let Some(name_match) = captures.get(1) {
                let name = name_match.as_str().to_string();

                // Get value from one of three possible groups:
                // Group 2: single-quoted value
                // Group 3: double-quoted value
                // Group 4: unquoted value
                let value = if let Some(quoted) = captures.get(2) {
                    quoted.as_str().to_string()
                } else if let Some(quoted) = captures.get(3) {
                    quoted.as_str().to_string()
                } else if let Some(unquoted) = captures.get(4) {
                    unquoted.as_str().to_string()
                } else {
                    continue;
                };

                if !value.is_empty() {
                    let placeholder_value = PlaceholderValue {
                        value: value.clone(),
                        line_number,
                        paired_with: None,
                    };

                    placeholder_values
                        .entry(name.clone())
                        .or_default()
                        .push(placeholder_value);

                    // T040: Integrate case validation into metadata extraction
                    // Validate placeholder name and value casing conventions match
                    let placeholder_name = format!("__{name}__");
                    if let Some(convention) =
                        validate_placeholder_value_pair(&placeholder_name, &value)
                    {
                        // T041: Add definition to case registry
                        let def = PlaceholderDefinition {
                            placeholder_name: placeholder_name.clone(),
                            inner_name: name.clone(),
                            normalized_name: name.to_lowercase(),
                            value: value.clone(),
                            convention,
                            source_file: file_path.to_path_buf(),
                            line_number,
                        };
                        metadata.case_registry.add_definition(def);
                    }

                    debug!("Extracted placeholder at line {line_number}: __{name}__ = {value}");
                }
            }
        }

        line_number += 1;
    }

    // T041: Validate case consistency and finalize registry
    let case_errors = metadata.case_registry.validate_consistency();
    if !case_errors.is_empty() {
        use log::warn;
        for error in &case_errors {
            if let ValidationError::MixedCasing {
                logical_name,
                definitions,
            } = error
            {
                warn!("Mixed casing conventions for placeholder '{logical_name}':");
                for def in definitions {
                    warn!(
                        "  {file}:{line} - __{name}__ = {value} ({convention:?})",
                        file = def.source_file.display(),
                        line = def.line_number,
                        name = def.inner_name,
                        value = def.value,
                        convention = def.convention
                    );
                }
            }
        }
    }
    metadata.case_registry.finalize();

    // T086/T087: Create PlaceholderMetadata for valid definitions
    // Note: For multi-value placeholders, all values share the same casing convention
    // (validated during extraction), so we create one PlaceholderMetadata with the first value
    // The actual case transformation happens during FOREACH iteration in processor.rs
    for (normalized_name, defs) in &metadata.case_registry.definitions {
        if !metadata.case_registry.rejected.contains(normalized_name) && !defs.is_empty() {
            let def = &defs[0]; // Use first definition as canonical
            let words = detect_word_boundaries(&def.value, def.convention);
            let placeholder_metadata = PlaceholderMetadata::new(
                def.placeholder_name.clone(),
                def.value.clone(),
                def.convention,
                words,
                false, // Special builtins will be handled in future stories
            );
            metadata
                .case_registry
                .validated
                .insert(normalized_name.clone(), placeholder_metadata);
        }
    }

    // T011: Determine arity and build Placeholder structs
    // T106: Insert all case variants as separate keys with transformed values
    // This allows __AliasName__, __aliasname__, __ALIASNAME__, __alias-name__ etc. to all work
    for (name, values) in placeholder_values {
        let arity = determine_placeholder_arity(&values);

        // Get the normalized name to check if this placeholder has case variants
        let normalized_name = name.to_lowercase();

        // If this placeholder has validated case variants, create separate entries for each variant
        if let Some(placeholder_metadata) = metadata.case_registry.validated.get(&normalized_name) {
            // Detect word boundaries in the placeholder name (not the value!)
            let placeholder_words =
                detect_word_boundaries(&name, placeholder_metadata.source_convention);

            // For each allowed casing convention, generate the variant key name AND transform the values
            for target_convention in &placeholder_metadata.allowed_variants {
                let variant_key = match target_convention {
                    CasingConvention::Lowercase => to_lowercase(&placeholder_words),
                    CasingConvention::Uppercase => to_uppercase(&placeholder_words),
                    CasingConvention::PascalCase => to_pascal_case(&placeholder_words),
                    CasingConvention::CamelCase => to_camel_case(&placeholder_words),
                    CasingConvention::LowerSnakeCase => to_lower_snake_case(&placeholder_words),
                    CasingConvention::ScreamingSnakeCase => {
                        to_screaming_snake_case(&placeholder_words)
                    }
                    CasingConvention::KebabCase => to_kebab_case(&placeholder_words),
                    CasingConvention::Fallback => name.clone(),
                };

                // Transform each value to the target convention
                let mut transformed_values = Vec::new();
                for value in &values {
                    // Detect word boundaries in the VALUE
                    let value_words = detect_word_boundaries(
                        &value.value,
                        placeholder_metadata.source_convention,
                    );

                    // Transform the value to the target convention
                    let transformed_value = match target_convention {
                        CasingConvention::Lowercase => to_lowercase(&value_words),
                        CasingConvention::Uppercase => to_uppercase(&value_words),
                        CasingConvention::PascalCase => to_pascal_case(&value_words),
                        CasingConvention::CamelCase => to_camel_case(&value_words),
                        CasingConvention::LowerSnakeCase => to_lower_snake_case(&value_words),
                        CasingConvention::ScreamingSnakeCase => {
                            to_screaming_snake_case(&value_words)
                        }
                        CasingConvention::KebabCase => to_kebab_case(&value_words),
                        CasingConvention::Fallback => value.value.clone(),
                    };

                    transformed_values.push(PlaceholderValue {
                        value: transformed_value,
                        line_number: value.line_number,
                        paired_with: value.paired_with.clone(),
                    });
                }

                // Create a new Placeholder with transformed values
                let variant_placeholder = Placeholder {
                    values: transformed_values,
                    arity,
                };

                // Insert this variant key with its transformed values
                metadata
                    .placeholders
                    .insert(variant_key, variant_placeholder);
            }

            // Also insert with original name and original values
            let original_placeholder = Placeholder {
                values: values.clone(),
                arity,
            };
            metadata
                .placeholders
                .insert(name.clone(), original_placeholder);
        } else {
            // No case variants - just insert with original name
            let placeholder = Placeholder {
                values: values.clone(),
                arity,
            };
            metadata.placeholders.insert(name.clone(), placeholder);
        }

        // T014: Populate single_value_map for backward compatibility
        if !values.is_empty() {
            metadata
                .single_value_map
                .insert(name, values[0].value.clone());
        }
    }

    debug!(
        "Multi-value extraction complete: {} placeholders from {}",
        metadata.placeholders.len(),
        file_path.display()
    );

    // Debug: Dump the entire placeholders HashMap
    debug!("=== METADATA EXTRACTION COMPLETE ===");
    debug!("File: {}", file_path.display());
    debug!("Total placeholder keys: {}", metadata.placeholders.len());
    debug!("");
    debug!("All placeholder keys and their values:");
    let mut sorted_keys: Vec<_> = metadata.placeholders.keys().collect();
    sorted_keys.sort();
    for key in sorted_keys {
        let placeholder = metadata.placeholders.get(key).unwrap();
        debug!("  KEY: '{key}'");
        debug!("    Arity: {:?}", placeholder.arity);
        debug!("    Values:");
        for (idx, val) in placeholder.values.iter().enumerate() {
            debug!(
                "      [{idx}] '{value}' (line {line})",
                value = val.value,
                line = val.line_number
            );
        }
    }
    debug!("=== END METADATA DUMP ===");
    debug!("");

    Ok(metadata)
}

/// T011: Classify placeholder arity based on number of values
pub fn determine_placeholder_arity(values: &[PlaceholderValue]) -> PlaceholderArity {
    match values.len() {
        0 | 1 => PlaceholderArity::SingleValue,
        _ => PlaceholderArity::MultiValue,
    }
}

/// T023: Validate placeholder consistency - check singular vs paired usage
/// Tracks how each placeholder is used across all lines and validates consistency.
///
/// # Arguments
/// * `placeholder_line_companions` - Map of placeholder name to list of (line_number, all_placeholders_on_line)
/// * `file_path` - Path to the DTO file for error messages
///
/// # Errors
/// Returns `MetadataError::InconsistentPairing` if a placeholder appears both alone and paired
pub fn validate_placeholder_consistency(
    placeholder_line_companions: &HashMap<String, Vec<(usize, Vec<String>)>>,
    file_path: &Path,
) -> Result<(), MetadataError> {
    for (placeholder, occurrences) in placeholder_line_companions {
        // Check if placeholder appears both alone and with companions
        // When alone, the full list will contain only itself
        // When paired, the full list will contain multiple names
        let alone_lines: Vec<usize> = occurrences
            .iter()
            .filter(|(_, all_placeholders)| all_placeholders.len() == 1)
            .map(|(line, _)| *line)
            .collect();

        let paired_lines: Vec<usize> = occurrences
            .iter()
            .filter(|(_, all_placeholders)| all_placeholders.len() > 1)
            .map(|(line, _)| *line)
            .collect();

        if !alone_lines.is_empty() && !paired_lines.is_empty() {
            return Err(MetadataError::InconsistentPairing {
                placeholder: placeholder.clone(),
                file_path: file_path.display().to_string(),
                singular_line: alone_lines[0],
                paired_line: paired_lines[0],
            });
        }
    }

    Ok(())
}

/// T024: Validate pair order consistency - check paired placeholders always in same order
/// Validates that when two placeholders appear together, they always appear in the same order.
///
/// # Arguments
/// * `placeholder_line_companions` - Map of placeholder name to list of (line_number, all_placeholders_on_line)
/// * `file_path` - Path to the DTO file for error messages
///
/// # Errors
/// Returns `MetadataError::InconsistentPairOrder` if paired placeholders appear in different orders
pub fn validate_pair_order_consistency(
    placeholder_line_companions: &HashMap<String, Vec<(usize, Vec<String>)>>,
    file_path: &Path,
) -> Result<(), MetadataError> {
    use std::collections::HashSet;

    // Build a map of all lines with multiple placeholders
    // We need to check if the same set of placeholders appears in different orders
    let mut placeholder_orders: HashMap<Vec<String>, Vec<usize>> = HashMap::new();

    // Collect all unique lines (avoid duplicates from different placeholders on same line)
    let mut seen_lines: HashSet<(usize, Vec<String>)> = HashSet::new();

    for occurrences in placeholder_line_companions.values() {
        for (line, all_placeholders) in occurrences {
            if all_placeholders.len() > 1 {
                seen_lines.insert((*line, all_placeholders.clone()));
            }
        }
    }

    // Group lines by their sorted placeholder set (to find same placeholders in different orders)
    for (line, all_placeholders) in seen_lines {
        let mut sorted_names = all_placeholders.clone();
        sorted_names.sort();

        placeholder_orders
            .entry(sorted_names)
            .or_default()
            .push(line);
    }

    // For each set of placeholders that appears on multiple lines,
    // check if the order is consistent
    for (sorted_names, lines) in &placeholder_orders {
        if lines.len() < 2 {
            continue; // Only one occurrence, no inconsistency possible
        }

        // Get the placeholder order from the first line
        let mut first_order: Option<Vec<String>> = None;
        for occurrences in placeholder_line_companions.values() {
            for (line, all_placeholders) in occurrences {
                if *line == lines[0] && all_placeholders.len() > 1 {
                    let mut sorted = all_placeholders.clone();
                    sorted.sort();
                    if &sorted == sorted_names {
                        first_order = Some(all_placeholders.clone());
                        break;
                    }
                }
            }
            if first_order.is_some() {
                break;
            }
        }

        let first_order = first_order.unwrap();

        // Check all other lines with the same placeholder set
        for &check_line in lines.iter().skip(1) {
            for occurrences in placeholder_line_companions.values() {
                for (line, all_placeholders) in occurrences {
                    if *line == check_line && all_placeholders.len() > 1 {
                        let mut sorted = all_placeholders.clone();
                        sorted.sort();
                        if &sorted == sorted_names && all_placeholders != &first_order {
                            // Found inconsistent order!
                            return Err(MetadataError::InconsistentPairOrder {
                                placeholder1: sorted_names[0].clone(),
                                placeholder2: sorted_names[1].clone(),
                                file_path: file_path.display().to_string(),
                                line1: lines[0],
                                line2: check_line,
                            });
                        }
                    }
                }
            }
        }
    }

    Ok(())
}

// ============================================================================
// ============================================================================
// Validation Error Types
// ============================================================================

/// Errors that can occur during metadata extraction
#[derive(Debug, Clone)]
pub enum MetadataError {
    InconsistentPairing {
        placeholder: String,
        file_path: String,
        singular_line: usize,
        paired_line: usize,
    },
    InconsistentPairOrder {
        placeholder1: String,
        placeholder2: String,
        file_path: String,
        line1: usize,
        line2: usize,
    },
}

impl std::fmt::Display for MetadataError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MetadataError::InconsistentPairing {
                placeholder,
                file_path,
                singular_line,
                paired_line,
            } => write!(
                f,
                "Inconsistent pairing for placeholder '__{placeholder}__' in file '{file_path}': \
                appears alone on line {singular_line} but paired on line {paired_line}. \
                Placeholders must be used consistently - either always alone or always paired with the same companion(s)."
            ),
            MetadataError::InconsistentPairOrder {
                placeholder1,
                placeholder2,
                file_path,
                line1,
                line2,
            } => write!(
                f,
                "Inconsistent pair order in file '{file_path}': \
                '__{placeholder1}__' and '__{placeholder2}__' appear together but in different orders on lines {line1} and {line2}. \
                Paired placeholders must always appear in the same order."
            ),
        }
    }
}

impl std::error::Error for MetadataError {}

/// Legacy metadata extraction function for backward compatibility
/// Returns a simple HashMap of placeholder names to their first value
pub fn extract_metadata(
    file_path: &Path,
    file_content: &str,
) -> Result<HashMap<String, String>, MetadataError> {
    // Delegate to the new multi-value extraction and return the single-value map
    let dto_metadata = extract_multi_value_metadata(file_path, file_content)?;

    Ok(dto_metadata.single_value_map)
}
