//! Code actions (quick fixes and refactorings) for Storybook LSP
//!
//! Provides automated fixes for diagnostics and code refactoring actions.

use std::collections::HashMap;

use tower_lsp::lsp_types::{
    CodeAction,
    CodeActionKind,
    CodeActionOrCommand,
    CodeActionParams,
    CodeActionResponse,
    Diagnostic,
    Position,
    Range,
    TextEdit,
    Url,
    WorkspaceEdit,
};

use super::document::Document;
use crate::{
    resolve::{
        find_all_references,
        DeclKind,
    },
    syntax::ast::Span,
};

/// Generate code actions for a given position in a document
pub fn get_code_actions(
    documents: &HashMap<Url, Document>,
    params: &CodeActionParams,
) -> Option<CodeActionResponse> {
    let uri = &params.text_document.uri;
    let doc = documents.get(uri)?;
    let range = params.range;

    let mut actions = Vec::new();

    // Collect all diagnostics at this position
    let diagnostics_at_position: Vec<&Diagnostic> = params
        .context
        .diagnostics
        .iter()
        .filter(|d| ranges_overlap(d.range, range))
        .collect();

    // Generate quick fixes for each diagnostic
    for diagnostic in &diagnostics_at_position {
        actions.extend(create_quick_fixes(documents, uri, doc, diagnostic));
    }

    // Generate refactoring actions (not dependent on diagnostics)
    actions.extend(create_refactoring_actions(documents, uri, doc, range));

    // Sort actions with field accessors first, then alphabetically
    actions.sort_by(|a, b| {
        let title_a = match a {
            | CodeActionOrCommand::CodeAction(action) => &action.title,
            | CodeActionOrCommand::Command(command) => &command.title,
        };
        let title_b = match b {
            | CodeActionOrCommand::CodeAction(action) => &action.title,
            | CodeActionOrCommand::Command(command) => &command.title,
        };

        // Check if actions are field/trait accessors (contain "field" or "trait" in
        // title)
        let is_field_a = title_a.contains(" field") || title_a.contains(" trait");
        let is_field_b = title_b.contains(" field") || title_b.contains(" trait");

        match (is_field_a, is_field_b) {
            | (true, false) => std::cmp::Ordering::Less, // a is field, b is not -> a comes first
            | (false, true) => std::cmp::Ordering::Greater, // b is field, a is not -> b comes first
            | _ => title_a.cmp(title_b),                 // both same type -> sort alphabetically
        }
    });

    if actions.is_empty() {
        None
    } else {
        Some(actions)
    }
}

/// Check if two ranges overlap
fn ranges_overlap(r1: Range, r2: Range) -> bool {
    !(r1.end.line < r2.start.line ||
        (r1.end.line == r2.start.line && r1.end.character <= r2.start.character) ||
        r2.end.line < r1.start.line ||
        (r2.end.line == r1.start.line && r2.end.character <= r1.start.character))
}

/// Create quick fixes for a diagnostic
fn create_quick_fixes(
    _documents: &HashMap<Url, Document>,
    uri: &Url,
    doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Parse the diagnostic message to determine what kind of error it is
    let message = &diagnostic.message;

    if message.contains("duplicate field") {
        actions.extend(fix_duplicate_field(uri, doc, diagnostic));
    } else if message.contains("not found") || message.contains("undefined") {
        actions.extend(create_missing_symbol(uri, doc, diagnostic));
    } else if message.contains("type mismatch") {
        actions.extend(fix_type_mismatch(uri, doc, diagnostic));
    } else if message.contains("duplicate definition") {
        actions.extend(fix_duplicate_definition(uri, doc, diagnostic));
    } else if message.contains("field") && message.contains("does not exist") {
        actions.extend(fix_invalid_field_access(uri, doc, diagnostic));
    } else if message.contains("out of range") {
        actions.extend(fix_trait_out_of_range(uri, doc, diagnostic));
    } else if message.contains("unknown action") {
        actions.extend(fix_unknown_behavior_action(uri, doc, diagnostic));
    } else if message.contains("schedule overlap") || message.contains("overlaps") {
        actions.extend(fix_schedule_overlap(uri, doc, diagnostic));
    }

    actions
}

/// Create refactoring actions (not tied to diagnostics)
fn create_refactoring_actions(
    documents: &HashMap<Url, Document>,
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Check for unused symbols
    if let Some(action) = remove_unused_symbol(documents, uri, doc, range) {
        actions.push(action);
    }

    // Phase 2: Add missing template fields
    if let Some(action) = add_missing_template_fields(uri, doc, range) {
        actions.push(action);
    }

    // Phase 2: Inline template
    if let Some(action) = inline_template(documents, uri, doc, range) {
        actions.push(action);
    }

    // Phase 2: Expand include inline
    if let Some(action) = expand_include_inline(documents, uri, doc, range) {
        actions.push(action);
    }

    // Phase 2: Extract to template
    if let Some(action) = extract_to_template(uri, doc, range) {
        actions.push(action);
    }

    // Phase 3: Generate character from template
    if let Some(action) = generate_character_from_template(uri, doc, range) {
        actions.push(action);
    }

    // Simple test actions (always available)
    actions.extend(add_simple_actions(uri, doc, range));

    // Phase 3: Sort declarations
    if let Some(action) = sort_declarations(uri, doc, range) {
        actions.push(action);
    }

    // Phase 3: Extract common fields
    if let Some(action) = extract_common_fields(uri, doc, range) {
        actions.push(action);
    }

    // Phase 3: Create relationship scaffold
    if let Some(action) = create_relationship_scaffold(uri, doc, range) {
        actions.push(action);
    }

    // Phase 4: Convert species to/from template
    if let Some(action) = convert_species_template(uri, doc, range) {
        actions.push(action);
    }

    actions
}

//
// Phase 1 Actions Implementation
//

/// Fix duplicate field by removing it
fn fix_duplicate_field(
    uri: &Url,
    _doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Extract field name from diagnostic message
    // Expected format: "duplicate field 'fieldname'"
    let field_name = extract_quoted_name(&diagnostic.message);
    if field_name.is_none() {
        return actions;
    }

    // Create action to remove the duplicate field
    let range = diagnostic.range;

    // Extend range to include the entire line (including comma/newline)
    let mut extended_range = range;
    extended_range.start.character = 0;
    // Try to include the newline
    extended_range.end.line += 1;
    extended_range.end.character = 0;

    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: extended_range,
            new_text: String::new(),
        }],
    );

    let action = CodeAction {
        title: format!("Remove duplicate field '{}'", field_name.unwrap()),
        kind: Some(CodeActionKind::QUICKFIX),
        diagnostics: Some(vec![diagnostic.clone()]),
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(true),
        disabled: None,
        data: None,
    };

    actions.push(CodeActionOrCommand::CodeAction(action));
    actions
}

/// Create missing symbol (character, template, species, enum)
fn create_missing_symbol(
    uri: &Url,
    doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Extract symbol name from diagnostic message
    // Expected formats: "name 'X' not found", "'X' is undefined"
    let symbol_name = extract_quoted_name(&diagnostic.message);
    if symbol_name.is_none() {
        return actions;
    }
    let symbol_name = symbol_name.unwrap();

    // Determine what kind of symbol to create based on context
    // For now, offer multiple options and let the user choose
    let symbol_kinds = determine_symbol_kind_from_context(doc, diagnostic);

    for kind in symbol_kinds {
        let declaration = match kind {
            | DeclKind::Character => format!("character {} {{}}\n\n", symbol_name),
            | DeclKind::Template => format!("template {} {{}}\n\n", symbol_name),
            | DeclKind::Species => format!("species {} {{}}\n\n", symbol_name),
            | DeclKind::Location => format!("location {} {{}}\n\n", symbol_name),
            | _ => continue,
        };

        // Insert at the beginning of the file
        let mut changes = HashMap::new();
        changes.insert(
            uri.clone(),
            vec![TextEdit {
                range: Range {
                    start: Position {
                        line: 0,
                        character: 0,
                    },
                    end: Position {
                        line: 0,
                        character: 0,
                    },
                },
                new_text: declaration,
            }],
        );

        let kind_name = match kind {
            | DeclKind::Character => "character",
            | DeclKind::Template => "template",
            | DeclKind::Species => "species",
            | DeclKind::Location => "location",
            | _ => continue,
        };

        let action = CodeAction {
            title: format!("Create {} '{}'", kind_name, symbol_name),
            kind: Some(CodeActionKind::QUICKFIX),
            diagnostics: Some(vec![diagnostic.clone()]),
            edit: Some(WorkspaceEdit {
                changes: Some(changes),
                document_changes: None,
                change_annotations: None,
            }),
            command: None,
            is_preferred: Some(matches!(kind, DeclKind::Character | DeclKind::Template)),
            disabled: None,
            data: None,
        };

        actions.push(CodeActionOrCommand::CodeAction(action));
    }

    actions
}

