//! Conversion from AST to resolved types
//!
//! This module handles converting parsed AST declarations into fully resolved
//! types that are ready for consumption by the game engine. It:
//! - Converts field vectors to HashMaps for efficient lookup
//! - Extracts prose blocks into separate collections
//! - Applies template overrides
//! - Validates that all references exist

use std::collections::HashMap;

use crate::{
    resolve::{
        merge,
        names::NameTable,
        validate,
        ErrorCollector,
        ResolveError,
        Result,
    },
    syntax::ast::{
        self,
        ProseBlock,
        Value,
    },
    types::*,
};

/// Convert a parsed file into resolved declarations
///
/// This is the old version that doesn't handle template composition.
/// Use `convert_file_with_name_table` for full template support.
pub fn convert_file(file: &ast::File) -> Result<Vec<ResolvedDeclaration>> {
    // Build name table for this file only (for backward compatibility)
    let name_table = NameTable::from_file(file)?;
    convert_file_with_name_table(file, &name_table)
}

/// Convert a parsed file into resolved declarations with a provided name table
///
/// This allows using a global name table that contains declarations from
/// multiple files.
pub fn convert_file_with_name_table(
    file: &ast::File,
    name_table: &NameTable,
) -> Result<Vec<ResolvedDeclaration>> {
    // For backward compatibility, use only this file (wrapped in a Vec for the API)
    convert_file_with_all_files(file, name_table, std::slice::from_ref(file))
}

/// Convert a parsed file with access to all files
///
/// This is needed for cross-file template resolution.
pub fn convert_file_with_all_files(
    file: &ast::File,
    name_table: &NameTable,
    all_files: &[ast::File],
) -> Result<Vec<ResolvedDeclaration>> {
    let mut resolved = Vec::new();

    for decl in &file.declarations {
        match decl {
            | ast::Declaration::Character(c) => {
                // Use template-aware conversion
                let resolved_char = convert_character_with_templates(c, all_files, name_table)?;
                resolved.push(ResolvedDeclaration::Character(resolved_char));
            },
            | ast::Declaration::Template(t) => {
                // Use include-aware conversion
                let resolved_template = convert_template_with_includes(t, all_files, name_table)?;
                resolved.push(ResolvedDeclaration::Template(resolved_template));
            },
            | ast::Declaration::LifeArc(la) => {
                resolved.push(ResolvedDeclaration::LifeArc(convert_life_arc(la)?));
            },
            | ast::Declaration::Schedule(s) => {
                resolved.push(ResolvedDeclaration::Schedule(convert_schedule(s)?));
            },
            | ast::Declaration::Behavior(b) => {
                resolved.push(ResolvedDeclaration::Behavior(convert_behavior(b)?));
            },
            | ast::Declaration::Institution(i) => {
                resolved.push(ResolvedDeclaration::Institution(convert_institution(i)?));
            },
            | ast::Declaration::Relationship(r) => {
                resolved.push(ResolvedDeclaration::Relationship(convert_relationship(r)?));
            },
            | ast::Declaration::Location(l) => {
                resolved.push(ResolvedDeclaration::Location(convert_location(l)?));
            },
            | ast::Declaration::Species(s) => {
                resolved.push(ResolvedDeclaration::Species(convert_species(s)?));
            },
            | ast::Declaration::Use(_) => {
                // Use declarations are handled during name resolution, not
                // conversion
            },
            | ast::Declaration::Concept(_) |
            ast::Declaration::SubConcept(_) |
            ast::Declaration::Definition(_) => {
                // TODO: Implement conversion for type system declarations
            },
        }
    }

    Ok(resolved)
}

/// Convert character AST to resolved type
pub fn convert_character(character: &ast::Character) -> Result<ResolvedCharacter> {
    let (fields, prose_blocks) = extract_fields_and_prose(&character.fields)?;

    Ok(ResolvedCharacter {
        name: character.name.clone(),
        species: None,
        fields,
        prose_blocks,
        uses_behaviors: character.uses_behaviors.clone(),
        uses_schedule: character.uses_schedule.clone(),
        span: character.span.clone(),
    })
}

