use crate::config::{
    acquire_profile_lock, apply_cli_overrides_to_config, clear_runtime_state,
    config_path_for_profile, load_or_create_config, load_runtime_state_result,
    profile_lock_path_for_profile, resolve_log_file_path, runtime_state_path_for_ephemeral,
    runtime_state_path_for_profile, save_config, validate_display_name_override,
    validate_saved_command_text, RuntimeStateLoadResult,
};
use crate::{
    CliMode, CliOptions, CliRequest, CliRunTarget, Config, PersistentConfigAccess, StartupState,
    APP_NAME, BG_CHILD_ENV, DEFAULT_COMMAND, MAX_PROFILE_LENGTH, RUNTIME_ALREADY_OPEN_MESSAGE,
    RUNTIME_INVALID_CLEARED_MESSAGE,
};
use std::env;
use std::fs;
use std::io;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};
use std::thread;
use std::time::{Duration, Instant};

pub(crate) fn detach_to_background_if_needed(cli: &CliOptions) -> Result<(), String> {
    if env::var_os(BG_CHILD_ENV).is_some() {
        return Ok(());
    }

    if !should_detach_for_terminal_launch() {
        return Ok(());
    }

    let executable =
        env::current_exe().map_err(|err| format!("unable to resolve executable path: {err}"))?;

    let mut command = Command::new(executable);
    command
        .args(build_detached_args(cli))
        .env(BG_CHILD_ENV, "1")
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null());

    #[cfg(unix)]
    {
        unsafe {
            command.pre_exec(|| {
                if libc::setsid() == -1 {
                    return Err(io::Error::last_os_error());
                }
                Ok(())
            });
        }
    }

    let mut child = command
        .spawn()
        .map_err(|err| format!("unable to spawn detached process: {err}"))?;

    // This is a startup heuristic, not a full parent/child readiness handshake.
    // It catches immediate startup failures, but failures after this window remain
    // the detached child's responsibility to report via its own logging path.
    let startup_timeout = Duration::from_millis(1_000);
    let poll_interval = Duration::from_millis(50);
    let start = Instant::now();
    while start.elapsed() < startup_timeout {
        if let Ok(Some(status)) = child.try_wait() {
            return Err(format!("detached process exited early: {status}"));
        }
        thread::sleep(poll_interval);
    }

    if let Ok(Some(status)) = child.try_wait() {
        return Err(format!("detached process exited early: {status}"));
    }

    process::exit(0);
}

pub(crate) fn build_detached_args(cli: &CliOptions) -> Vec<String> {
    match &cli.run_target {
        CliRunTarget::PersistentProfile { profile } => {
            let mut args = vec!["--config".to_string(), profile.clone()];
            if let Some(name_override) = &cli.name_override {
                args.push("--name".to_string());
                args.push(name_override.clone());
            }
            if let Some(command_override) = &cli.command_override {
                args.push("--command".to_string());
                args.push(command_override.clone());
            }
            if let Some(icon_source) = &cli.icon_source {
                args.push("--icon".to_string());
                args.push(icon_source.display().to_string());
            }
            if let Some(log_file) = &cli.log_file {
                args.push("--log-file".to_string());
                args.push(log_file.display().to_string());
            }
            args
        }
        CliRunTarget::EphemeralArgv { argv } => {
            let mut args = Vec::with_capacity(argv.len() + 3);
            if let Some(name_override) = &cli.name_override {
                args.push("--name".to_string());
                args.push(name_override.clone());
            }
            args.push("--".to_string());
            args.extend(argv.iter().cloned());
            args
        }
    }
}

pub(crate) fn tray_tooltip(run_target: &CliRunTarget, display_name: Option<&str>) -> String {
    if let Some(display_name) = display_name {
        return display_name.to_string();
    }
    if matches!(run_target, CliRunTarget::EphemeralArgv { .. }) {
        return APP_NAME.to_string();
    }
    format!("{APP_NAME} ({})", run_target_label(run_target))
}

pub(crate) fn run_target_label(run_target: &CliRunTarget) -> String {
    match run_target {
        CliRunTarget::PersistentProfile { profile } => profile.clone(),
        CliRunTarget::EphemeralArgv { .. } => "ephemeral".to_string(),
    }
}

