#[derive(Serialize)]
struct FallbackErrorOutput {
    error: FallbackError,
}

#[derive(Serialize)]
struct FallbackError {
    code: String,
    message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    suggestion: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    actions: Vec<String>,
}

fn handle_error(e: Box<dyn std::error::Error>, robot_mode: bool) -> ! {
    if let Some(gi_error) = e.downcast_ref::<LoreError>() {
        if robot_mode {
            let output = RobotErrorOutput::from(gi_error);
            eprintln!(
                "{}",
                serde_json::to_string(&output).unwrap_or_else(|_| {
                    let fallback = FallbackErrorOutput {
                        error: FallbackError {
                            code: "INTERNAL_ERROR".to_string(),
                            message: gi_error.to_string(),
                            suggestion: None,
                            actions: Vec::new(),
                        },
                    };
                    serde_json::to_string(&fallback)
                        .unwrap_or_else(|_| r#"{"error":{"code":"INTERNAL_ERROR","message":"Serialization failed"}}"#.to_string())
                })
            );
            std::process::exit(gi_error.exit_code());
        } else {
            eprintln!();
            eprintln!(
                "  {}  {}",
                Theme::error().render(Icons::error()),
                Theme::error().bold().render(&gi_error.to_string())
            );
            if let Some(suggestion) = gi_error.suggestion() {
                eprintln!();
                eprintln!("     {suggestion}");
            }
            let actions = gi_error.actions();
            if !actions.is_empty() {
                eprintln!();
                for action in &actions {
                    eprintln!(
                        "     {} {}",
                        Theme::dim().render("\u{2192}"),
                        Theme::bold().render(action)
                    );
                }
            }
            eprintln!();
            std::process::exit(gi_error.exit_code());
        }
    }

    if robot_mode {
        let output = FallbackErrorOutput {
            error: FallbackError {
                code: "INTERNAL_ERROR".to_string(),
                message: e.to_string(),
                suggestion: None,
                actions: Vec::new(),
            },
        };
        eprintln!(
            "{}",
            serde_json::to_string(&output).unwrap_or_else(|_| {
                r#"{"error":{"code":"INTERNAL_ERROR","message":"Serialization failed"}}"#
                    .to_string()
            })
        );
    } else {
        eprintln!();
        eprintln!(
            "  {}  {}",
            Theme::error().render(Icons::error()),
            Theme::error().bold().render(&e.to_string())
        );
        eprintln!();
    }
    std::process::exit(1);
}

/// Emit stderr warnings for any corrections applied during Phase 1.5.
fn emit_correction_warnings(result: &CorrectionResult, robot_mode: bool) {
    if robot_mode {
        #[derive(Serialize)]
        struct CorrectionWarning<'a> {
            warning: CorrectionWarningInner<'a>,
        }
        #[derive(Serialize)]
        struct CorrectionWarningInner<'a> {
            r#type: &'static str,
            corrections: &'a [autocorrect::Correction],
            teaching: Vec<String>,
        }

        let teaching: Vec<String> = result
            .corrections
            .iter()
            .map(autocorrect::format_teaching_note)
            .collect();

        let warning = CorrectionWarning {
            warning: CorrectionWarningInner {
                r#type: "ARG_CORRECTED",
                corrections: &result.corrections,
                teaching,
            },
        };
        if let Ok(json) = serde_json::to_string(&warning) {
            eprintln!("{json}");
        }
    } else {
        for c in &result.corrections {
            eprintln!(
                "{} {}",
                Theme::warning().render("Auto-corrected:"),
                autocorrect::format_teaching_note(c)
            );
        }
    }
}

