use serde_json::Value;

use crate::localization::{
    BackendLocale, backend_strings, format_more_actions, localized_tool_display_name,
    resolve_backend_locale,
};
use crate::tool_types::{ToolCall, ToolDefinition};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolNarrationPhase {
    Started,
    Waiting,
    Completed,
    Failed,
}

fn title_case(name: &str) -> String {
    name.split(['_', ' '])
        .filter(|part| !part.is_empty())
        .map(|part| {
            let mut chars = part.chars();
            match chars.next() {
                Some(first) => {
                    let mut value = String::new();
                    value.extend(first.to_uppercase());
                    value.push_str(chars.as_str());
                    value
                }
                None => String::new(),
            }
        })
        .collect::<Vec<_>>()
        .join(" ")
}

fn display_name(
    tool_def: Option<&ToolDefinition>,
    tool_call: &ToolCall,
    locale: Option<&str>,
) -> String {
    localized_tool_display_name(
        &tool_call.name,
        tool_def.and_then(|def| def.display_name()),
        locale,
    )
    .unwrap_or_else(|| title_case(&tool_call.name))
}

fn arg_str<'a>(arguments: &'a Value, keys: &[&str]) -> Option<&'a str> {
    keys.iter()
        .find_map(|key| arguments.get(*key))
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
}

fn arg_bool(arguments: &Value, key: &str) -> Option<bool> {
    arguments.get(key).and_then(Value::as_bool)
}

fn basename(path: &str) -> &str {
    let trimmed = path.trim_end_matches('/');
    trimmed
        .rsplit('/')
        .next()
        .filter(|part| !part.is_empty())
        .unwrap_or(path)
}

fn truncate(value: &str, max_len: usize) -> String {
    let clean = value.trim();
    if clean.chars().count() <= max_len {
        return clean.to_string();
    }

    let truncated: String = clean.chars().take(max_len).collect();
    format!("{truncated}...")
}

fn location_phrase(arguments: &Value, locale: Option<&str>) -> String {
    arg_str(arguments, &["path", "directory", "working_dir"])
        .map(|value| {
            if value == "." || value == "/workspace" {
                backend_strings(locale).current_directory.to_string()
            } else {
                value.to_string()
            }
        })
        .unwrap_or_else(|| backend_strings(locale).current_directory.to_string())
}

/// Map a CRUD operation to (started, completed, failed) verb forms.
fn operation_verbs(operation: &str) -> (&str, &str, &str) {
    match operation {
        "create" => ("Creating", "Created", "Failed to create"),
        "update" => ("Updating", "Updated", "Failed to update"),
        "delete" | "destroy" => ("Deleting", "Deleted", "Failed to delete"),
        "copy" | "clone" | "duplicate" => ("Copying", "Copied", "Failed to copy"),
        "list" => ("Listing", "Listed", "Failed to list"),
        "get" | "read" => ("Reading", "Read", "Failed to read"),
        "set" => ("Setting", "Set", "Failed to set"),
        "send" => ("Sending", "Sent", "Failed to send"),
        "run" | "execute" => ("Running", "Ran", "Failed to run"),
        _ => ("Running", "Ran", "Failed to run"),
    }
}

/// Build narration from `narration_noun` + `operation` arg.
/// E.g. noun="agent", operation="create", name="Neon Cartographer"
/// → "Creating agent: Neon Cartographer" / "Created agent: Neon Cartographer"
fn operation_narration(noun: &str, arguments: &Value, phase: ToolNarrationPhase) -> Option<String> {
    let operation = arg_str(arguments, &["operation", "action"])?;
    let (started, completed, failed) = operation_verbs(operation);
    let name =
        arg_str(arguments, &["display_name", "name", "title", "new_name"]).map(|v| truncate(v, 40));
    let target = match name {
        Some(name) => format!("{noun}: {name}"),
        None => noun.to_string(),
    };
    Some(generic_phrase(
        started,
        completed,
        failed,
        Some(target),
        phase,
    ))
}