pub(crate) fn persistent_config_access(
    profile: Option<String>,
    config_path: Option<PathBuf>,
) -> Option<PersistentConfigAccess> {
    match (profile, config_path) {
        (Some(profile), Some(config_path)) => Some(PersistentConfigAccess {
            profile,
            config_path,
        }),
        _ => None,
    }
}

pub(crate) fn should_expose_configuration(access: Option<&PersistentConfigAccess>) -> bool {
    access.is_some()
}

pub(crate) fn ephemeral_command_text(argv: &[String]) -> String {
    argv.iter()
        .map(|arg| shell_command_token(arg))
        .collect::<Vec<String>>()
        .join(" ")
}

fn shell_command_token(arg: &str) -> String {
    if !arg.is_empty()
        && arg
            .chars()
            .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '/' | '.' | '=' | ':'))
    {
        return arg.to_string();
    }

    let escaped = arg.replace('\'', "'\"'\"'");
    format!("'{escaped}'")
}

pub(crate) fn ephemeral_runtime_config(argv: &[String], display_name: Option<String>) -> Config {
    Config {
        command: ephemeral_command_text(argv),
        autostart: false,
        name: display_name,
        icon_path: None,
        log_to_file: false,
        log_file_path: None,
    }
}

pub(crate) struct PreflightState {
    profile: String,
    config_path: PathBuf,
    config: Config,
}

fn acquire_profile_lock_for_startup(
    profile: &str,
) -> Result<(bool, Option<crate::config::ProfileLockHandle>), String> {
    let lock_path = profile_lock_path_for_profile(profile);
    match lock_path {
        Some(ref path) => match acquire_profile_lock(path) {
            Ok(handle) => Ok((true, Some(handle))),
            Err(err) if err.contains("profile lock already held") => Ok((false, None)),
            Err(err) => Err(err),
        },
        None => Ok((true, None)),
    }
}

fn load_config_without_side_effects(path: &Path) -> Config {
    let default = Config {
        command: DEFAULT_COMMAND.to_string(),
        autostart: false,
        name: None,
        icon_path: None,
        log_to_file: false,
        log_file_path: None,
    };

    let content = match fs::read_to_string(path) {
        Ok(data) => data,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return default,
        Err(err) => {
            eprintln!("failed to read config at {}: {err}", path.display());
            return default;
        }
    };

    match toml::from_str(&content) {
        Ok(config) => config,
        Err(err) => {
            eprintln!("failed to parse config at {}: {err}", path.display());
            default
        }
    }
}

pub(crate) fn build_startup_state(cli: &CliOptions) -> Result<StartupState, String> {
    match &cli.run_target {
        CliRunTarget::PersistentProfile { profile } => {
            let (owns_profile_lock, profile_lock) = acquire_profile_lock_for_startup(profile)?;
            let preflight = if owns_profile_lock {
                prepare_preflight_for_persistent_profile(profile, cli)?
            } else {
                prepare_readonly_preflight_for_persistent_profile(profile)?
            };

            complete_persistent_startup(
                preflight,
                cli.name_override.as_deref(),
                owns_profile_lock,
                profile_lock,
            )
        }
        CliRunTarget::EphemeralArgv { argv } => {
            let runtime_state_path = runtime_state_path_for_ephemeral();
            let (runtime_ownership, startup_message) =
                load_startup_runtime_state(runtime_state_path.as_deref());
            let display_name = cli.name_override.clone();

            Ok(StartupState {
                profile_label: run_target_label(&cli.run_target),
                display_name: display_name.clone(),
                persistent_config_access: None,
                config: ephemeral_runtime_config(argv, display_name),
                log_file_path: None,
                launch_on_startup: true,
                runtime_state_path,
                runtime_ownership,
                restored_running: false,
                owns_profile_lock: false,
                profile_lock: None,
                startup_message,
            })
        }
    }
}

pub(crate) fn prepare_preflight_for_persistent_profile(
    profile: &str,
    cli: &CliOptions,
) -> Result<PreflightState, String> {
    let config_path = config_path_for_profile(profile)
        .ok_or_else(|| "failed to resolve configuration path".to_string())?;
    let mut config = load_or_create_config(&config_path);

    match apply_cli_overrides_to_config(&mut config, cli) {
        Ok(true) => save_config(&config_path, &config)
            .map_err(|err| format!("failed to save config overrides: {err}"))?,
        Ok(false) => {}
        Err(err) => return Err(format!("failed to apply CLI overrides: {err}")),
    }

    Ok(PreflightState {
        profile: profile.to_string(),
        config_path,
        config,
    })
}