/// Fix type mismatch
fn fix_type_mismatch(
    uri: &Url,
    doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Extract the type information from the diagnostic message
    // Expected format: "type mismatch: expected X, got Y"
    let message = &diagnostic.message;

    if let Some((expected, got)) = parse_type_mismatch(message) {
        // Offer to change the value to match the expected type
        if expected == "Enum" && got == "String" {
            // Remove quotes from string to make it an enum value
            let range = diagnostic.range;
            let text = get_text_in_range(doc, range);

            if let Some(unquoted) = text.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
                let mut changes = HashMap::new();
                changes.insert(
                    uri.clone(),
                    vec![TextEdit {
                        range,
                        new_text: unquoted.to_string(),
                    }],
                );

                let action = CodeAction {
                    title: format!("Change to enum value: {}", unquoted),
                    kind: Some(CodeActionKind::QUICKFIX),
                    diagnostics: Some(vec![diagnostic.clone()]),
                    edit: Some(WorkspaceEdit {
                        changes: Some(changes),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(true),
                    disabled: None,
                    data: None,
                };

                actions.push(CodeActionOrCommand::CodeAction(action));
            }
        }
    }

    actions
}

/// Fix duplicate definition
fn fix_duplicate_definition(
    uri: &Url,
    _doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Extract symbol name from diagnostic message
    let symbol_name = extract_quoted_name(&diagnostic.message);
    if symbol_name.is_none() {
        return actions;
    }
    let symbol_name = symbol_name.unwrap();

    // Offer to rename the duplicate
    let new_name = format!("{}2", symbol_name);
    let range = diagnostic.range;

    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range,
            new_text: new_name.clone(),
        }],
    );

    let action = CodeAction {
        title: format!("Rename to '{}'", new_name),
        kind: Some(CodeActionKind::QUICKFIX),
        diagnostics: Some(vec![diagnostic.clone()]),
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(true),
        disabled: None,
        data: None,
    };

    actions.push(CodeActionOrCommand::CodeAction(action));

    // Also offer to remove the duplicate
    let mut extended_range = range;
    extended_range.start.character = 0;
    extended_range.end.line += 1;
    extended_range.end.character = 0;

    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: extended_range,
            new_text: String::new(),
        }],
    );

    let action = CodeAction {
        title: "Remove duplicate definition".to_string(),
        kind: Some(CodeActionKind::QUICKFIX),
        diagnostics: Some(vec![diagnostic.clone()]),
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    };

    actions.push(CodeActionOrCommand::CodeAction(action));

    actions
}

/// Remove unused symbol
fn remove_unused_symbol(
    documents: &HashMap<Url, Document>,
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    // Get the symbol at the cursor position
    let offset = range_to_offset(doc, range.start)?;
    let word = doc.word_at_offset(offset)?;

    // Check if this is a symbol definition in the name table
    let entry = doc.name_table.resolve_name(&word)?;

    // Don't offer to remove if cursor is not on the definition itself
    // (We want to only show this action when on the declaration line)
    if !span_contains_position(entry.span.clone(), doc, range.start) {
        return None;
    }

    // Collect all ASTs for cross-file reference checking
    let mut file_asts = Vec::new();
    for document in documents.values() {
        if let Some(ref ast) = document.ast {
            file_asts.push(ast.clone());
        }
    }

    // Find all references using semantic analysis
    let references = find_all_references(&file_asts, &word, entry.kind);

    // If there's only one reference (the definition itself), it's unused
    if references.len() == 1 {
        // Create action to remove the symbol
        let span = entry.span.clone();
        let mut positions = doc.positions.clone();
        let start_pos = positions.offset_to_position(span.start);
        let end_pos = positions.offset_to_position(span.end);

        // Extend to include the entire declaration (whole line)
        let extended_range = Range {
            start: Position {
                line: start_pos.0 as u32,
                character: 0,
            },
            end: Position {
                line: end_pos.0 as u32 + 1,
                character: 0,
            },
        };

        let mut changes = HashMap::new();
        changes.insert(
            uri.clone(),
            vec![TextEdit {
                range: extended_range,
                new_text: String::new(),
            }],
        );

        let kind_name = match entry.kind {
            | DeclKind::Character => "character",
            | DeclKind::Template => "template",
            | DeclKind::Species => "species",
            | DeclKind::Location => "location",
            | _ => "symbol",
        };

        let action = CodeAction {
            title: format!("Remove unused {} '{}'", kind_name, word),
            kind: Some(CodeActionKind::REFACTOR),
            diagnostics: None,
            edit: Some(WorkspaceEdit {
                changes: Some(changes),
                document_changes: None,
                change_annotations: None,
            }),
            command: None,
            is_preferred: Some(false),
            disabled: None,
            data: None,
        };

        return Some(CodeActionOrCommand::CodeAction(action));
    }

    None
}

//
// Phase 2 Actions Implementation
//

/// Add missing fields from template to character
fn add_missing_template_fields(
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the character declaration at this position
    let offset = range_to_offset(doc, range.start)?;

    // DEBUG: Check all characters
    let mut debug_info = format!("Checking {} declarations. ", ast.declarations.len());
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Character(c) = decl {
            debug_info.push_str(&format!(
                "Char '{}' span {}..{} ",
                c.name, c.span.start, c.span.end
            ));
        }
    }

    // Look through all characters to find one at this position
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Character(character) = decl {
            let char_span = character.span.clone();

            // Check if cursor is within this character
            if offset >= char_span.start && offset <= char_span.end {
                debug_info.push_str(&format!("FOUND char '{}' ", character.name));

                // Check if this character uses a template
                if let Some(ref template_names) = character.template {
                    debug_info.push_str(&format!("has template {:?} ", template_names));
                    // For now, just handle the first template
                    if let Some(template_name) = template_names.first() {
                        // Look up the template in the name table
                        if let Some(template_entry) = doc.name_table.resolve_name(template_name) {
                            if template_entry.kind != DeclKind::Template {
                                continue;
                            }

                            // Find the template declaration in the AST
                            let template_decl = ast.declarations.iter().find_map(|d| {
                                if let crate::syntax::ast::Declaration::Template(t) = d {
                                    if t.name == *template_name {
                                        Some(t)
                                    } else {
                                        None
                                    }
                                } else {
                                    None
                                }
                            })?;

                            // Get character's existing fields
                            let char_field_names: std::collections::HashSet<String> =
                                character.fields.iter().map(|f| f.name.clone()).collect();

                            // Find missing fields
                            let missing_fields: Vec<_> = template_decl
                                .fields
                                .iter()
                                .filter(|f| !char_field_names.contains(&f.name))
                                .collect();

                            if missing_fields.is_empty() {
                                return None;
                            }

                            // Generate field text
                            let mut field_text = String::new();
                            for field in &missing_fields {
                                let default_value = get_default_value_for_type(&field.value);
                                field_text
                                    .push_str(&format!("  {}: {},\n", field.name, default_value));
                            }

                            // Find where to insert (after last field or before closing brace)
                            let mut positions = doc.positions.clone();
                            let insert_position = if let Some(last_field) = character.fields.last()
                            {
                                // After last field
                                positions.offset_to_position(last_field.span.end)
                            } else {
                                // Before closing brace - find the opening brace first
                                let opening_brace_offset = doc.text[char_span.start..char_span.end]
                                    .find('{')
                                    .map(|pos| char_span.start + pos + 1)?;
                                positions.offset_to_position(opening_brace_offset)
                            };

                            let mut changes = HashMap::new();
                            changes.insert(
                                uri.clone(),
                                vec![TextEdit {
                                    range: Range {
                                        start: Position {
                                            line: insert_position.0 as u32,
                                            character: insert_position.1 as u32,
                                        },
                                        end: Position {
                                            line: insert_position.0 as u32,
                                            character: insert_position.1 as u32,
                                        },
                                    },
                                    new_text: format!("\n{}", field_text),
                                }],
                            );

                            let action = CodeAction {
                                title: if missing_fields.len() == 1 {
                                    format!("Add missing field '{}'", missing_fields[0].name)
                                } else {
                                    format!("Add {} missing fields", missing_fields.len())
                                },
                                kind: Some(CodeActionKind::REFACTOR),
                                diagnostics: None,
                                edit: Some(WorkspaceEdit {
                                    changes: Some(changes),
                                    document_changes: None,
                                    change_annotations: None,
                                }),
                                command: None,
                                is_preferred: Some(false),
                                disabled: None,
                                data: None,
                            };

                            return Some(CodeActionOrCommand::CodeAction(action));
                        }
                    }
                }
            }
        }
    }

    None
}