fn generic_phrase(
    verb_started: &str,
    verb_completed: &str,
    verb_failed: &str,
    target: Option<String>,
    phase: ToolNarrationPhase,
) -> String {
    let verb = match phase {
        ToolNarrationPhase::Started => verb_started,
        ToolNarrationPhase::Waiting => verb_started,
        ToolNarrationPhase::Completed => verb_completed,
        ToolNarrationPhase::Failed => verb_failed,
    };

    match target {
        Some(target) if !target.is_empty() => format!("{verb} {target}"),
        _ => verb.to_string(),
    }
}

fn is_shell_exec_tool(name: &str) -> bool {
    matches!(
        name,
        "bash"
            | "daytona_exec"
            | "sandbox_exec"
            | "e2b_exec"
            | "deno_exec"
            | "docker_exec"
            | "sprites_exec"
    )
}

fn ukrainian_phrase(
    _tool_def: Option<&ToolDefinition>,
    tool_call: &ToolCall,
    fallback_name: &str,
    phase: ToolNarrationPhase,
) -> String {
    let args = &tool_call.arguments;
    match tool_call.name.as_str() {
        name if is_shell_exec_tool(name) => {
            let command = arg_str(args, &["commands", "command"])
                .map(|value| format!("`{}`", truncate(value, 48)))
                .unwrap_or_else(|| fallback_name.to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                    format!("Запускаю {command}")
                }
                ToolNarrationPhase::Completed => format!("Запустив {command}"),
                ToolNarrationPhase::Failed => format!("Не вдалося запустити {command}"),
            }
        }
        "read_file" | "session_read_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Читаю {target}"),
                    None => "Читаю файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Прочитав {target}"),
                    None => "Прочитав файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося прочитати {target}"),
                    None => "Не вдалося прочитати файл".to_string(),
                },
            }
        }
        "read_many_files" => match phase {
            ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                "Читаю кілька файлів".to_string()
            }
            ToolNarrationPhase::Completed => "Прочитав кілька файлів".to_string(),
            ToolNarrationPhase::Failed => "Не вдалося прочитати кілька файлів".to_string(),
        },
        "list_directory" | "list_files" => {
            let target = location_phrase(args, Some("uk"));
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                    format!("Переглядаю файли у {target}")
                }
                ToolNarrationPhase::Completed => format!("Переглянув файли у {target}"),
                ToolNarrationPhase::Failed => format!("Не вдалося переглянути файли у {target}"),
            }
        }
        "grep_files" => {
            let pattern = arg_str(args, &["pattern"]).map(|pattern| truncate(pattern, 36));
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match pattern {
                    Some(pattern) => format!("Шукаю `{pattern}` у файлах"),
                    None => "Шукаю у файлах".to_string(),
                },
                ToolNarrationPhase::Completed => match pattern {
                    Some(pattern) => format!("Знайшов `{pattern}` у файлах"),
                    None => "Завершив пошук у файлах".to_string(),
                },
                ToolNarrationPhase::Failed => match pattern {
                    Some(pattern) => format!("Не вдалося знайти `{pattern}` у файлах"),
                    None => "Не вдалося виконати пошук у файлах".to_string(),
                },
            }
        }
        "search" | "search_web" => {
            let query = arg_str(args, &["query", "q", "search"]).map(|query| truncate(query, 48));
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match query {
                    Some(query) => format!("Шукаю у вебі: {query}"),
                    None => "Шукаю у вебі".to_string(),
                },
                ToolNarrationPhase::Completed => match query {
                    Some(query) => format!("Завершив пошук у вебі: {query}"),
                    None => "Завершив пошук у вебі".to_string(),
                },
                ToolNarrationPhase::Failed => match query {
                    Some(query) => format!("Не вдалося знайти у вебі: {query}"),
                    None => "Не вдалося виконати пошук у вебі".to_string(),
                },
            }
        }
        name if name.ends_with("__search") => {
            let query = arg_str(args, &["query", "q", "search"]).map(|query| truncate(query, 48));
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match query {
                    Some(query) => format!("Шукаю: {query}"),
                    None => "Шукаю".to_string(),
                },
                ToolNarrationPhase::Completed => match query {
                    Some(query) => format!("Завершив пошук: {query}"),
                    None => "Завершив пошук".to_string(),
                },
                ToolNarrationPhase::Failed => match query {
                    Some(query) => format!("Не вдалося знайти: {query}"),
                    None => "Не вдалося виконати пошук".to_string(),
                },
            }
        }
        "write_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Записую {target}"),
                    None => "Записую файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Записав {target}"),
                    None => "Записав файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося записати {target}"),
                    None => "Не вдалося записати файл".to_string(),
                },
            }
        }
        "edit_file" | "replace_in_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Редагую {target}"),
                    None => "Редагую файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Відредагував {target}"),
                    None => "Відредагував файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося відредагувати {target}"),
                    None => "Не вдалося відредагувати файл".to_string(),
                },
            }
        }
        "append_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Дописую у {target}"),
                    None => "Дописую у файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Дописав у {target}"),
                    None => "Дописав у файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося дописати у {target}"),
                    None => "Не вдалося дописати у файл".to_string(),
                },
            }
        }
        "move_file" => {
            let target = arg_str(args, &["to", "destination", "new_path"])
                .or_else(|| arg_str(args, &["path"]))
                .map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Переміщую {target}"),
                    None => "Переміщую файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Перемістив {target}"),
                    None => "Перемістив файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося перемістити {target}"),
                    None => "Не вдалося перемістити файл".to_string(),
                },
            }
        }
        "delete_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Видаляю {target}"),
                    None => "Видаляю файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Видалив {target}"),
                    None => "Видалив файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося видалити {target}"),
                    None => "Не вдалося видалити файл".to_string(),
                },
            }
        }
        "mkdir" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Створюю директорію {target}"),
                    None => "Створюю директорію".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Створив директорію {target}"),
                    None => "Створив директорію".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося створити директорію {target}"),
                    None => "Не вдалося створити директорію".to_string(),
                },
            }
        }
        "stat_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => match target {
                    Some(target) => format!("Перевіряю {target}"),
                    None => "Перевіряю файл".to_string(),
                },
                ToolNarrationPhase::Completed => match target {
                    Some(target) => format!("Перевірив {target}"),
                    None => "Перевірив файл".to_string(),
                },
                ToolNarrationPhase::Failed => match target {
                    Some(target) => format!("Не вдалося перевірити {target}"),
                    None => "Не вдалося перевірити файл".to_string(),
                },
            }
        }
        "secret_store" | "kv_store" => {
            let operation = arg_str(args, &["operation"]).unwrap_or("використати");
            let target = arg_str(args, &["name", "key"])
                .map(str::to_string)
                .unwrap_or_else(|| fallback_name.to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                    format!("Виконую {} {}", title_case(operation), target)
                        .trim()
                        .to_string()
                }
                ToolNarrationPhase::Completed => {
                    format!("Виконав {} {}", title_case(operation), target)
                        .trim()
                        .to_string()
                }
                ToolNarrationPhase::Failed => {
                    format!("Не вдалося виконати {} {}", operation, target)
                        .trim()
                        .to_string()
                }
            }
        }
        "spawn_subagent" => {
            let target = arg_str(args, &["name"])
                .map(|name| format!("субагента {name}"))
                .unwrap_or_else(|| "субагента".to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                    format!("Запускаю {target}")
                }
                ToolNarrationPhase::Completed => format!("Запустив {target}"),
                ToolNarrationPhase::Failed => format!("Не вдалося запустити {target}"),
            }
        }
        "get_subagents" => {
            let target = arg_str(args, &["name_or_id"])
                .map(|value| format!("субагента {value}"))
                .unwrap_or_else(|| "субагентів".to_string());
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                    format!("Перевіряю {target}")
                }
                ToolNarrationPhase::Completed => format!("Перевірив {target}"),
                ToolNarrationPhase::Failed => format!("Не вдалося перевірити {target}"),
            }
        }
        "message_subagent" => {
            let target = arg_str(args, &["name_or_id"])
                .map(|value| format!("повідомлення для {value}"))
                .unwrap_or_else(|| "повідомлення субагенту".to_string());
            if matches!(phase, ToolNarrationPhase::Completed)
                && arg_bool(args, "cancel").unwrap_or(false)
            {
                format!(
                    "Поставив у чергу запит на зупинку {}",
                    target.trim_start_matches("повідомлення для ")
                )
            } else {
                match phase {
                    ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                        format!("Ставлю у чергу {target}")
                    }
                    ToolNarrationPhase::Completed => format!("Поставив у чергу {target}"),
                    ToolNarrationPhase::Failed => format!("Не вдалося поставити у чергу {target}"),
                }
            }
        }
        "write_todos" => match phase {
            ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                "Оновлюю список задач".to_string()
            }
            ToolNarrationPhase::Completed => "Оновив список задач".to_string(),
            ToolNarrationPhase::Failed => "Не вдалося оновити список задач".to_string(),
        },
        // Ukrainian operation_narration not yet localized — use generic
        // Ukrainian fallback to avoid mixing English verbs into the UI.
        _ => match phase {
            ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                format!("Запускаю {fallback_name}")
            }
            ToolNarrationPhase::Completed => format!("Запустив {fallback_name}"),
            ToolNarrationPhase::Failed => {
                format!("Не вдалося запустити {fallback_name}")
            }
        },
    }
}