fn prepare_readonly_preflight_for_persistent_profile(
    profile: &str,
) -> Result<PreflightState, String> {
    let config_path = config_path_for_profile(profile)
        .ok_or_else(|| "failed to resolve configuration path".to_string())?;
    let config = load_config_without_side_effects(&config_path);

    Ok(PreflightState {
        profile: profile.to_string(),
        config_path,
        config,
    })
}

pub(crate) fn complete_persistent_startup(
    preflight: PreflightState,
    cli_name_override: Option<&str>,
    owns_profile_lock: bool,
    profile_lock: Option<crate::config::ProfileLockHandle>,
) -> Result<StartupState, String> {
    let PreflightState {
        profile,
        config_path,
        config,
    } = preflight;
    let display_name = resolve_display_name(cli_name_override, config.name.as_deref());

    let (runtime_ownership, startup_message) = if owns_profile_lock {
        let runtime_state_path = runtime_state_path_for_profile(&profile);
        load_startup_runtime_state(runtime_state_path.as_deref())
    } else {
        (None, Some(RUNTIME_ALREADY_OPEN_MESSAGE.to_string()))
    };

    Ok(StartupState {
        profile_label: profile.clone(),
        display_name,
        persistent_config_access: persistent_config_access(
            Some(profile.clone()),
            Some(config_path),
        ),
        log_file_path: resolve_log_file_path(&profile, &config),
        launch_on_startup: config.autostart,
        config,
        runtime_state_path: if owns_profile_lock {
            runtime_state_path_for_profile(&profile)
        } else {
            None
        },
        runtime_ownership,
        restored_running: false,
        owns_profile_lock,
        profile_lock,
        startup_message,
    })
}

fn load_startup_runtime_state(
    runtime_state_path: Option<&Path>,
) -> (Option<crate::RuntimeOwnershipState>, Option<String>) {
    let Some(path) = runtime_state_path else {
        return (None, None);
    };

    match load_runtime_state_result(path) {
        RuntimeStateLoadResult::Loaded(state) => (Some(state), None),
        RuntimeStateLoadResult::Missing => (None, None),
        RuntimeStateLoadResult::Invalid => {
            if let Err(err) = clear_runtime_state(path) {
                eprintln!("failed to clear invalid runtime state: {err}");
            }
            (None, Some(RUNTIME_INVALID_CLEARED_MESSAGE.to_string()))
        }
    }
}

pub(crate) fn prepare_run_startup(cli: &CliOptions) -> Result<StartupState, String> {
    prepare_run_startup_with(cli, detach_to_background_if_needed)
}

pub(crate) fn prepare_run_startup_with<F>(
    cli: &CliOptions,
    detach: F,
) -> Result<StartupState, String>
where
    F: FnOnce(&CliOptions) -> Result<(), String>,
{
    match &cli.run_target {
        CliRunTarget::PersistentProfile { profile } => {
            detach(cli).map_err(|err| format!("failed to start background instance: {err}"))?;
            let (owns_profile_lock, profile_lock) = acquire_profile_lock_for_startup(profile)?;
            let preflight = if owns_profile_lock {
                prepare_preflight_for_persistent_profile(profile, cli)?
            } else {
                prepare_readonly_preflight_for_persistent_profile(profile)?
            };
            complete_persistent_startup(
                preflight,
                cli.name_override.as_deref(),
                owns_profile_lock,
                profile_lock,
            )
        }
        CliRunTarget::EphemeralArgv { .. } => {
            detach(cli).map_err(|err| format!("failed to start background instance: {err}"))?;
            build_startup_state(cli)
        }
    }
}

fn should_detach_for_terminal_launch() -> bool {
    unsafe {
        libc::isatty(libc::STDIN_FILENO) == 1
            || libc::isatty(libc::STDOUT_FILENO) == 1
            || libc::isatty(libc::STDERR_FILENO) == 1
    }
}

pub(crate) fn command_file_name(arg: &str) -> Option<&str> {
    Path::new(arg).file_name().and_then(|name| name.to_str())
}