/// Debug version of inline_template
/// Inline template - replace template usage with explicit fields
fn inline_template(
    documents: &HashMap<Url, Document>,
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the character declaration at this position
    let offset = range_to_offset(doc, range.start)?;

    // Look through all characters to find one at this position
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Character(character) = decl {
            let char_span = character.span.clone();

            // Check if cursor is within this character
            if offset >= char_span.start && offset <= char_span.end {
                // Check if this character uses a template
                if let Some(ref template_names) = character.template {
                    if template_names.is_empty() {
                        continue;
                    }

                    // For now, handle first template
                    let template_name = &template_names[0];

                    // Look up the template (may be in another file)
                    if let Some(template_entry) = doc.name_table.resolve_name(template_name) {
                        if template_entry.kind != DeclKind::Template {
                            continue;
                        }

                        // Find the template declaration (check current file first, then other
                        // files)
                        let mut template_decl = None;

                        // Try current file first
                        for d in &ast.declarations {
                            if let crate::syntax::ast::Declaration::Template(t) = d {
                                if &t.name == template_name {
                                    template_decl = Some(t.clone());
                                    break;
                                }
                            }
                        }

                        // If not found in current file, search other open documents
                        if template_decl.is_none() {
                            for other_doc in documents.values() {
                                if let Some(other_ast) = &other_doc.ast {
                                    for d in &other_ast.declarations {
                                        if let crate::syntax::ast::Declaration::Template(t) = d {
                                            if &t.name == template_name {
                                                template_decl = Some(t.clone());
                                                break;
                                            }
                                        }
                                    }
                                    if template_decl.is_some() {
                                        break;
                                    }
                                }
                            }
                        }

                        // TODO: Load template from file system using file_index if not in open
                        // documents For now, template must be in an open
                        // file

                        let template_decl = template_decl?;

                        // Get character's existing fields
                        let char_fields: HashMap<String, String> = character
                            .fields
                            .iter()
                            .map(|f| {
                                let value = format_value(&f.value);
                                (f.name.clone(), value)
                            })
                            .collect();

                        // Build inline fields (template fields + character overrides)
                        let mut inline_fields = Vec::new();
                        for template_field in &template_decl.fields {
                            let value = char_fields
                                .get(&template_field.name)
                                .cloned()
                                .unwrap_or_else(|| {
                                    get_default_value_for_type(&template_field.value)
                                });

                            inline_fields.push(format!("  {}: {}", template_field.name, value));
                        }

                        // Add any character fields not in template
                        for (name, value) in &char_fields {
                            if !template_decl.fields.iter().any(|f| &f.name == name) {
                                inline_fields.push(format!("  {}: {}", name, value));
                            }
                        }

                        // Find the "from Template" part to remove
                        let from_keyword_start = doc.text[char_span.start..char_span.end]
                            .find("from")
                            .map(|pos| char_span.start + pos)?;

                        // Find the opening brace after "from Template"
                        let opening_brace = doc.text[from_keyword_start..char_span.end]
                            .find('{')
                            .map(|pos| from_keyword_start + pos)?;

                        let mut positions = doc.positions.clone();
                        let from_start_pos = positions.offset_to_position(from_keyword_start);
                        let brace_pos = positions.offset_to_position(opening_brace);

                        // Create the replacement text
                        let fields_text = if inline_fields.is_empty() {
                            String::from("{}")
                        } else {
                            format!("{{\n{}\n}}", inline_fields.join(",\n"))
                        };

                        let mut changes = HashMap::new();
                        changes.insert(
                            uri.clone(),
                            vec![TextEdit {
                                range: Range {
                                    start: Position {
                                        line: from_start_pos.0 as u32,
                                        character: from_start_pos.1 as u32,
                                    },
                                    end: Position {
                                        line: brace_pos.0 as u32,
                                        character: brace_pos.1 as u32 + 1,
                                    },
                                },
                                new_text: fields_text,
                            }],
                        );

                        let action = CodeAction {
                            title: format!("Inline template '{}'", template_name),
                            kind: Some(CodeActionKind::REFACTOR_INLINE),
                            diagnostics: None,
                            edit: Some(WorkspaceEdit {
                                changes: Some(changes),
                                document_changes: None,
                                change_annotations: None,
                            }),
                            command: None,
                            is_preferred: Some(false),
                            disabled: None,
                            data: None,
                        };

                        return Some(CodeActionOrCommand::CodeAction(action));
                    }
                }
            }
        }
    }

    None
}

/// Expand include inline - replace `include TemplateName` with template's
/// fields
fn expand_include_inline(
    documents: &HashMap<Url, Document>,
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    use crate::syntax::lexer::{
        Lexer,
        Token,
    };

    let ast = doc.ast.as_ref()?;
    let offset = range_to_offset(doc, range.start)?;

    // Find which template we're in
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Template(template) = decl {
            let template_span = &template.span;

            // Check if cursor is within this template
            if offset >= template_span.start && offset <= template_span.end {
                // Check if this template has includes
                if template.includes.is_empty() {
                    continue;
                }

                // Lex the template body to find include statements
                let template_text = &doc.text[template_span.start..template_span.end];
                let lexer = Lexer::new(template_text);
                let tokens: Vec<_> = lexer.collect();

                // Find include statements and their positions
                let mut include_positions = Vec::new();
                let mut i = 0;
                while i < tokens.len() {
                    if let (tok_start, Token::Include, _) = tokens[i] {
                        // Next token should be the template name
                        if i + 1 < tokens.len() {
                            if let (_name_start, Token::Ident(name), name_end) = &tokens[i + 1] {
                                let abs_start = template_span.start + tok_start;
                                let abs_end = template_span.start + name_end;
                                include_positions.push((abs_start, abs_end, name.clone()));
                            }
                        }
                    }
                    i += 1;
                }

                // Find which include statement the cursor is on
                for (include_start, include_end, include_name) in include_positions {
                    if offset >= include_start && offset <= include_end {
                        // Look up the included template
                        if let Some(template_entry) = doc.name_table.resolve_name(&include_name) {
                            if template_entry.kind != DeclKind::Template {
                                continue;
                            }

                            // Find the template declaration
                            let mut template_decl = None;

                            // Try current file first
                            for d in &ast.declarations {
                                if let crate::syntax::ast::Declaration::Template(t) = d {
                                    if t.name == include_name {
                                        template_decl = Some(t.clone());
                                        break;
                                    }
                                }
                            }

                            // If not found, search other open documents
                            if template_decl.is_none() {
                                for other_doc in documents.values() {
                                    if let Some(other_ast) = &other_doc.ast {
                                        for d in &other_ast.declarations {
                                            if let crate::syntax::ast::Declaration::Template(t) = d
                                            {
                                                if t.name == include_name {
                                                    template_decl = Some(t.clone());
                                                    break;
                                                }
                                            }
                                        }
                                        if template_decl.is_some() {
                                            break;
                                        }
                                    }
                                }
                            }

                            let template_decl = template_decl?;

                            // Build the expanded fields text
                            let mut expanded_fields = Vec::new();
                            for field in &template_decl.fields {
                                let value = format_value(&field.value);
                                expanded_fields.push(format!("  {}: {}", field.name, value));
                            }

                            let fields_text = if expanded_fields.is_empty() {
                                String::new()
                            } else {
                                expanded_fields.join("\n") + "\n"
                            };

                            // Find the full line containing the include statement
                            // to replace it completely
                            let include_line_start = doc.text[..include_start]
                                .rfind('\n')
                                .map(|pos| pos + 1)
                                .unwrap_or(0);

                            let include_line_end = doc.text[include_end..]
                                .find('\n')
                                .map(|pos| include_end + pos)
                                .unwrap_or(doc.text.len());

                            let mut positions = doc.positions.clone();
                            let start_pos = positions.offset_to_position(include_line_start);
                            let end_pos = positions.offset_to_position(include_line_end + 1); // +1 to include newline

                            let mut changes = HashMap::new();
                            changes.insert(
                                uri.clone(),
                                vec![TextEdit {
                                    range: Range {
                                        start: Position {
                                            line: start_pos.0 as u32,
                                            character: start_pos.1 as u32,
                                        },
                                        end: Position {
                                            line: end_pos.0 as u32,
                                            character: end_pos.1 as u32,
                                        },
                                    },
                                    new_text: fields_text,
                                }],
                            );

                            let action = CodeAction {
                                title: format!("Expand include '{}'", include_name),
                                kind: Some(CodeActionKind::REFACTOR_INLINE),
                                diagnostics: None,
                                edit: Some(WorkspaceEdit {
                                    changes: Some(changes),
                                    document_changes: None,
                                    change_annotations: None,
                                }),
                                command: None,
                                is_preferred: Some(false),
                                disabled: None,
                                data: None,
                            };

                            return Some(CodeActionOrCommand::CodeAction(action));
                        }
                    }
                }
            }
        }
    }

    None
}