pub fn render_tool_narration_with_locale(
    tool_def: Option<&ToolDefinition>,
    tool_call: &ToolCall,
    phase: ToolNarrationPhase,
    locale: Option<&str>,
) -> String {
    let args = &tool_call.arguments;
    let fallback_name = display_name(tool_def, tool_call, locale);

    if resolve_backend_locale(locale) == BackendLocale::Uk {
        return ukrainian_phrase(tool_def, tool_call, &fallback_name, phase);
    }

    match tool_call.name.as_str() {
        name if is_shell_exec_tool(name) => {
            let command = arg_str(args, &["commands", "command"])
                .map(|value| format!("`{}`", truncate(value, 48)))
                .unwrap_or_else(|| fallback_name.clone());
            generic_phrase("Running", "Ran", "Failed to run", Some(command), phase)
        }
        "read_file" | "session_read_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase("Reading", "Read", "Failed to read", target, phase)
        }
        "read_many_files" => generic_phrase(
            "Reading",
            "Read",
            "Failed to read",
            Some("multiple files".to_string()),
            phase,
        ),
        "list_directory" | "list_files" => generic_phrase(
            "Listing files in",
            "Listed files in",
            "Failed to list files in",
            Some(location_phrase(args, locale)),
            phase,
        ),
        "grep_files" => {
            let target = arg_str(args, &["pattern"])
                .map(|pattern| format!("files for {}", truncate(pattern, 36)))
                .unwrap_or_else(|| "files".to_string());
            generic_phrase(
                "Searching",
                "Searched",
                "Failed to search",
                Some(target),
                phase,
            )
        }
        "search" | "search_web" => {
            let target = arg_str(args, &["query", "q", "search"])
                .map(|query| format!("web for {}", truncate(query, 48)))
                .unwrap_or_else(|| "web".to_string());
            generic_phrase(
                "Searching",
                "Searched",
                "Failed to search",
                Some(target),
                phase,
            )
        }
        name if name.ends_with("__search") => {
            let target = arg_str(args, &["query", "q", "search"])
                .map(|query| truncate(query, 48))
                .unwrap_or_else(|| "query".to_string());
            generic_phrase(
                "Searching",
                "Searched",
                "Failed to search",
                Some(target),
                phase,
            )
        }
        "write_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase("Writing", "Wrote", "Failed to write", target, phase)
        }
        "edit_file" | "replace_in_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase("Editing", "Edited", "Failed to edit", target, phase)
        }
        "append_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase(
                "Appending to",
                "Appended to",
                "Failed to append to",
                target,
                phase,
            )
        }
        "move_file" => {
            let target = arg_str(args, &["to", "destination", "new_path"])
                .or_else(|| arg_str(args, &["path"]))
                .map(|path| basename(path).to_string());
            generic_phrase("Moving", "Moved", "Failed to move", target, phase)
        }
        "delete_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase("Deleting", "Deleted", "Failed to delete", target, phase)
        }
        "mkdir" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase(
                "Creating directory",
                "Created directory",
                "Failed to create directory",
                target,
                phase,
            )
        }
        "stat_file" => {
            let target = arg_str(args, &["path"]).map(|path| basename(path).to_string());
            generic_phrase("Checking", "Checked", "Failed to check", target, phase)
        }
        "secret_store" | "kv_store" => {
            let operation = arg_str(args, &["operation"]).unwrap_or("use");
            let target = arg_str(args, &["name", "key"])
                .map(str::to_string)
                .or_else(|| Some(fallback_name.clone()));
            let started = format!("{}ing", title_case(operation));
            let completed = if operation.eq_ignore_ascii_case("list") {
                format!(
                    "Listed {target}",
                    target = target.clone().unwrap_or_default()
                )
            } else {
                format!(
                    "{} {}",
                    title_case(operation),
                    target.clone().unwrap_or_default()
                )
            };
            match phase {
                ToolNarrationPhase::Started | ToolNarrationPhase::Waiting => {
                    format!("{} {}", started, target.unwrap_or_default())
                        .trim()
                        .to_string()
                }
                ToolNarrationPhase::Completed => completed.trim().to_string(),
                ToolNarrationPhase::Failed => format!(
                    "Failed to {} {}",
                    operation.to_lowercase(),
                    target.unwrap_or_default()
                )
                .trim()
                .to_string(),
            }
        }
        "spawn_subagent" => {
            let target = arg_str(args, &["name"])
                .map(|name| format!("{name} subagent"))
                .or_else(|| Some("subagent".to_string()));
            generic_phrase("Launching", "Launched", "Failed to launch", target, phase)
        }
        "get_subagents" => {
            let target = arg_str(args, &["name_or_id"])
                .map(|value| format!("subagent {value}"))
                .unwrap_or_else(|| "subagent status".to_string());
            generic_phrase(
                "Checking",
                "Checked",
                "Failed to check",
                Some(target),
                phase,
            )
        }
        "message_subagent" => {
            let target = arg_str(args, &["name_or_id"])
                .map(|value| format!("message for {value}"))
                .unwrap_or_else(|| "subagent message".to_string());
            if matches!(phase, ToolNarrationPhase::Completed)
                && arg_bool(args, "cancel").unwrap_or(false)
            {
                format!(
                    "Queued stop request for {}",
                    target.trim_start_matches("message for ")
                )
            } else {
                generic_phrase("Queueing", "Queued", "Failed to queue", Some(target), phase)
            }
        }
        "write_todos" => generic_phrase(
            "Updating",
            "Updated",
            "Failed to update",
            Some("task list".to_string()),
            phase,
        ),
        "browserless_open_browser" => {
            let target = arg_str(args, &["url"]).map(|url| truncate(url, 48));
            generic_phrase(
                "Opening browser",
                "Opened browser",
                "Failed to open browser",
                target,
                phase,
            )
        }
        "browserless_close_browser" => generic_phrase(
            "Closing browser",
            "Closed browser",
            "Failed to close browser",
            None,
            phase,
        ),
        "browserless_navigate" => {
            let target = arg_str(args, &["url"]).map(|url| truncate(url, 48));
            generic_phrase(
                "Navigating to",
                "Navigated to",
                "Failed to navigate to",
                target,
                phase,
            )
        }
        "browserless_screenshot" => generic_phrase(
            "Taking screenshot",
            "Took screenshot",
            "Failed to take screenshot",
            None,
            phase,
        ),
        "browserless_content" => generic_phrase(
            "Reading page content",
            "Read page content",
            "Failed to read page content",
            None,
            phase,
        ),
        "browserless_scrape" => generic_phrase(
            "Scraping page",
            "Scraped page",
            "Failed to scrape page",
            None,
            phase,
        ),
        "browserless_interact" => generic_phrase(
            "Interacting with page",
            "Interacted with page",
            "Failed to interact with page",
            None,
            phase,
        ),
        _ => {
            if let Some(narration) = tool_def
                .and_then(|def| def.hints().narration_noun.as_deref())
                .and_then(|noun| operation_narration(noun, args, phase))
            {
                narration
            } else {
                generic_phrase(
                    "Running",
                    "Ran",
                    "Failed to run",
                    Some(fallback_name),
                    phase,
                )
            }
        }
    }
}