/// Convert character AST to resolved type with template composition
///
/// This version resolves template inheritance by:
/// 1. Merging all templates listed in `from Template1, Template2`
/// 2. Recursively resolving template includes
/// 3. Validating strict mode requirements
/// 4. Applying character's own fields on top
/// 5. Merging behavior and schedule links from templates
pub fn convert_character_with_templates(
    character: &ast::Character,
    all_files: &[ast::File],
    name_table: &NameTable,
) -> Result<ResolvedCharacter> {
    // Merge character templates if any
    let merged_fields = if character.template.is_some() {
        merge::merge_character_templates(character, all_files, name_table)?
    } else {
        character.fields.clone()
    };

    // Collect behavior and schedule links from templates
    let (merged_behaviors, merged_schedules) = if let Some(template_names) = &character.template {
        let template_behaviors = Vec::new();
        let template_schedules = Vec::new();

        for template_name in template_names {
            // Look up template
            let entry = name_table
                .lookup(std::slice::from_ref(template_name))
                .ok_or_else(|| ResolveError::NameNotFound {
                    name: template_name.clone(),
                    suggestion: name_table.find_suggestion(template_name),
                })?;

            // Get template declaration
            if let ast::Declaration::Template(_template) =
                &all_files[entry.file_index].declarations[entry.decl_index]
            {
                // Templates don't have uses_behaviors/uses_schedule yet, but
                // they will For now, just pass empty vecs
                // TODO: Add template resource linking support
            }
        }

        // Merge using merge functions from merge.rs
        let merged_b =
            merge::merge_behavior_links(character.uses_behaviors.clone(), template_behaviors);
        let merged_s =
            merge::merge_schedule_links(character.uses_schedule.clone(), template_schedules);

        (merged_b, merged_s)
    } else {
        // No templates, just use character's own links
        (
            character.uses_behaviors.clone(),
            character.uses_schedule.clone(),
        )
    };

    // Extract fields and prose blocks from merged result
    let (fields, prose_blocks) = extract_fields_and_prose(&merged_fields)?;

    Ok(ResolvedCharacter {
        name: character.name.clone(),
        species: None,
        fields,
        prose_blocks,
        uses_behaviors: merged_behaviors,
        uses_schedule: merged_schedules,
        span: character.span.clone(),
    })
}

/// Convert template AST to resolved type
pub fn convert_template(template: &ast::Template) -> Result<ResolvedTemplate> {
    let (fields, _) = extract_fields_and_prose(&template.fields)?;

    Ok(ResolvedTemplate {
        name: template.name.clone(),
        fields,
        span: template.span.clone(),
    })
}

/// Convert template AST to resolved type with include resolution
///
/// This version resolves template includes by:
/// 1. Recursively resolving all included templates
/// 2. Merging included fields (later includes override earlier ones)
/// 3. Adding template's own fields on top
pub fn convert_template_with_includes(
    template: &ast::Template,
    all_files: &[ast::File],
    name_table: &NameTable,
) -> Result<ResolvedTemplate> {
    // Resolve template includes if any
    let merged_fields = if !template.includes.is_empty() {
        let mut visited = std::collections::HashSet::new();
        merge::resolve_template_includes(template, all_files, name_table, &mut visited)?
    } else {
        template.fields.clone()
    };

    // Extract fields (templates don't have prose blocks)
    let (fields, _) = extract_fields_and_prose(&merged_fields)?;

    Ok(ResolvedTemplate {
        name: template.name.clone(),
        fields,
        span: template.span.clone(),
    })
}

/// Convert life arc AST to resolved type
pub fn convert_life_arc(life_arc: &ast::LifeArc) -> Result<ResolvedLifeArc> {
    let states = life_arc
        .states
        .iter()
        .map(|state| ResolvedArcState {
            name: state.name.clone(),
            transitions: state.transitions.clone(),
            span: state.span.clone(),
        })
        .collect();

    Ok(ResolvedLifeArc {
        name: life_arc.name.clone(),
        states,
        span: life_arc.span.clone(),
    })
}

/// Convert schedule AST to resolved type
pub fn convert_schedule(schedule: &ast::Schedule) -> Result<ResolvedSchedule> {
    let blocks = schedule
        .blocks
        .iter()
        .map(|block| ResolvedScheduleBlock {
            activity: block.activity.clone(),
            start: block.start.clone(),
            end: block.end.clone(),
            span: block.span.clone(),
        })
        .collect();

    Ok(ResolvedSchedule {
        name: schedule.name.clone(),
        blocks,
        span: schedule.span.clone(),
    })
}

/// Convert behavior tree AST to resolved type
pub fn convert_behavior(behavior: &ast::Behavior) -> Result<ResolvedBehavior> {
    Ok(ResolvedBehavior {
        name: behavior.name.clone(),
        root: behavior.root.clone(),
        span: behavior.span.clone(),
    })
}

/// Convert institution AST to resolved type
pub fn convert_institution(institution: &ast::Institution) -> Result<ResolvedInstitution> {
    let (fields, _) = extract_fields_and_prose(&institution.fields)?;

    Ok(ResolvedInstitution {
        name: institution.name.clone(),
        fields,
        span: institution.span.clone(),
    })
}