/// Extract to template - create a template from character fields
fn extract_to_template(uri: &Url, doc: &Document, range: Range) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the character declaration at this position
    let offset = range_to_offset(doc, range.start)?;

    // Look through all characters to find one at this position
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Character(character) = decl {
            let char_span = character.span.clone();

            // Check if cursor is within this character
            if offset >= char_span.start && offset <= char_span.end {
                // Only offer this if character has fields and doesn't already use a template
                if character.fields.is_empty() {
                    return None;
                }

                // Generate template name (capitalize character name + "Template")
                let template_name = format!("{}Template", character.name);

                // Build template fields from character fields
                let mut template_fields = Vec::new();
                for field in &character.fields {
                    let type_str = infer_type_from_value(&field.value);
                    template_fields.push(format!("  {}: {}", field.name, type_str));
                }

                let template_text = format!(
                    "template {} {{\n{}\n}}\n\n",
                    template_name,
                    template_fields.join(",\n")
                );

                // Build character fields (values only, types come from template)
                let mut char_fields = Vec::new();
                for field in &character.fields {
                    let value = format_value(&field.value);
                    char_fields.push(format!("  {}: {}", field.name, value));
                }

                // Find character declaration start
                let mut positions = doc.positions.clone();

                // Find the closing brace of the character
                let closing_brace = char_span.end - 1; // Assuming } is last char

                let brace_end_pos = positions.offset_to_position(closing_brace + 1);

                let new_char_body = if char_fields.is_empty() {
                    String::from(" {}")
                } else {
                    format!(
                        " from {} {{\n{}\n}}",
                        template_name,
                        char_fields.join(",\n")
                    )
                };

                // Find where "character Name" ends
                let name_end = char_span.start +
                    "character ".len() +
                    character.name.len() +
                    if character.species.is_some() {
                        // Account for ": Species" if present
                        3 + character.species.as_ref().unwrap().len()
                    } else {
                        0
                    };

                let name_end_pos = positions.offset_to_position(name_end);

                let mut changes = HashMap::new();
                changes.insert(
                    uri.clone(),
                    vec![
                        // Insert template at beginning
                        TextEdit {
                            range: Range {
                                start: Position {
                                    line: 0,
                                    character: 0,
                                },
                                end: Position {
                                    line: 0,
                                    character: 0,
                                },
                            },
                            new_text: template_text,
                        },
                        // Replace character body
                        TextEdit {
                            range: Range {
                                start: Position {
                                    line: name_end_pos.0 as u32,
                                    character: name_end_pos.1 as u32,
                                },
                                end: Position {
                                    line: brace_end_pos.0 as u32,
                                    character: brace_end_pos.1 as u32,
                                },
                            },
                            new_text: new_char_body,
                        },
                    ],
                );

                let action = CodeAction {
                    title: format!("Extract to template '{}'", template_name),
                    kind: Some(CodeActionKind::REFACTOR_EXTRACT),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(changes),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(false),
                    disabled: None,
                    data: None,
                };

                return Some(CodeActionOrCommand::CodeAction(action));
            }
        }
    }

    None
}

//
// Phase 3 Actions Implementation
//

/// Generate a new character from a template
fn generate_character_from_template(
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the template declaration at this position
    let offset = range_to_offset(doc, range.start)?;

    // Look through all templates to find one at this position
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Template(template) = decl {
            let template_span = template.span.clone();

            // Check if cursor is within this template
            if offset >= template_span.start && offset <= template_span.end {
                // Generate a new character name
                let char_name = "NewCharacter";

                // Build character fields with default values
                let mut char_fields = Vec::new();
                for field in &template.fields {
                    let default_value = get_default_value_for_type(&field.value);
                    char_fields.push(format!("  {}: {}", field.name, default_value));
                }

                let char_text = if char_fields.is_empty() {
                    format!("\ncharacter {} from {} {{}}\n", char_name, template.name)
                } else {
                    format!(
                        "\ncharacter {} from {} {{\n{}\n}}\n",
                        char_name,
                        template.name,
                        char_fields.join(",\n")
                    )
                };

                // Insert after the template
                let mut positions = doc.positions.clone();
                let insert_pos = positions.offset_to_position(template_span.end);

                let mut changes = HashMap::new();
                changes.insert(
                    uri.clone(),
                    vec![TextEdit {
                        range: Range {
                            start: Position {
                                line: insert_pos.0 as u32,
                                character: insert_pos.1 as u32,
                            },
                            end: Position {
                                line: insert_pos.0 as u32,
                                character: insert_pos.1 as u32,
                            },
                        },
                        new_text: char_text,
                    }],
                );

                let action = CodeAction {
                    title: format!("Generate character from template '{}'", template.name),
                    kind: Some(CodeActionKind::REFACTOR),
                    diagnostics: None,
                    edit: Some(WorkspaceEdit {
                        changes: Some(changes),
                        document_changes: None,
                        change_annotations: None,
                    }),
                    command: None,
                    is_preferred: Some(false),
                    disabled: None,
                    data: None,
                };

                return Some(CodeActionOrCommand::CodeAction(action));
            }
        }
    }

    None
}