pub fn render_group_headline(
    tool_calls: &[ToolCall],
    tool_defs: &[ToolDefinition],
    phase: ToolNarrationPhase,
) -> Option<String> {
    render_group_headline_with_locale(tool_calls, tool_defs, phase, None)
}

pub fn render_tool_narration(
    tool_def: Option<&ToolDefinition>,
    tool_call: &ToolCall,
    phase: ToolNarrationPhase,
) -> String {
    render_tool_narration_with_locale(tool_def, tool_call, phase, None)
}

pub fn render_group_headline_with_locale(
    tool_calls: &[ToolCall],
    tool_defs: &[ToolDefinition],
    phase: ToolNarrationPhase,
    locale: Option<&str>,
) -> Option<String> {
    if tool_calls.is_empty() {
        return None;
    }

    let tool_map: std::collections::HashMap<&str, &ToolDefinition> =
        tool_defs.iter().map(|def| (def.name(), def)).collect();

    let phrases = tool_calls
        .iter()
        .map(|tool_call| {
            render_tool_narration_with_locale(
                tool_map.get(tool_call.name.as_str()).copied(),
                tool_call,
                phase,
                locale,
            )
        })
        .take(3)
        .collect::<Vec<_>>();

    Some(join_phrases(&phrases, tool_calls.len(), locale))
}