fn sudo_option_takes_value(arg: &str) -> bool {
    matches!(
        arg,
        "-C" | "-D"
            | "-R"
            | "-T"
            | "-U"
            | "-g"
            | "-h"
            | "-p"
            | "-r"
            | "-t"
            | "-u"
            | "--chdir"
            | "--chroot"
            | "--close-from"
            | "--command-timeout"
            | "--group"
            | "--host"
            | "--other-user"
            | "--prompt"
            | "--role"
            | "--type"
            | "--user"
    )
}

fn is_environment_assignment(arg: &str) -> bool {
    let Some((name, _)) = arg.split_once('=') else {
        return false;
    };

    !name.is_empty() && !name.starts_with('-')
}

pub(crate) fn effective_command_token(argv: &[String]) -> Option<&str> {
    let first = argv.first()?;
    let first_name = command_file_name(first)?;
    if first_name != "sudo" {
        return Some(first_name);
    }

    let mut skip_next = false;
    for arg in &argv[1..] {
        if skip_next {
            skip_next = false;
            continue;
        }

        if arg == "--" {
            continue;
        }

        if is_environment_assignment(arg) {
            continue;
        }

        if arg.starts_with("--") {
            if let Some((flag, _)) = arg.split_once('=') {
                if sudo_option_takes_value(flag) {
                    continue;
                }
            }
            if sudo_option_takes_value(arg) {
                skip_next = true;
                continue;
            }
        } else if arg.starts_with('-') {
            if arg.len() == 2 && sudo_option_takes_value(arg) {
                skip_next = true;
                continue;
            }
            if arg.len() > 2 && sudo_option_takes_value(&arg[..2]) {
                continue;
            }
            continue;
        }

        return command_file_name(arg);
    }

    None
}

pub(crate) fn parse_cli_args() -> Result<CliOptions, String> {
    match parse_cli_request_from(env::args())? {
        CliRequest::Run(cli) => Ok(cli),
        CliRequest::PrintHelp => {
            print_help();
            process::exit(0);
        }
        CliRequest::PrintVersion => {
            print_version();
            process::exit(0);
        }
    }
}

pub(crate) fn parse_cli_request_from<I, S>(args: I) -> Result<CliRequest, String>
where
    I: IntoIterator<Item = S>,
    S: Into<String>,
{
    let args: Vec<String> = args.into_iter().map(Into::into).collect();
    if let Ok(cli) = parse_cli_args_from(args.clone()) {
        return Ok(CliRequest::Run(cli));
    }

    let raw_args = args.get(1..).unwrap_or(&[]);
    let option_args = match raw_args.iter().position(|arg| arg == "--") {
        Some(separator_index) => &raw_args[..separator_index],
        None => raw_args,
    };

    let mut skip_next = false;
    for arg in option_args {
        if skip_next {
            skip_next = false;
            continue;
        }

        if arg == "-h" || arg == "--help" {
            return Ok(CliRequest::PrintHelp);
        }
        if arg == "-V" || arg == "--version" {
            return Ok(CliRequest::PrintVersion);
        }

        if matches!(
            arg.as_str(),
            "-c" | "--config"
                | "-cmd"
                | "--command"
                | "-n"
                | "--name"
                | "--icon"
                | "--log-file"
                | "--output-dir"
        ) {
            skip_next = true;
        }
    }

    parse_cli_args_from(args).map(CliRequest::Run)
}