/// Sort declarations alphabetically within each category
fn sort_declarations(uri: &Url, doc: &Document, _range: Range) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Only offer this if there are multiple declarations
    if ast.declarations.len() < 2 {
        return None;
    }

    // Group declarations by type
    use crate::syntax::ast::Declaration;
    let mut templates = Vec::new();
    let mut species = Vec::new();
    let mut characters = Vec::new();
    let mut enums = Vec::new();
    let mut locations = Vec::new();
    let mut institutions = Vec::new();
    let mut relationships = Vec::new();
    let mut others = Vec::new();

    for decl in &ast.declarations {
        match decl {
            | Declaration::Template(t) => templates.push((t.name.clone(), decl)),
            | Declaration::Species(s) => species.push((s.name.clone(), decl)),
            | Declaration::Character(c) => characters.push((c.name.clone(), decl)),
            | Declaration::Location(l) => locations.push((l.name.clone(), decl)),
            | Declaration::Institution(i) => institutions.push((i.name.clone(), decl)),
            | Declaration::Relationship(r) => relationships.push((r.name.clone(), decl)),
            | Declaration::Use(_) => {}, // Skip use declarations - they stay at top
            | _ => others.push((String::new(), decl)),
        }
    }

    // Check if sorting would change anything
    let is_sorted = |group: &Vec<(String, &Declaration)>| -> bool {
        group.windows(2).all(|w| w[0].0 <= w[1].0)
    };

    if is_sorted(&templates) &&
        is_sorted(&species) &&
        is_sorted(&characters) &&
        is_sorted(&enums) &&
        is_sorted(&locations) &&
        is_sorted(&institutions) &&
        is_sorted(&relationships)
    {
        // Already sorted
        return None;
    }

    // Sort each group
    templates.sort_by(|a, b| a.0.cmp(&b.0));
    species.sort_by(|a, b| a.0.cmp(&b.0));
    characters.sort_by(|a, b| a.0.cmp(&b.0));
    enums.sort_by(|a, b| a.0.cmp(&b.0));
    locations.sort_by(|a, b| a.0.cmp(&b.0));
    institutions.sort_by(|a, b| a.0.cmp(&b.0));
    relationships.sort_by(|a, b| a.0.cmp(&b.0));

    // Build sorted text by extracting the original text for each declaration
    let mut sorted_text = String::new();

    let add_group = |text: &mut String, group: &Vec<(String, &Declaration)>, doc: &Document| {
        if !group.is_empty() {
            for (_name, decl) in group {
                let span = get_declaration_span(decl);

                // Safety checks
                if span.start >= doc.text.len() ||
                    span.end > doc.text.len() ||
                    span.start >= span.end
                {
                    eprintln!(
                        "Invalid span: {}..{} for doc length {}",
                        span.start,
                        span.end,
                        doc.text.len()
                    );
                    continue;
                }

                // Extract declaration text
                let decl_text = match doc.text.get(span.start..span.end) {
                    | Some(t) => t,
                    | None => {
                        eprintln!(
                            "Failed to extract text for span {}..{}",
                            span.start, span.end
                        );
                        continue;
                    },
                };

                if decl_text.is_empty() {
                    eprintln!(
                        "Empty declaration text for span {}..{}",
                        span.start, span.end
                    );
                    continue;
                }

                text.push_str(decl_text);
                if !decl_text.ends_with('\n') {
                    text.push('\n');
                }
            }
            text.push('\n');
        }
    };

    add_group(&mut sorted_text, &templates, doc);
    add_group(&mut sorted_text, &species, doc);
    add_group(&mut sorted_text, &enums, doc);
    add_group(&mut sorted_text, &locations, doc);
    add_group(&mut sorted_text, &institutions, doc);
    add_group(&mut sorted_text, &characters, doc);
    add_group(&mut sorted_text, &relationships, doc);
    add_group(&mut sorted_text, &others, doc);

    // Safety check: don't replace if sorted text is empty or too short
    if sorted_text.trim().is_empty() {
        eprintln!("Sorted text is empty, aborting sort");
        return None;
    }

    if sorted_text.len() < doc.text.len() / 2 {
        eprintln!(
            "Sorted text is suspiciously short ({} vs {}), aborting",
            sorted_text.len(),
            doc.text.len()
        );
        return None;
    }

    // Replace entire file content
    let mut changes = HashMap::new();
    let line_count = doc.text.lines().count();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: 0,
                    character: 0,
                },
                end: Position {
                    line: line_count as u32,
                    character: 0,
                },
            },
            new_text: sorted_text.trim_end().to_string() + "\n",
        }],
    );

    let action = CodeAction {
        title: "Sort declarations".to_string(),
        kind: Some(CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    };

    Some(CodeActionOrCommand::CodeAction(action))
}

/// Add simple, always-available actions
fn add_simple_actions(uri: &Url, doc: &Document, range: Range) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Get username for Google-style comments (from git or default)
    let username = std::env::var("USER")
        .or_else(|_| std::env::var("USERNAME"))
        .unwrap_or_else(|_| "username".to_string());

    // Action 1: Add Google-style TODO comment
    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: range.start.line,
                    character: 0,
                },
                end: Position {
                    line: range.start.line,
                    character: 0,
                },
            },
            new_text: format!("// TODO({}): \n", username),
        }],
    );

    actions.push(CodeActionOrCommand::CodeAction(CodeAction {
        title: "Add todo comment".to_string(),
        kind: Some(CodeActionKind::REFACTOR),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    }));

    // Action 2: Add FIXME comment
    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: range.start.line,
                    character: 0,
                },
                end: Position {
                    line: range.start.line,
                    character: 0,
                },
            },
            new_text: format!("// FIXME({}): \n", username),
        }],
    );

    actions.push(CodeActionOrCommand::CodeAction(CodeAction {
        title: "Add fixme comment".to_string(),
        kind: Some(CodeActionKind::REFACTOR),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    }));

    // Action 3: Add NOTE comment
    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: range.start.line,
                    character: 0,
                },
                end: Position {
                    line: range.start.line,
                    character: 0,
                },
            },
            new_text: format!("// NB(@{}): \n", username),
        }],
    );

    actions.push(CodeActionOrCommand::CodeAction(CodeAction {
        title: "Add note comment".to_string(),
        kind: Some(CodeActionKind::REFACTOR),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    }));

    // Action 4: Add section divider
    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: range.start.line,
                    character: 0,
                },
                end: Position {
                    line: range.start.line,
                    character: 0,
                },
            },
            new_text: "// ============================================================\n// \n// ============================================================\n\n".to_string(),
        }],
    );

    actions.push(CodeActionOrCommand::CodeAction(CodeAction {
        title: "Add section divider".to_string(),
        kind: Some(CodeActionKind::REFACTOR),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    }));

    // Action 5: Add documentation comment above declaration
    if let Some(ast) = &doc.ast {
        let offset = range_to_offset(doc, range.start);
        if let Some(offset) = offset {
            // Find if we're on a declaration
            for decl in &ast.declarations {
                let span = get_declaration_span(decl);
                if offset >= span.start && offset <= span.end {
                    let decl_name = get_declaration_name(decl);
                    let decl_type = get_declaration_type_name(decl);

                    let doc_comment =
                        format!("// {} {}\n//\n// Description: \n", decl_type, decl_name);

                    // Find the line number for this offset
                    let text_before = &doc.text[..span.start.min(doc.text.len())];
                    let line_num = text_before.matches('\n').count();

                    let mut changes = HashMap::new();
                    changes.insert(
                        uri.clone(),
                        vec![TextEdit {
                            range: Range {
                                start: Position {
                                    line: line_num as u32,
                                    character: 0,
                                },
                                end: Position {
                                    line: line_num as u32,
                                    character: 0,
                                },
                            },
                            new_text: doc_comment,
                        }],
                    );

                    actions.push(CodeActionOrCommand::CodeAction(CodeAction {
                        title: format!("Add documentation comment for {}", decl_name),
                        kind: Some(CodeActionKind::REFACTOR),
                        diagnostics: None,
                        edit: Some(WorkspaceEdit {
                            changes: Some(changes),
                            document_changes: None,
                            change_annotations: None,
                        }),
                        command: None,
                        is_preferred: Some(false),
                        disabled: None,
                        data: None,
                    }));
                    break;
                }
            }
        }
    }

    // Action 6: Duplicate declaration
    if let Some(ast) = &doc.ast {
        let offset = range_to_offset(doc, range.start);
        if let Some(offset) = offset {
            // Find if we're on a declaration
            for decl in &ast.declarations {
                let span = get_declaration_span(decl);
                if offset >= span.start && offset <= span.end {
                    // Extract the declaration text
                    if let Some(decl_text) = doc.text.get(span.start..span.end) {
                        let decl_name = get_declaration_name(decl);

                        // Find line number after this declaration
                        let text_before = &doc.text[..span.end.min(doc.text.len())];
                        let line_num = text_before.matches('\n').count();

                        let mut new_text = String::from("\n\n");
                        new_text.push_str(decl_text);
                        if !decl_text.ends_with('\n') {
                            new_text.push('\n');
                        }

                        let mut changes = HashMap::new();
                        changes.insert(
                            uri.clone(),
                            vec![TextEdit {
                                range: Range {
                                    start: Position {
                                        line: line_num as u32,
                                        character: 0,
                                    },
                                    end: Position {
                                        line: line_num as u32,
                                        character: 0,
                                    },
                                },
                                new_text,
                            }],
                        );

                        actions.push(CodeActionOrCommand::CodeAction(CodeAction {
                            title: format!("Duplicate {}", decl_name),
                            kind: Some(CodeActionKind::REFACTOR),
                            diagnostics: None,
                            edit: Some(WorkspaceEdit {
                                changes: Some(changes),
                                document_changes: None,
                                change_annotations: None,
                            }),
                            command: None,
                            is_preferred: Some(false),
                            disabled: None,
                            data: None,
                        }));
                    }
                    break;
                }
            }
        }
    }

    actions
}