/// Phase 1 & 4: Handle clap parsing errors with structured JSON output in robot mode.
/// Also includes fuzzy command matching and flag-level suggestions.
fn handle_clap_error(e: clap::Error, robot_mode: bool, corrections: &CorrectionResult) -> ! {
    use clap::error::ErrorKind;

    // Always let clap handle --help and --version normally (print and exit 0).
    // These are intentional user actions, not errors, even when stdout is redirected.
    if matches!(e.kind(), ErrorKind::DisplayHelp | ErrorKind::DisplayVersion) {
        e.exit()
    }

    if robot_mode {
        let error_code = map_clap_error_kind(e.kind());
        let full_msg = e.to_string();
        let message = full_msg
            .lines()
            .take(3)
            .collect::<Vec<_>>()
            .join("; ")
            .trim()
            .to_string();

        let (suggestion, correction, valid_values) = match e.kind() {
            // Phase 4: Suggest similar command for unknown subcommands
            ErrorKind::InvalidSubcommand => {
                let suggestion = if let Some(invalid_cmd) = extract_invalid_subcommand(&e) {
                    suggest_similar_command(&invalid_cmd)
                } else {
                    "Run 'lore robot-docs' for valid commands".to_string()
                };
                (suggestion, None, None)
            }
            // Flag-level fuzzy matching for unknown flags
            ErrorKind::UnknownArgument => {
                let invalid_flag = extract_invalid_flag(&e);
                let similar = invalid_flag
                    .as_deref()
                    .and_then(|flag| autocorrect::suggest_similar_flag(flag, &corrections.args));
                let suggestion = if let Some(ref s) = similar {
                    format!("Did you mean '{s}'? Run 'lore robot-docs' for all flags")
                } else {
                    "Run 'lore robot-docs' for valid flags".to_string()
                };
                (suggestion, similar, None)
            }
            // Value-level suggestions for invalid enum values
            ErrorKind::InvalidValue => {
                let (flag, valid_vals) = extract_invalid_value_context(&e);
                let suggestion = if let Some(vals) = &valid_vals {
                    format!(
                        "Valid values: {}. Run 'lore robot-docs' for details",
                        vals.join(", ")
                    )
                } else if let Some(ref f) = flag {
                    if let Some(vals) = autocorrect::valid_values_for_flag(f) {
                        format!("Valid values for {f}: {}", vals.join(", "))
                    } else {
                        "Run 'lore robot-docs' for valid values".to_string()
                    }
                } else {
                    "Run 'lore robot-docs' for valid values".to_string()
                };
                let vals_vec = valid_vals.or_else(|| {
                    flag.as_deref()
                        .and_then(autocorrect::valid_values_for_flag)
                        .map(|v| v.iter().map(|s| (*s).to_string()).collect())
                });
                (suggestion, None, vals_vec)
            }
            ErrorKind::MissingRequiredArgument => {
                let suggestion = format!(
                    "A required argument is missing. {}",
                    if let Some(subcmd) = extract_subcommand_from_context(&e) {
                        format!(
                            "Example: {}. Run 'lore {subcmd} --help' for required arguments",
                            command_example(&subcmd)
                        )
                    } else {
                        "Run 'lore robot-docs' for command reference".to_string()
                    }
                );
                (suggestion, None, None)
            }
            ErrorKind::MissingSubcommand => {
                let suggestion =
                    "No command specified. Common commands: issues, mrs, search, sync, \
                     timeline, who, me. Run 'lore robot-docs' for the full list"
                        .to_string();
                (suggestion, None, None)
            }
            ErrorKind::TooFewValues | ErrorKind::TooManyValues => {
                let suggestion = if let Some(subcmd) = extract_subcommand_from_context(&e) {
                    format!(
                        "Example: {}. Run 'lore {subcmd} --help' for usage",
                        command_example(&subcmd)
                    )
                } else {
                    "Run 'lore robot-docs' for command reference".to_string()
                };
                (suggestion, None, None)
            }
            _ => (
                "Run 'lore robot-docs' for valid commands".to_string(),
                None,
                None,
            ),
        };

        let output = RobotErrorWithSuggestion {
            error: RobotErrorSuggestionData {
                code: error_code.to_string(),
                message,
                suggestion,
                correction,
                valid_values,
            },
        };
        eprintln!(
            "{}",
            serde_json::to_string(&output).unwrap_or_else(|_| {
                r#"{"error":{"code":"PARSE_ERROR","message":"Parse error"}}"#.to_string()
            })
        );
        std::process::exit(2);
    } else {
        e.exit()
    }
}