fn join_phrases(phrases: &[String], total_count: usize, locale: Option<&str>) -> String {
    let strings = backend_strings(locale);
    match phrases {
        [] => strings.working.to_string(),
        [only] => only.clone(),
        [first, second] => match resolve_backend_locale(locale) {
            BackendLocale::Uk => format!("{first} і {second}"),
            BackendLocale::En => format!("{first} and {second}"),
        },
        [first, second, ..] => {
            let more = format_more_actions(locale, total_count.saturating_sub(2));
            format!("{first}, {second}, {more}")
        }
    }
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::{
        ToolNarrationPhase, render_group_headline, render_group_headline_with_locale,
        render_tool_narration, render_tool_narration_with_locale,
    };
    use crate::tool_types::ToolCall;

    #[test]
    fn renders_read_file_narration() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "read_file".to_string(),
            arguments: json!({ "path": "/workspace/AGENTS.md" }),
        };

        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Started),
            "Reading AGENTS.md"
        );
        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Completed),
            "Read AGENTS.md"
        );
    }

    #[test]
    fn renders_edit_file_narration() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "edit_file".to_string(),
            arguments: json!({ "path": "/workspace/src/main.rs" }),
        };

        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Started),
            "Editing main.rs"
        );
        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Completed),
            "Edited main.rs"
        );
    }

    #[test]
    fn renders_daytona_exec_narration() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "daytona_exec".to_string(),
            arguments: json!({ "command": "cargo test -p everruns-core" }),
        };

        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Started),
            "Running `cargo test -p everruns-core`"
        );
        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Completed),
            "Ran `cargo test -p everruns-core`"
        );
    }

    #[test]
    fn renders_sandbox_exec_narration() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "sandbox_exec".to_string(),
            arguments: json!({ "command": "npm test" }),
        };

        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Started),
            "Running `npm test`"
        );
        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Completed),
            "Ran `npm test`"
        );
    }

    #[test]
    fn renders_bash_narration_with_commands_arg() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "bash".to_string(),
            arguments: json!({ "commands": "echo hi" }),
        };

        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Started),
            "Running `echo hi`"
        );
        assert_eq!(
            render_tool_narration(None, &tool_call, ToolNarrationPhase::Completed),
            "Ran `echo hi`"
        );
    }

    #[test]
    fn renders_group_headline_from_multiple_tool_calls() {
        let tool_calls = vec![
            ToolCall {
                id: "call_1".to_string(),
                name: "list_directory".to_string(),
                arguments: json!({ "path": "/workspace" }),
            },
            ToolCall {
                id: "call_2".to_string(),
                name: "read_file".to_string(),
                arguments: json!({ "path": "/workspace/AGENTS.md" }),
            },
            ToolCall {
                id: "call_3".to_string(),
                name: "grep_files".to_string(),
                arguments: json!({ "pattern": "Doppler" }),
            },
        ];

        let headline =
            render_group_headline(&tool_calls, &[], ToolNarrationPhase::Completed).unwrap();

        assert_eq!(
            headline,
            "Listed files in current directory, Read AGENTS.md, and 1 more actions"
        );
    }

    #[test]
    fn renders_ukrainian_read_file_narration() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "read_file".to_string(),
            arguments: json!({ "path": "/workspace/AGENTS.md" }),
        };

        assert_eq!(
            render_tool_narration_with_locale(
                None,
                &tool_call,
                ToolNarrationPhase::Started,
                Some("uk-UA"),
            ),
            "Читаю AGENTS.md"
        );
        assert_eq!(
            render_tool_narration_with_locale(
                None,
                &tool_call,
                ToolNarrationPhase::Completed,
                Some("uk-UA"),
            ),
            "Прочитав AGENTS.md"
        );
    }

    #[test]
    fn renders_ukrainian_sandbox_exec_narration() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "sandbox_exec".to_string(),
            arguments: json!({ "command": "npm test" }),
        };

        assert_eq!(
            render_tool_narration_with_locale(
                None,
                &tool_call,
                ToolNarrationPhase::Started,
                Some("uk-UA"),
            ),
            "Запускаю `npm test`"
        );
        assert_eq!(
            render_tool_narration_with_locale(
                None,
                &tool_call,
                ToolNarrationPhase::Completed,
                Some("uk-UA"),
            ),
            "Запустив `npm test`"
        );
    }

    #[test]
    fn renders_ukrainian_bash_narration_with_commands_arg() {
        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "bash".to_string(),
            arguments: json!({ "commands": "echo hi" }),
        };

        assert_eq!(
            render_tool_narration_with_locale(
                None,
                &tool_call,
                ToolNarrationPhase::Started,
                Some("uk-UA"),
            ),
            "Запускаю `echo hi`"
        );
        assert_eq!(
            render_tool_narration_with_locale(
                None,
                &tool_call,
                ToolNarrationPhase::Completed,
                Some("uk-UA"),
            ),
            "Запустив `echo hi`"
        );
    }

    #[test]
    fn renders_ukrainian_group_headline() {
        let tool_calls = vec![
            ToolCall {
                id: "call_1".to_string(),
                name: "list_directory".to_string(),
                arguments: json!({ "path": "/workspace" }),
            },
            ToolCall {
                id: "call_2".to_string(),
                name: "read_file".to_string(),
                arguments: json!({ "path": "/workspace/AGENTS.md" }),
            },
            ToolCall {
                id: "call_3".to_string(),
                name: "grep_files".to_string(),
                arguments: json!({ "pattern": "Doppler" }),
            },
        ];

        let headline = render_group_headline_with_locale(
            &tool_calls,
            &[],
            ToolNarrationPhase::Completed,
            Some("uk"),
        )
        .unwrap();

        assert_eq!(
            headline,
            "Переглянув файли у поточній директорії, Прочитав AGENTS.md, і ще 1 дію"
        );
    }

    #[test]
    fn narration_noun_produces_operation_based_narration() {
        use crate::tool_types::{BuiltinTool, ToolDefinition, ToolHints};

        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "manage_agents".to_string(),
            arguments: json!({ "operation": "create", "name": "Neon Cartographer" }),
        };

        let def = ToolDefinition::Builtin(BuiltinTool {
            name: "manage_agents".to_string(),
            display_name: Some("Manage Agents".to_string()),
            description: String::new(),
            parameters: json!({}),
            policy: Default::default(),
            category: None,
            deferrable: Default::default(),
            hints: ToolHints::default().with_narration_noun("agent"),
        });

        assert_eq!(
            render_tool_narration(Some(&def), &tool_call, ToolNarrationPhase::Started),
            "Creating agent: Neon Cartographer"
        );
        assert_eq!(
            render_tool_narration(Some(&def), &tool_call, ToolNarrationPhase::Completed),
            "Created agent: Neon Cartographer"
        );
        assert_eq!(
            render_tool_narration(Some(&def), &tool_call, ToolNarrationPhase::Failed),
            "Failed to create agent: Neon Cartographer"
        );
    }

    #[test]
    fn narration_noun_without_name_shows_noun_only() {
        use crate::tool_types::{BuiltinTool, ToolDefinition, ToolHints};

        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "manage_agents".to_string(),
            arguments: json!({ "operation": "delete", "agent_id": "agent_123" }),
        };

        let def = ToolDefinition::Builtin(BuiltinTool {
            name: "manage_agents".to_string(),
            display_name: Some("Manage Agents".to_string()),
            description: String::new(),
            parameters: json!({}),
            policy: Default::default(),
            category: None,
            deferrable: Default::default(),
            hints: ToolHints::default().with_narration_noun("agent"),
        });

        assert_eq!(
            render_tool_narration(Some(&def), &tool_call, ToolNarrationPhase::Completed),
            "Deleted agent"
        );
    }

    #[test]
    fn narration_noun_without_operation_falls_back() {
        use crate::tool_types::{BuiltinTool, ToolDefinition, ToolHints};

        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "manage_agents".to_string(),
            arguments: json!({ "name": "Test" }),
        };

        let def = ToolDefinition::Builtin(BuiltinTool {
            name: "manage_agents".to_string(),
            display_name: Some("Manage Agents".to_string()),
            description: String::new(),
            parameters: json!({}),
            policy: Default::default(),
            category: None,
            deferrable: Default::default(),
            hints: ToolHints::default().with_narration_noun("agent"),
        });

        // No operation arg → falls back to generic
        assert_eq!(
            render_tool_narration(Some(&def), &tool_call, ToolNarrationPhase::Completed),
            "Ran Manage Agents"
        );
    }

    #[test]
    fn narration_noun_ukrainian_uses_generic_fallback() {
        use crate::tool_types::{BuiltinTool, ToolDefinition, ToolHints};

        let tool_call = ToolCall {
            id: "call_1".to_string(),
            name: "manage_agents".to_string(),
            arguments: json!({ "operation": "create", "name": "Test Agent" }),
        };

        let def = ToolDefinition::Builtin(BuiltinTool {
            name: "manage_agents".to_string(),
            display_name: Some("Manage Agents".to_string()),
            description: String::new(),
            parameters: json!({}),
            policy: Default::default(),
            category: None,
            deferrable: Default::default(),
            hints: ToolHints::default().with_narration_noun("agent"),
        });

        // Ukrainian doesn't use operation_narration yet — stays in Ukrainian
        assert_eq!(
            render_tool_narration_with_locale(
                Some(&def),
                &tool_call,
                ToolNarrationPhase::Completed,
                Some("uk"),
            ),
            "Запустив Manage Agents"
        );
    }
}