/// Convert relationship AST to resolved type
pub fn convert_relationship(relationship: &ast::Relationship) -> Result<ResolvedRelationship> {
    let (fields, _) = extract_fields_and_prose(&relationship.fields)?;

    Ok(ResolvedRelationship {
        name: relationship.name.clone(),
        participants: relationship.participants.clone(),
        fields,
        span: relationship.span.clone(),
    })
}

/// Convert location AST to resolved type
pub fn convert_location(location: &ast::Location) -> Result<ResolvedLocation> {
    let (fields, _) = extract_fields_and_prose(&location.fields)?;

    Ok(ResolvedLocation {
        name: location.name.clone(),
        fields,
        span: location.span.clone(),
    })
}

/// Convert species AST to resolved type
pub fn convert_species(species: &ast::Species) -> Result<ResolvedSpecies> {
    let (fields, _) = extract_fields_and_prose(&species.fields)?;

    Ok(ResolvedSpecies {
        name: species.name.clone(),
        fields,
        span: species.span.clone(),
    })
}

/// Extract fields and prose blocks from a field list
///
/// Returns (fields_map, prose_blocks_map)
fn extract_fields_and_prose(
    fields: &[ast::Field],
) -> Result<(HashMap<String, Value>, HashMap<String, ProseBlock>)> {
    let mut fields_map = HashMap::new();
    let mut prose_map = HashMap::new();
    let mut collector = ErrorCollector::new();

    // Validate no reserved keywords
    validate::validate_no_reserved_keywords(fields, &mut collector);

    for field in fields {
        // Check for duplicate field names
        if fields_map.contains_key(&field.name) || prose_map.contains_key(&field.name) {
            collector.add(ResolveError::ValidationError {
                message: format!("Duplicate field name: '{}'", field.name),
                help: Some(format!(
                    "Each field name must be unique. The field '{}' appears more than once. Remove or rename the duplicate field.",
                    field.name
                )),
            });
            continue; // Continue collecting errors
        }

        match &field.value {
            | Value::ProseBlock(prose) => {
                prose_map.insert(field.name.clone(), prose.clone());
            },
            | value => {
                fields_map.insert(field.name.clone(), value.clone());
            },
        }
    }

    // Return any accumulated errors
    collector.into_result((fields_map, prose_map))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::syntax::ast::{
        Character,
        Field,
        Span,
    };

    #[test]
    fn test_convert_simple_character() {
        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![
                Field {
                    name: "age".to_string(),
                    value: Value::Number(34),
                    span: Span::new(0, 10),
                },
                Field {
                    name: "health".to_string(),
                    value: Value::Decimal(0.8),
                    span: Span::new(10, 20),
                },
            ],
            template: None,
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let resolved = convert_character(&character).unwrap();

        assert_eq!(resolved.name, "Martha");
        assert_eq!(resolved.fields.len(), 2);
        assert_eq!(resolved.fields.get("age"), Some(&Value::Number(34)));
        assert_eq!(resolved.fields.get("health"), Some(&Value::Decimal(0.8)));
        assert_eq!(resolved.prose_blocks.len(), 0);
    }

    #[test]
    fn test_convert_character_with_prose() {
        let prose_block = ProseBlock {
            tag: "backstory".to_string(),
            content: "Martha grew up in a small town.".to_string(),
            span: Span::new(10, 50),
        };

        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![
                Field {
                    name: "age".to_string(),
                    value: Value::Number(34),
                    span: Span::new(0, 10),
                },
                Field {
                    name: "backstory".to_string(),
                    value: Value::ProseBlock(prose_block.clone()),
                    span: Span::new(10, 50),
                },
            ],
            template: None,
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 100),
        };

        let resolved = convert_character(&character).unwrap();

        assert_eq!(resolved.name, "Martha");
        assert_eq!(resolved.fields.len(), 1);
        assert_eq!(resolved.fields.get("age"), Some(&Value::Number(34)));
        assert_eq!(resolved.prose_blocks.len(), 1);
        assert_eq!(resolved.prose_blocks.get("backstory"), Some(&prose_block));
    }

    #[test]
    fn test_convert_character_duplicate_field_fails() {
        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![
                Field {
                    name: "age".to_string(),
                    value: Value::Number(34),
                    span: Span::new(0, 10),
                },
                Field {
                    name: "age".to_string(),
                    value: Value::Number(35),
                    span: Span::new(10, 20),
                },
            ],
            template: None,
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let result = convert_character(&character);
        assert!(result.is_err());
    }

    #[test]
    fn test_convert_file_mixed_declarations() {
        let file = ast::File {
            declarations: vec![ast::Declaration::Character(Character {
                name: "Martha".to_string(),
                species: None,
                fields: vec![Field {
                    name: "age".to_string(),
                    value: Value::Number(34),
                    span: Span::new(0, 10),
                }],
                template: None,
                uses_behaviors: None,
                uses_schedule: None,
                span: Span::new(0, 50),
            })],
        };

        let resolved = convert_file(&file).unwrap();

        assert_eq!(resolved.len(), 1);
        match &resolved[0] {
            | ResolvedDeclaration::Character(c) => assert_eq!(c.name, "Martha"),
            | _ => panic!("Expected Character"),
        }
    }

    #[test]
    fn test_convert_file_skips_use_declarations() {
        let file = ast::File {
            declarations: vec![
                ast::Declaration::Use(ast::UseDecl {
                    path: vec!["foo".to_string()],
                    kind: ast::UseKind::Wildcard,
                    span: Span::new(0, 10),
                }),
                ast::Declaration::Character(Character {
                    name: "Martha".to_string(),
                    species: None,
                    fields: vec![],
                    template: None,
                    uses_behaviors: None,
                    uses_schedule: None,

                    span: Span::new(10, 50),
                }),
            ],
        };

        let resolved = convert_file(&file).unwrap();

        // Should only have the character, not the use declaration
        assert_eq!(resolved.len(), 1);
        match &resolved[0] {
            | ResolvedDeclaration::Character(c) => assert_eq!(c.name, "Martha"),
            | _ => panic!("Expected Character"),
        }
    }

    #[test]
    fn test_extract_fields_and_prose_empty() {
        let (fields, prose) = extract_fields_and_prose(&[]).unwrap();
        assert_eq!(fields.len(), 0);
        assert_eq!(prose.len(), 0);
    }

    #[test]
    fn test_extract_fields_and_prose_mixed() {
        let prose_block = ProseBlock {
            tag: "description".to_string(),
            content: "Test content".to_string(),
            span: Span::new(10, 30),
        };

        let fields = vec![
            Field {
                name: "age".to_string(),
                value: Value::Number(30),
                span: Span::new(0, 10),
            },
            Field {
                name: "description".to_string(),
                value: Value::ProseBlock(prose_block.clone()),
                span: Span::new(10, 30),
            },
            Field {
                name: "active".to_string(),
                value: Value::Boolean(true),
                span: Span::new(30, 40),
            },
        ];

        let (field_map, prose_map) = extract_fields_and_prose(&fields).unwrap();

        assert_eq!(field_map.len(), 2);
        assert_eq!(field_map.get("age"), Some(&Value::Number(30)));
        assert_eq!(field_map.get("active"), Some(&Value::Boolean(true)));

        assert_eq!(prose_map.len(), 1);
        assert_eq!(prose_map.get("description"), Some(&prose_block));
    }

    // ===== Template Composition Integration Tests =====

    #[test]
    fn test_convert_character_with_single_template() {
        use crate::resolve::names::NameTable;

        let template = ast::Template {
            name: "Person".to_string(),
            species_base: None,
            fields: vec![Field {
                name: "type".to_string(), // Changed from "species"
                value: Value::Text("human".to_string()),
                span: Span::new(0, 10),
            }],
            strict: false,
            includes: vec![],
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![Field {
                name: "age".to_string(),
                value: Value::Number(34),
                span: Span::new(0, 10),
            }],
            template: Some(vec!["Person".to_string()]),
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 100),
        };

        let declarations = vec![
            ast::Declaration::Template(template),
            ast::Declaration::Character(character.clone()),
        ];

        let file = ast::File {
            declarations: declarations.clone(),
        };
        let name_table = NameTable::from_file(&file).unwrap();

        let files = vec![file.clone()];
        let resolved = convert_character_with_templates(&character, &files, &name_table).unwrap();

        assert_eq!(resolved.name, "Martha");
        assert_eq!(resolved.fields.len(), 2);
        assert_eq!(resolved.fields.get("age"), Some(&Value::Number(34)));
        assert_eq!(
            resolved.fields.get("type"),
            Some(&Value::Text("human".to_string()))
        );
    }

    #[test]
    fn test_convert_character_with_multiple_templates() {
        use crate::resolve::names::NameTable;

        let physical = ast::Template {
            name: "Physical".to_string(),
            species_base: None,
            fields: vec![Field {
                name: "height".to_string(),
                value: Value::Number(0),
                span: Span::new(0, 10),
            }],
            strict: false,
            includes: vec![],
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let mental = ast::Template {
            name: "Mental".to_string(),
            species_base: None,
            fields: vec![Field {
                name: "iq".to_string(),
                value: Value::Number(0),
                span: Span::new(0, 10),
            }],
            strict: false,
            includes: vec![],
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![
                Field {
                    name: "height".to_string(),
                    value: Value::Number(165),
                    span: Span::new(0, 10),
                },
                Field {
                    name: "iq".to_string(),
                    value: Value::Number(120),
                    span: Span::new(10, 20),
                },
            ],
            template: Some(vec!["Physical".to_string(), "Mental".to_string()]),
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 100),
        };

        let declarations = vec![
            ast::Declaration::Template(physical),
            ast::Declaration::Template(mental),
            ast::Declaration::Character(character.clone()),
        ];

        let file = ast::File {
            declarations: declarations.clone(),
        };
        let name_table = NameTable::from_file(&file).unwrap();

        let files = vec![file.clone()];
        let resolved = convert_character_with_templates(&character, &files, &name_table).unwrap();

        assert_eq!(resolved.name, "Martha");
        assert_eq!(resolved.fields.len(), 2);
        assert_eq!(resolved.fields.get("height"), Some(&Value::Number(165)));
        assert_eq!(resolved.fields.get("iq"), Some(&Value::Number(120)));
    }

    #[test]
    fn test_convert_template_with_includes() {
        use crate::resolve::names::NameTable;

        let base = ast::Template {
            name: "Human".to_string(),
            species_base: None,
            fields: vec![Field {
                name: "type".to_string(), // Changed from "species"
                value: Value::Text("human".to_string()),
                span: Span::new(0, 10),
            }],
            strict: false,
            includes: vec![],
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let derived = ast::Template {
            name: "Person".to_string(),
            species_base: None,
            fields: vec![Field {
                name: "age".to_string(),
                value: Value::Number(0),
                span: Span::new(0, 10),
            }],
            strict: false,
            includes: vec!["Human".to_string()],
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let declarations = vec![
            ast::Declaration::Template(base),
            ast::Declaration::Template(derived.clone()),
        ];

        let file = ast::File {
            declarations: declarations.clone(),
        };
        let name_table = NameTable::from_file(&file).unwrap();

        let files = vec![file.clone()];
        let resolved = convert_template_with_includes(&derived, &files, &name_table).unwrap();

        assert_eq!(resolved.name, "Person");
        assert_eq!(resolved.fields.len(), 2);
        assert_eq!(resolved.fields.get("age"), Some(&Value::Number(0)));
        assert_eq!(
            resolved.fields.get("type"),
            Some(&Value::Text("human".to_string()))
        );
    }

    #[test]
    fn test_convert_character_reserved_keyword_fails() {
        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![Field {
                name: "species".to_string(), // Reserved keyword!
                value: Value::Text("human".to_string()),
                span: Span::new(0, 10),
            }],
            template: None,
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let result = convert_character(&character);
        assert!(result.is_err());
        if let Err(ResolveError::ValidationError { message, help }) = result {
            assert!(message.contains("reserved keyword"));
            assert!(message.contains("species"));
            assert!(help.is_some());
        } else {
            panic!("Expected ValidationError for reserved keyword");
        }
    }

    #[test]
    fn test_convert_character_strict_mode_validation() {
        use crate::resolve::names::NameTable;

        let template = ast::Template {
            name: "Person".to_string(),
            species_base: None,
            fields: vec![Field {
                name: "age".to_string(),
                value: Value::Range(Box::new(Value::Number(18)), Box::new(Value::Number(65))),
                span: Span::new(0, 10),
            }],
            strict: true,
            includes: vec![],
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 50),
        };

        let character = Character {
            name: "Martha".to_string(),
            species: None,
            fields: vec![], // No fields - inherits range from template
            template: Some(vec!["Person".to_string()]),
            uses_behaviors: None,
            uses_schedule: None,
            span: Span::new(0, 100),
        };

        let declarations = vec![
            ast::Declaration::Template(template),
            ast::Declaration::Character(character.clone()),
        ];

        let file = ast::File {
            declarations: declarations.clone(),
        };
        let name_table = NameTable::from_file(&file).unwrap();

        let files = vec![file.clone()];
        let result = convert_character_with_templates(&character, &files, &name_table);
        assert!(result.is_err());
        if let Err(ResolveError::ValidationError { message, .. }) = result {
            assert!(message.contains("strict template"));
        }
    }
}