pub(crate) fn parse_cli_args_from<I, S>(args: I) -> Result<CliOptions, String>
where
    I: IntoIterator<Item = S>,
    S: Into<String>,
{
    let mut args: Vec<String> = args.into_iter().map(Into::into).collect();
    if !args.is_empty() {
        args.remove(0);
    }

    let mut mode = CliMode::Run;
    if args.first().is_some_and(|arg| arg == "desktop-file") {
        mode = CliMode::DesktopFile {
            output_dir: None,
            autostart: false,
        };
        args.remove(0);
    }

    if let Some(separator_index) = args.iter().position(|arg| arg == "--") {
        if matches!(mode, CliMode::DesktopFile { .. }) {
            return Err("desktop-file does not support ephemeral mode".to_string());
        }

        let prefix = &args[..separator_index];
        let argv = args[(separator_index + 1)..].to_vec();
        if argv.is_empty() {
            return Err("ephemeral mode command cannot be empty".to_string());
        }
        if let Some(flag) = prefix
            .iter()
            .find(|arg| arg.as_str() == "-c" || arg.as_str() == "--config")
        {
            return Err(format!(
                "cannot mix persistent profile mode and ephemeral mode: {flag}"
            ));
        }
        if let Some(flag) = prefix
            .iter()
            .find(|arg| matches!(arg.as_str(), "-cmd" | "--command" | "--icon" | "--log-file"))
        {
            return Err(format!("{flag} is not allowed in ephemeral mode"));
        }
        if let Some(flag) = prefix
            .iter()
            .find(|arg| matches!(arg.as_str(), "--output-dir" | "--autostart"))
        {
            return Err(format!("{flag} is only valid with desktop-file"));
        }
        let mut name_override: Option<String> = None;
        let mut i = 0usize;
        while i < prefix.len() {
            match prefix[i].as_str() {
                "-n" | "--name" => {
                    let value = prefix
                        .get(i + 1)
                        .ok_or_else(|| "missing value for -n/--name".to_string())?;
                    if name_override.is_some() {
                        return Err("-n/--name provided more than once".to_string());
                    }
                    name_override = Some(validate_display_name(value)?);
                    i += 2;
                }
                unknown => {
                    return Err(format!("unknown argument: {unknown}"));
                }
            }
        }

        return Ok(CliOptions {
            run_target: CliRunTarget::EphemeralArgv { argv },
            command_override: None,
            name_override,
            icon_source: None,
            log_file: None,
            mode,
        });
    }

    let mut profile: Option<String> = None;
    let mut command_override: Option<String> = None;
    let mut name_override: Option<String> = None;
    let mut icon_source = None;
    let mut log_file = None;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "-c" | "--config" => {
                let value = args
                    .get(i + 1)
                    .ok_or_else(|| "missing value for -c/--config".to_string())?;
                if profile.is_some() {
                    return Err("-c/--config provided more than once".to_string());
                }
                profile = Some(validate_profile_name(value)?);
                i += 2;
            }
            "-cmd" | "--command" => {
                let value = args
                    .get(i + 1)
                    .ok_or_else(|| "missing value for -cmd/--command".to_string())?;
                if command_override.is_some() {
                    return Err("-cmd/--command provided more than once".to_string());
                }
                command_override = Some(validate_command_override(value)?);
                i += 2;
            }
            "-n" | "--name" => {
                let value = args
                    .get(i + 1)
                    .ok_or_else(|| "missing value for -n/--name".to_string())?;
                if name_override.is_some() {
                    return Err("-n/--name provided more than once".to_string());
                }
                name_override = Some(validate_display_name(value)?);
                i += 2;
            }
            "--icon" => {
                let value = args
                    .get(i + 1)
                    .ok_or_else(|| "missing value for --icon".to_string())?;
                icon_source = Some(PathBuf::from(value));
                i += 2;
            }
            "--log-file" => {
                let value = args
                    .get(i + 1)
                    .ok_or_else(|| "missing value for --log-file".to_string())?;
                if matches!(mode, CliMode::DesktopFile { .. }) {
                    return Err("--log-file is only valid in app mode".to_string());
                }
                log_file = Some(PathBuf::from(value));
                i += 2;
            }
            "--output-dir" => {
                let value = args
                    .get(i + 1)
                    .ok_or_else(|| "missing value for --output-dir".to_string())?;
                match &mut mode {
                    CliMode::DesktopFile {
                        output_dir,
                        autostart: _,
                    } => {
                        *output_dir = Some(PathBuf::from(value));
                        i += 2;
                    }
                    CliMode::Run => {
                        return Err("--output-dir is only valid with desktop-file".to_string());
                    }
                }
            }
            "--autostart" => match &mut mode {
                CliMode::DesktopFile {
                    output_dir: _,
                    autostart,
                } => {
                    *autostart = true;
                    i += 1;
                }
                CliMode::Run => {
                    return Err("--autostart is only valid with desktop-file".to_string());
                }
            },
            unknown => {
                return Err(format!("unknown argument: {unknown}"));
            }
        }
    }

    let profile =
        profile.ok_or_else(|| "missing required -c/--config PROFILE argument".to_string())?;

    Ok(CliOptions {
        run_target: CliRunTarget::PersistentProfile { profile },
        command_override,
        name_override,
        icon_source,
        log_file,
        mode,
    })
}