/// Map clap ErrorKind to semantic error codes
fn map_clap_error_kind(kind: clap::error::ErrorKind) -> &'static str {
    use clap::error::ErrorKind;
    match kind {
        ErrorKind::InvalidSubcommand => "UNKNOWN_COMMAND",
        ErrorKind::UnknownArgument => "UNKNOWN_FLAG",
        ErrorKind::MissingRequiredArgument => "MISSING_REQUIRED",
        ErrorKind::InvalidValue => "INVALID_VALUE",
        ErrorKind::ValueValidation => "INVALID_VALUE",
        ErrorKind::TooManyValues => "TOO_MANY_VALUES",
        ErrorKind::TooFewValues => "TOO_FEW_VALUES",
        ErrorKind::ArgumentConflict => "ARGUMENT_CONFLICT",
        ErrorKind::MissingSubcommand => "MISSING_COMMAND",
        ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => "HELP_REQUESTED",
        _ => "PARSE_ERROR",
    }
}

/// Extract the invalid subcommand from a clap error (Phase 4)
fn extract_invalid_subcommand(e: &clap::Error) -> Option<String> {
    // Parse the error message to find the invalid subcommand
    // Format is typically: "error: unrecognized subcommand 'foo'"
    let msg = e.to_string();
    if let Some(start) = msg.find('\'')
        && let Some(end) = msg[start + 1..].find('\'')
    {
        return Some(msg[start + 1..start + 1 + end].to_string());
    }
    None
}

/// Extract the invalid flag from a clap UnknownArgument error.
/// Format is typically: "error: unexpected argument '--xyzzy' found"
fn extract_invalid_flag(e: &clap::Error) -> Option<String> {
    let msg = e.to_string();
    if let Some(start) = msg.find('\'')
        && let Some(end) = msg[start + 1..].find('\'')
    {
        let value = &msg[start + 1..start + 1 + end];
        if value.starts_with('-') {
            return Some(value.to_string());
        }
    }
    None
}

/// Extract flag name and valid values from a clap InvalidValue error.
/// Returns (flag_name, valid_values_if_listed_in_error).
fn extract_invalid_value_context(e: &clap::Error) -> (Option<String>, Option<Vec<String>>) {
    let msg = e.to_string();

    // Try to find the flag name from "[possible values: ...]" pattern or from the arg info
    // Clap format: "error: invalid value 'opend' for '--state <STATE>'"
    let flag = if let Some(for_pos) = msg.find("for '") {
        let after_for = &msg[for_pos + 5..];
        if let Some(end) = after_for.find('\'') {
            let raw = &after_for[..end];
            // Strip angle-bracket value placeholder: "--state <STATE>" -> "--state"
            Some(raw.split_whitespace().next().unwrap_or(raw).to_string())
        } else {
            None
        }
    } else {
        None
    };

    // Try to extract possible values from the error message
    // Clap format: "[possible values: opened, closed, merged, locked, all]"
    let valid_values = if let Some(pv_pos) = msg.find("[possible values: ") {
        let after_pv = &msg[pv_pos + 18..];
        after_pv.find(']').map(|end| {
            after_pv[..end]
                .split(", ")
                .map(|s| s.trim().to_string())
                .collect()
        })
    } else {
        // Fall back to our static registry
        flag.as_deref()
            .and_then(autocorrect::valid_values_for_flag)
            .map(|v| v.iter().map(|s| (*s).to_string()).collect())
    };

    (flag, valid_values)
}