/// Get the name of a declaration
fn get_declaration_name(decl: &crate::syntax::ast::Declaration) -> String {
    use crate::syntax::ast::Declaration;
    match decl {
        | Declaration::Template(t) => t.name.clone(),
        | Declaration::Species(s) => s.name.clone(),
        | Declaration::Character(c) => c.name.clone(),
        | Declaration::Location(l) => l.name.clone(),
        | Declaration::Institution(i) => i.name.clone(),
        | Declaration::Relationship(r) => r.name.clone(),
        | Declaration::LifeArc(la) => la.name.clone(),
        | Declaration::Schedule(s) => s.name.clone(),
        | Declaration::Behavior(b) => b.name.clone(),
        | Declaration::Use(_) => "use".to_string(),
        | Declaration::Concept(c) => c.name.clone(),
        | Declaration::SubConcept(sc) => sc.name.clone(),
        | Declaration::Definition(cc) => cc.name.clone(),
    }
}

/// Get the type name of a declaration
fn get_declaration_type_name(decl: &crate::syntax::ast::Declaration) -> &'static str {
    use crate::syntax::ast::Declaration;
    match decl {
        | Declaration::Template(_) => "Template",
        | Declaration::Species(_) => "Species",
        | Declaration::Character(_) => "Character",
        | Declaration::Location(_) => "Location",
        | Declaration::Institution(_) => "Institution",
        | Declaration::Relationship(_) => "Relationship",
        | Declaration::LifeArc(_) => "Life Arc",
        | Declaration::Schedule(_) => "Schedule",
        | Declaration::Behavior(_) => "Behavior",
        | Declaration::Use(_) => "Use",
        | Declaration::Concept(_) => "Concept",
        | Declaration::SubConcept(_) => "Sub Concept",
        | Declaration::Definition(_) => "Definition",
    }
}

/// Extract common fields from multiple characters to a template
fn extract_common_fields(uri: &Url, doc: &Document, range: Range) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the character at cursor position
    let offset = range_to_offset(doc, range.start)?;

    // Find the character we're on
    let current_char = ast.declarations.iter().find_map(|d| {
        if let crate::syntax::ast::Declaration::Character(c) = d {
            if offset >= c.span.start && offset <= c.span.end {
                Some(c)
            } else {
                None
            }
        } else {
            None
        }
    })?;

    // Find all other characters
    let all_characters: Vec<_> = ast
        .declarations
        .iter()
        .filter_map(|d| {
            if let crate::syntax::ast::Declaration::Character(c) = d {
                Some(c)
            } else {
                None
            }
        })
        .collect();

    if all_characters.len() < 2 {
        // Need at least 2 characters
        return None;
    }

    // Find common fields across all characters
    let mut common_fields: HashMap<String, String> = HashMap::new();

    // Start with current character's fields
    for field in &current_char.fields {
        let field_type = infer_type_from_value(&field.value);
        common_fields.insert(field.name.clone(), field_type);
    }

    // Check other characters and keep only common fields
    for other_char in &all_characters {
        if other_char.name == current_char.name {
            continue;
        }

        let other_fields: HashMap<String, String> = other_char
            .fields
            .iter()
            .map(|f| (f.name.clone(), infer_type_from_value(&f.value)))
            .collect();

        // Keep only fields that exist in both with same type
        common_fields.retain(|name, typ| other_fields.get(name).map(|t| t == typ).unwrap_or(false));
    }

    // Need at least one common field
    if common_fields.is_empty() {
        return None;
    }

    // Generate template name
    let template_name = "CommonFields";

    // Build template text
    let mut template_fields = Vec::new();
    for (name, typ) in &common_fields {
        template_fields.push(format!("  {}: {}", name, typ));
    }

    let template_text = format!(
        "template {} {{\n{}\n}}\n\n",
        template_name,
        template_fields.join(",\n")
    );

    // Insert template at beginning
    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: 0,
                    character: 0,
                },
                end: Position {
                    line: 0,
                    character: 0,
                },
            },
            new_text: template_text,
        }],
    );

    let action = CodeAction {
        title: format!(
            "Extract {} common field{} to template",
            common_fields.len(),
            if common_fields.len() == 1 { "" } else { "s" }
        ),
        kind: Some(CodeActionKind::REFACTOR_EXTRACT),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    };

    Some(CodeActionOrCommand::CodeAction(action))
}

/// Create a relationship scaffold between two characters
fn create_relationship_scaffold(
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the character at cursor position
    let offset = range_to_offset(doc, range.start)?;

    // Find the character we're on
    let current_char = ast.declarations.iter().find_map(|d| {
        if let crate::syntax::ast::Declaration::Character(c) = d {
            if offset >= c.span.start && offset <= c.span.end {
                Some(c)
            } else {
                None
            }
        } else {
            None
        }
    })?;

    // Find another character to create a relationship with
    let other_char = ast.declarations.iter().find_map(|d| {
        if let crate::syntax::ast::Declaration::Character(c) = d {
            if c.name != current_char.name {
                Some(c)
            } else {
                None
            }
        } else {
            None
        }
    })?;

    // Generate relationship name
    let relationship_name = format!("{}{}", current_char.name, other_char.name);

    // Build relationship text
    let relationship_text = format!(
        "\nrelationship {} {{\n  {} <-> {}\n}}\n",
        relationship_name, current_char.name, other_char.name
    );

    // Insert at end of file
    let line_count = doc.text.lines().count();
    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: Range {
                start: Position {
                    line: line_count as u32,
                    character: 0,
                },
                end: Position {
                    line: line_count as u32,
                    character: 0,
                },
            },
            new_text: relationship_text,
        }],
    );

    let action = CodeAction {
        title: format!(
            "Create relationship between '{}' and '{}'",
            current_char.name, other_char.name
        ),
        kind: Some(CodeActionKind::REFACTOR),
        diagnostics: None,
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(false),
        disabled: None,
        data: None,
    };

    Some(CodeActionOrCommand::CodeAction(action))
}

/// Helper to get the span of any declaration
fn get_declaration_span(decl: &crate::syntax::ast::Declaration) -> Span {
    use crate::syntax::ast::Declaration;
    match decl {
        | Declaration::Template(t) => t.span.clone(),
        | Declaration::Species(s) => s.span.clone(),
        | Declaration::Character(c) => c.span.clone(),
        | Declaration::Location(l) => l.span.clone(),
        | Declaration::Institution(i) => i.span.clone(),
        | Declaration::Relationship(r) => r.span.clone(),
        | Declaration::LifeArc(la) => la.span.clone(),
        | Declaration::Schedule(s) => s.span.clone(),
        | Declaration::Behavior(b) => b.span.clone(),
        | Declaration::Use(u) => u.span.clone(),
        | Declaration::Concept(c) => c.span.clone(),
        | Declaration::SubConcept(sc) => sc.span.clone(),
        | Declaration::Definition(cc) => cc.span.clone(),
    }
}