pub(crate) fn validate_runtime_mode(cli: &CliOptions) -> Result<(), String> {
    if let CliRunTarget::EphemeralArgv { ref argv } = cli.run_target {
        if effective_command_token(argv).is_some_and(|token| token == APP_NAME) {
            return Err("recursive ephemeral mode cannot launch givetray again".to_string());
        }
    }

    Ok(())
}

const HELP_USAGE_LINES: [&str; 3] = [
    "  {name} -c PROFILE [-n NAME|--name NAME] [-cmd COMMAND|--command COMMAND] [--icon ICON_PATH] [--log-file LOG_PATH]",
    "  {name} [-n NAME|--name NAME] -- <command...>",
    "  {name} desktop-file -c PROFILE [-n NAME|--name NAME] [-cmd COMMAND|--command COMMAND] [--output-dir DIR] [--autostart] [--icon ICON_PATH]",
];

const HELP_MODE_LINES: [&str; 3] = [
    "  Persistent profile mode  Saved config, desktop entry support, and Configuration access",
    "  Ephemeral mode           Temporary, profile-free command launch via -- <command...>",
    "                           Starts immediately and keeps tray Start/Stop; Configuration is hidden",
];

const HELP_OPTION_LINES: [&str; 9] = [
    "  -c, --config PROFILE    Required profile name (letters, numbers, '-' or '_')",
    "  -n, --name NAME         Human-readable tray name (persisted for profiles)",
    "  -cmd, --command COMMAND Set or overwrite saved command for the profile",
    "      --icon ICON_PATH    Copy icon into the selected profile and update config",
    "      --log-file LOG_PATH Enable log-to-file and set output path (app mode only)",
    "      --output-dir DIR    Output directory for desktop file (desktop-file mode only)",
    "      --autostart         Mark desktop file as autostart and default to ~/.config/autostart",
    "  -h, --help              Show this help",
    "  -V, --version           Show version",
];

fn render_help_lines(lines: &[&str]) -> String {
    lines
        .iter()
        .map(|line| line.replace("{name}", APP_NAME))
        .collect::<Vec<String>>()
        .join("\n")
}

pub(crate) fn help_text() -> String {
    format!(
        "{name}\n\nUsage:\n{usage}\n\nModes:\n{modes}\n\nOptions:\n{options}\n",
        name = APP_NAME,
        usage = render_help_lines(&HELP_USAGE_LINES),
        modes = render_help_lines(&HELP_MODE_LINES),
        options = render_help_lines(&HELP_OPTION_LINES),
    )
}

fn print_help() {
    println!("{}", help_text());
}

fn print_version() {
    println!("{APP_NAME} {}", env!("CARGO_PKG_VERSION"));
}

fn validate_profile_name(raw: &str) -> Result<String, String> {
    let profile = raw.trim();
    if profile.is_empty() {
        return Err("profile cannot be empty".to_string());
    }
    if profile.len() > MAX_PROFILE_LENGTH {
        return Err(format!(
            "profile is too long (max {MAX_PROFILE_LENGTH} characters)"
        ));
    }
    if !profile
        .chars()
        .all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_')
    {
        return Err("invalid profile name: use only letters, numbers, '-' or '_'".to_string());
    }
    Ok(profile.to_string())
}

fn validate_command_override(raw: &str) -> Result<String, String> {
    match validate_saved_command_text(raw) {
        Ok(command) => Ok(command),
        Err(err) if err.starts_with("invalid command: ") => {
            Err(err.replacen("invalid command: ", "invalid -cmd/--command value: ", 1))
        }
        Err(err) => Err(err),
    }
}

fn normalize_display_name(raw: &str) -> Option<String> {
    crate::config::normalize_display_name(raw)
}

fn resolve_display_name(
    cli_name_override: Option<&str>,
    config_name: Option<&str>,
) -> Option<String> {
    cli_name_override
        .and_then(normalize_display_name)
        .or_else(|| config_name.and_then(normalize_display_name))
}

fn validate_display_name(raw: &str) -> Result<String, String> {
    validate_display_name_override(raw)
}