/// Extract the subcommand context from a clap error for better suggestions.
/// Looks at the error message to find which command was being invoked.
fn extract_subcommand_from_context(e: &clap::Error) -> Option<String> {
    let msg = e.to_string();

    let known = [
        "issues",
        "mrs",
        "notes",
        "search",
        "sync",
        "ingest",
        "count",
        "status",
        "auth",
        "doctor",
        "stats",
        "timeline",
        "who",
        "me",
        "drift",
        "related",
        "trace",
        "file-history",
        "generate-docs",
        "embed",
        "token",
        "cron",
        "init",
        "migrate",
    ];
    for cmd in known {
        if msg.contains(&format!("lore {cmd}")) || msg.contains(&format!("'{cmd}'")) {
            return Some(cmd.to_string());
        }
    }
    None
}

/// Phase 4: Suggest similar command using fuzzy matching
fn suggest_similar_command(invalid: &str) -> String {
    // Primary commands + common aliases for fuzzy matching
    const VALID_COMMANDS: &[(&str, &str)] = &[
        ("issues", "issues"),
        ("issue", "issues"),
        ("mrs", "mrs"),
        ("mr", "mrs"),
        ("merge-requests", "mrs"),
        ("search", "search"),
        ("find", "search"),
        ("query", "search"),
        ("sync", "sync"),
        ("ingest", "ingest"),
        ("count", "count"),
        ("status", "status"),
        ("auth", "auth"),
        ("doctor", "doctor"),
        ("version", "version"),
        ("init", "init"),
        ("stats", "stats"),
        ("stat", "stats"),
        ("generate-docs", "generate-docs"),
        ("embed", "embed"),
        ("migrate", "migrate"),
        ("health", "health"),
        ("robot-docs", "robot-docs"),
        ("completions", "completions"),
        ("timeline", "timeline"),
        ("who", "who"),
        ("notes", "notes"),
        ("note", "notes"),
        ("drift", "drift"),
        ("file-history", "file-history"),
        ("trace", "trace"),
        ("related", "related"),
        ("me", "me"),
        ("token", "token"),
        ("cron", "cron"),
        // Hidden but may be known to agents
        ("list", "list"),
        ("show", "show"),
        ("reset", "reset"),
        ("backup", "backup"),
    ];

    let invalid_lower = invalid.to_lowercase();

    // Find the best match using Jaro-Winkler similarity
    let best_match = VALID_COMMANDS
        .iter()
        .map(|(alias, canonical)| (*canonical, jaro_winkler(&invalid_lower, alias)))
        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));

    if let Some((cmd, score)) = best_match
        && score > 0.7
    {
        let example = command_example(cmd);
        return format!(
            "Did you mean 'lore {cmd}'? Example: {example}. Run 'lore robot-docs' for all commands"
        );
    }

    "Run 'lore robot-docs' for valid commands. Common: issues, mrs, search, sync, timeline, who"
        .to_string()
}

/// Return a contextual usage example for a command.
fn command_example(cmd: &str) -> &'static str {
    match cmd {
        "issues" => "lore --robot issues -n 10",
        "mrs" => "lore --robot mrs -n 10",
        "search" => "lore --robot search \"auth bug\"",
        "sync" => "lore --robot sync",
        "ingest" => "lore --robot ingest issues",
        "notes" => "lore --robot notes --for-issue 123",
        "count" => "lore --robot count issues",
        "status" => "lore --robot status",
        "stats" => "lore --robot stats",
        "timeline" => "lore --robot timeline \"auth flow\"",
        "who" => "lore --robot who --path src/",
        "health" => "lore --robot health",
        "generate-docs" => "lore --robot generate-docs",
        "embed" => "lore --robot embed",
        "robot-docs" => "lore robot-docs",
        "trace" => "lore --robot trace src/main.rs",
        "init" => "lore init",
        "related" => "lore --robot related issues 42 -n 5",
        "me" => "lore --robot me",
        "drift" => "lore --robot drift issues 42",
        "file-history" => "lore --robot file-history src/main.rs",
        "token" => "lore --robot token show",
        "cron" => "lore --robot cron status",
        "auth" => "lore --robot auth",
        "doctor" => "lore --robot doctor",
        "migrate" => "lore --robot migrate",
        "completions" => "lore completions bash",
        _ => "lore --robot <command>",
    }
}