//
// Phase 4 Actions Implementation
//

/// Fix invalid field access
fn fix_invalid_field_access(
    uri: &Url,
    _doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Extract field name from diagnostic message
    // Expected format: "field 'X' does not exist on type 'Y'"
    let field_name = extract_quoted_name(&diagnostic.message);
    if field_name.is_none() {
        return actions;
    }

    let range = diagnostic.range;

    // Offer to remove the invalid field
    let mut extended_range = range;
    extended_range.start.character = 0;
    extended_range.end.line += 1;
    extended_range.end.character = 0;

    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: extended_range,
            new_text: String::new(),
        }],
    );

    let action = CodeAction {
        title: format!("Remove invalid field '{}'", field_name.unwrap()),
        kind: Some(CodeActionKind::QUICKFIX),
        diagnostics: Some(vec![diagnostic.clone()]),
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(true),
        disabled: None,
        data: None,
    };

    actions.push(CodeActionOrCommand::CodeAction(action));
    actions
}

/// Fix trait value out of range
fn fix_trait_out_of_range(
    uri: &Url,
    doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Parse the diagnostic message to extract the range
    // Expected format: "trait value X out of range [min, max]"
    let message = &diagnostic.message;

    // Try to extract min and max values
    if let Some(range_str) = message.split('[').nth(1) {
        if let Some(range_part) = range_str.split(']').next() {
            let parts: Vec<&str> = range_part.split(',').collect();
            if parts.len() == 2 {
                let min = parts[0].trim();
                let max = parts[1].trim();

                let range = diagnostic.range;
                let current_value = get_text_in_range(doc, range);

                // Offer to clamp to min
                if current_value != min {
                    let mut changes = HashMap::new();
                    changes.insert(
                        uri.clone(),
                        vec![TextEdit {
                            range,
                            new_text: min.to_string(),
                        }],
                    );

                    let action = CodeAction {
                        title: format!("Change to minimum value ({})", min),
                        kind: Some(CodeActionKind::QUICKFIX),
                        diagnostics: Some(vec![diagnostic.clone()]),
                        edit: Some(WorkspaceEdit {
                            changes: Some(changes),
                            document_changes: None,
                            change_annotations: None,
                        }),
                        command: None,
                        is_preferred: Some(false),
                        disabled: None,
                        data: None,
                    };

                    actions.push(CodeActionOrCommand::CodeAction(action));
                }

                // Offer to clamp to max
                if current_value != max {
                    let mut changes = HashMap::new();
                    changes.insert(
                        uri.clone(),
                        vec![TextEdit {
                            range,
                            new_text: max.to_string(),
                        }],
                    );

                    let action = CodeAction {
                        title: format!("Change to maximum value ({})", max),
                        kind: Some(CodeActionKind::QUICKFIX),
                        diagnostics: Some(vec![diagnostic.clone()]),
                        edit: Some(WorkspaceEdit {
                            changes: Some(changes),
                            document_changes: None,
                            change_annotations: None,
                        }),
                        command: None,
                        is_preferred: Some(true),
                        disabled: None,
                        data: None,
                    };

                    actions.push(CodeActionOrCommand::CodeAction(action));
                }
            }
        }
    }

    actions
}

/// Fix unknown behavior action
fn fix_unknown_behavior_action(
    _uri: &Url,
    _doc: &Document,
    _diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    // TODO: Re-implement using concept/sub_concept system instead of enum
    // For now, return no code actions
    Vec::new()
}

/// Fix schedule overlap
fn fix_schedule_overlap(
    uri: &Url,
    _doc: &Document,
    diagnostic: &Diagnostic,
) -> Vec<CodeActionOrCommand> {
    let mut actions = Vec::new();

    // Offer to remove the overlapping schedule entry
    let range = diagnostic.range;
    let mut extended_range = range;
    extended_range.start.character = 0;
    extended_range.end.line += 1;
    extended_range.end.character = 0;

    let mut changes = HashMap::new();
    changes.insert(
        uri.clone(),
        vec![TextEdit {
            range: extended_range,
            new_text: String::new(),
        }],
    );

    let action = CodeAction {
        title: "Remove overlapping schedule entry".to_string(),
        kind: Some(CodeActionKind::QUICKFIX),
        diagnostics: Some(vec![diagnostic.clone()]),
        edit: Some(WorkspaceEdit {
            changes: Some(changes),
            document_changes: None,
            change_annotations: None,
        }),
        command: None,
        is_preferred: Some(true),
        disabled: None,
        data: None,
    };

    actions.push(CodeActionOrCommand::CodeAction(action));
    actions
}

/// Convert between species and template
fn convert_species_template(
    uri: &Url,
    doc: &Document,
    range: Range,
) -> Option<CodeActionOrCommand> {
    // Get the AST
    let ast = doc.ast.as_ref()?;

    // Find the character declaration at this position
    let offset = range_to_offset(doc, range.start)?;

    // Look through all characters to find one at this position
    for decl in &ast.declarations {
        if let crate::syntax::ast::Declaration::Character(character) = decl {
            let char_span = character.span.clone();

            // Check if cursor is within this character
            if offset >= char_span.start && offset <= char_span.end {
                // Check if it has species or template
                if let Some(ref species) = character.species {
                    // Has species, offer to convert to template
                    // Find ": Species" in the text and replace with "from Species"
                    let text = &doc.text[char_span.start..char_span.end];
                    if let Some(colon_pos) = text.find(&format!(": {}", species)) {
                        let abs_colon_pos = char_span.start + colon_pos;
                        let mut positions = doc.positions.clone();
                        let (line, col) = positions.offset_to_position(abs_colon_pos);
                        let (end_line, end_col) =
                            positions.offset_to_position(abs_colon_pos + 2 + species.len());

                        let mut changes = HashMap::new();
                        changes.insert(
                            uri.clone(),
                            vec![TextEdit {
                                range: Range {
                                    start: Position {
                                        line: line as u32,
                                        character: col as u32,
                                    },
                                    end: Position {
                                        line: end_line as u32,
                                        character: end_col as u32,
                                    },
                                },
                                new_text: format!("from {}", species),
                            }],
                        );

                        let action = CodeAction {
                            title: "Convert species to template".to_string(),
                            kind: Some(CodeActionKind::REFACTOR),
                            diagnostics: None,
                            edit: Some(WorkspaceEdit {
                                changes: Some(changes),
                                document_changes: None,
                                change_annotations: None,
                            }),
                            command: None,
                            is_preferred: Some(false),
                            disabled: None,
                            data: None,
                        };

                        return Some(CodeActionOrCommand::CodeAction(action));
                    }
                } else if let Some(ref templates) = character.template {
                    if let Some(template_name) = templates.first() {
                        // Has template, offer to convert to species
                        // Find "from Template" in the text and replace with ": Template"
                        let text = &doc.text[char_span.start..char_span.end];
                        if let Some(from_pos) = text.find(&format!("from {}", template_name)) {
                            let abs_from_pos = char_span.start + from_pos;
                            let mut positions = doc.positions.clone();
                            let (line, col) = positions.offset_to_position(abs_from_pos);
                            let (end_line, end_col) = positions
                                .offset_to_position(abs_from_pos + 5 + template_name.len());

                            let mut changes = HashMap::new();
                            changes.insert(
                                uri.clone(),
                                vec![TextEdit {
                                    range: Range {
                                        start: Position {
                                            line: line as u32,
                                            character: col as u32,
                                        },
                                        end: Position {
                                            line: end_line as u32,
                                            character: end_col as u32,
                                        },
                                    },
                                    new_text: format!(": {}", template_name),
                                }],
                            );

                            let action = CodeAction {
                                title: "Convert template to species".to_string(),
                                kind: Some(CodeActionKind::REFACTOR),
                                diagnostics: None,
                                edit: Some(WorkspaceEdit {
                                    changes: Some(changes),
                                    document_changes: None,
                                    change_annotations: None,
                                }),
                                command: None,
                                is_preferred: Some(false),
                                disabled: None,
                                data: None,
                            };

                            return Some(CodeActionOrCommand::CodeAction(action));
                        }
                    }
                }
            }
        }
    }

    None
}

//
// Helper functions
//

/// Extract a name from within quotes in a message
/// Example: "duplicate field 'age'" -> Some("age")
pub(crate) fn extract_quoted_name(message: &str) -> Option<String> {
    // Look for text between single quotes
    let start = message.find('\'')?;
    let end = message[start + 1..].find('\'')?;
    Some(message[start + 1..start + 1 + end].to_string())
}

/// Determine what kind of symbol to create based on context
fn determine_symbol_kind_from_context(doc: &Document, diagnostic: &Diagnostic) -> Vec<DeclKind> {
    // Look at the line where the diagnostic is to determine context
    let text = &doc.text;
    let range = diagnostic.range;

    // Get the line text
    let line_start = doc.positions.line_offset(range.start.line as usize);
    let line_end = doc
        .positions
        .line_offset(range.start.line as usize + 1)
        .unwrap_or(text.len());

    if let Some(start) = line_start {
        let line = &text[start..line_end.min(text.len())];

        // Check for context clues
        if line.contains("from") {
            // Template reference
            vec![DeclKind::Template, DeclKind::Character]
        } else if line.contains(": ") && !line.contains("from") {
            // Likely a species reference
            vec![DeclKind::Species, DeclKind::Character]
        } else if line.contains("friend:") || line.contains("enemy:") {
            // Character reference in a field
            vec![DeclKind::Character, DeclKind::Template]
        } else {
            // Default: offer character and template
            vec![DeclKind::Character, DeclKind::Template]
        }
    } else {
        vec![DeclKind::Character, DeclKind::Template]
    }
}

/// Parse type mismatch message
/// Example: "type mismatch: expected Enum, got String" -> Some(("Enum",
/// "String"))
pub(crate) fn parse_type_mismatch(message: &str) -> Option<(String, String)> {
    let parts: Vec<&str> = message.split("expected ").collect();
    if parts.len() < 2 {
        return None;
    }

    let rest = parts[1];
    let parts: Vec<&str> = rest.split(", got ").collect();
    if parts.len() < 2 {
        return None;
    }

    Some((parts[0].to_string(), parts[1].to_string()))
}

/// Get text in a range
fn get_text_in_range(doc: &Document, range: Range) -> String {
    let start_offset = range_to_offset(doc, range.start).unwrap_or(0);
    let end_offset = range_to_offset(doc, range.end).unwrap_or(start_offset);

    doc.text[start_offset..end_offset].to_string()
}

/// Convert LSP range to byte offset
fn range_to_offset(doc: &Document, position: Position) -> Option<usize> {
    let line_start = doc.positions.line_offset(position.line as usize)?;
    Some(line_start + position.character as usize)
}

/// Check if a span contains a position
fn span_contains_position(span: Span, doc: &Document, position: Position) -> bool {
    let offset = range_to_offset(doc, position);
    if let Some(offset) = offset {
        offset >= span.start && offset <= span.end
    } else {
        false
    }
}

/// Get default value for a field type
fn get_default_value_for_type(field_type: &crate::syntax::ast::Value) -> String {
    use crate::syntax::ast::Value;

    match field_type {
        | Value::Identifier(path) => {
            // Check if it's a primitive type or a reference
            if let Some(type_name) = path.last() {
                match type_name.as_str() {
                    | "String" => String::from("\"\""),
                    | "Int" | "Float" => String::from("0"),
                    | "Bool" => String::from("false"),
                    | _ => {
                        // It's a reference type (Character, Enum, etc.)
                        // Can't provide a good default
                        type_name.clone()
                    },
                }
            } else {
                String::from("Unknown")
            }
        },
        | Value::List(_) => String::from("[]"),
        | Value::Text(_) => String::from("\"\""),
        | Value::Number(_) => String::from("0"),
        | Value::Decimal(_) => String::from("0.0"),
        | Value::Boolean(_) => String::from("false"),
        | Value::Object(_) => String::from("{}"),
        | Value::Range(_, _) => String::from("0..10"),
        | Value::Time(_) => String::from("00:00"),
        | Value::Duration(_) => String::from("0h"),
        | Value::ProseBlock(_) => String::from("@tag \"\""),
        | Value::Override(_) => String::from("Base {}"),
        | Value::Any => String::from("any"),
    }
}

/// Format a Value to a string representation
fn format_value(value: &crate::syntax::ast::Value) -> String {
    use crate::syntax::ast::Value;

    match value {
        | Value::Text(s) => format!("\"{}\"", s),
        | Value::Number(n) => n.to_string(),
        | Value::Decimal(f) => f.to_string(),
        | Value::Boolean(b) => b.to_string(),
        | Value::Identifier(path) => path.join("."),
        | Value::List(items) => {
            let formatted: Vec<String> = items.iter().map(format_value).collect();
            format!("[{}]", formatted.join(", "))
        },
        | Value::Object(fields) => {
            let formatted: Vec<String> = fields
                .iter()
                .map(|f| format!("{}: {}", f.name, format_value(&f.value)))
                .collect();
            format!("{{ {} }}", formatted.join(", "))
        },
        | Value::Range(start, end) => {
            format!("{}..{}", format_value(start), format_value(end))
        },
        | Value::Time(t) => format!("{}:{:02}", t.hour, t.minute),
        | Value::Duration(d) => format!("{}h", d.hours),
        | Value::ProseBlock(p) => format!("@{} \"{}\"", p.tag, p.content),
        | Value::Override(o) => {
            // Format override as "BaseTemplate { overrides }"
            format!("{} {{ ... }}", o.base.join("."))
        },
        | Value::Any => String::from("any"),
    }
}

/// Infer type from a value (for extract-to-template)
fn infer_type_from_value(value: &crate::syntax::ast::Value) -> String {
    use crate::syntax::ast::Value;

    match value {
        | Value::Text(_) => String::from("Text"),
        | Value::Number(_) => String::from("Number"),
        | Value::Decimal(_) => String::from("Decimal"),
        | Value::Boolean(_) => String::from("Boolean"),
        | Value::Identifier(path) => path.join("."), // Reference type
        | Value::List(items) => {
            if items.is_empty() {
                String::from("List")
            } else {
                format!("[{}]", infer_type_from_value(&items[0]))
            }
        },
        | Value::Object(_) => String::from("Object"),
        | Value::Range(_, _) => String::from("Range"),
        | Value::Time(_) => String::from("Time"),
        | Value::Duration(_) => String::from("Duration"),
        | Value::ProseBlock(_) => String::from("ProseBlock"),
        | Value::Override(_) => String::from("Override"),
        | Value::Any => String::from("Any"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extract_quoted_name() {
        assert_eq!(
            extract_quoted_name("duplicate field 'age'"),
            Some("age".to_string())
        );
        assert_eq!(
            extract_quoted_name("name 'Person' not found"),
            Some("Person".to_string())
        );
        assert_eq!(extract_quoted_name("no quotes here"), None);
    }

    #[test]
    fn test_parse_type_mismatch() {
        assert_eq!(
            parse_type_mismatch("type mismatch: expected Enum, got String"),
            Some(("Enum".to_string(), "String".to_string()))
        );
        assert_eq!(
            parse_type_mismatch("type mismatch: expected Number, got String"),
            Some(("Number".to_string(), "String".to_string()))
        );
        assert_eq!(parse_type_mismatch("not a type mismatch"), None);
    }

    #[test]
    fn test_ranges_overlap() {
        let r1 = Range {
            start: Position {
                line: 0,
                character: 0,
            },
            end: Position {
                line: 0,
                character: 10,
            },
        };
        let r2 = Range {
            start: Position {
                line: 0,
                character: 5,
            },
            end: Position {
                line: 0,
                character: 15,
            },
        };
        assert!(ranges_overlap(r1, r2));

        let r3 = Range {
            start: Position {
                line: 1,
                character: 0,
            },
            end: Position {
                line: 1,
                character: 10,
            },
        };
        assert!(!ranges_overlap(r1, r3));
    }
}
