use std::fmt::Write;

use anyhow::Result;

use crate::config::{Config, Span};

use super::shared::{
    Features, ansi_pair, color_escape, features as detect_features, git_branch_icon, layout,
    shell_single_quote, status_separator, zsh_os_icon_cases, zsh_os_unknown_icon, zsh_prompt_ansi,
    zsh_prompt_single_quote, zsh_status_template_cases, zsh_upstream_icon_cases,
};

const RESET: &str = "\u{1b}[0m";

pub fn generate(config: &Config) -> Result<String> {
    let layout = layout(config)?;
    let os = layout.os;
    let path = layout.path;
    let git = layout.git;
    let prompt = layout.prompt_span;
    let features = detect_features(config, &layout);

    let os_style = zsh_ansi(&ansi_pair(os.foreground.as_ref(), os.background.as_ref())?);
    let path_style = zsh_ansi(&ansi_pair(
        path.foreground.as_ref(),
        path.background.as_ref(),
    )?);
    let git_clean_style = if let Some(git) = git {
        zsh_ansi(&ansi_pair(
            git.foreground.as_ref(),
            git.background.as_ref(),
        )?)
    } else {
        String::new()
    };
    let git_dirty_style = if let Some(git) = git {
        zsh_ansi(&ansi_pair(
            git.foreground.as_ref(),
            git.dirty_background.as_ref().or(git.background.as_ref()),
        )?)
    } else {
        String::new()
    };
    let reset = zsh_ansi(RESET);

    let os_tail = edge(
        os.tail.as_deref(),
        os.background.as_ref(),
        path.background.as_ref(),
        os.invert_tail,
    )?;
    let path_sep_clean = edge(
        path.separator.as_deref(),
        path.background.as_ref(),
        git.and_then(|git| git.background.as_ref()),
        path.invert_separator,
    )?;
    let path_sep_dirty = edge(
        path.separator.as_deref(),
        path.background.as_ref(),
        git.and_then(|git| git.dirty_background.as_ref().or(git.background.as_ref())),
        path.invert_separator,
    )?;
    let path_sep_last = edge(
        path.separator.as_deref().or(path.tail.as_deref()),
        path.background.as_ref(),
        None,
        path.invert_separator || path.invert_tail,
    )?;
    let git_sep_clean = edge(
        git.and_then(|git| git.separator.as_deref()),
        git.and_then(|git| git.background.as_ref()),
        None,
        git.is_some_and(|git| git.invert_separator),
    )?;
    let git_sep_dirty = edge(
        git.and_then(|git| git.separator.as_deref()),
        git.and_then(|git| git.dirty_background.as_ref().or(git.background.as_ref())),
        None,
        git.is_some_and(|git| git.invert_separator),
    )?;

    let prompt_ok_style = zsh_ansi(&ansi_pair(prompt.success_foreground.as_ref(), None)?);
    let prompt_error_style = zsh_ansi(&ansi_pair(prompt.error_foreground.as_ref(), None)?);

    let transient = config.transient.as_ref();
    let transient_enabled = transient.is_some_and(|transient| transient.enabled);
    let transient_template = transient
        .map(|transient| transient.template.as_str())
        .filter(|template| !template.is_empty())
        .unwrap_or(&prompt.template);
    let transient_ok_style = zsh_ansi(&ansi_pair(
        transient
            .and_then(|transient| transient.success_foreground.as_ref())
            .or(prompt.success_foreground.as_ref()),
        transient.and_then(|transient| transient.background.as_ref()),
    )?);
    let transient_error_style = zsh_ansi(&ansi_pair(
        transient
            .and_then(|transient| transient.error_foreground.as_ref())
            .or(prompt.error_foreground.as_ref()),
        transient.and_then(|transient| transient.background.as_ref()),
    )?);

    let mut script = String::new();
    writeln!(script, "# Generated by cailoxo. Do not edit by hand.")?;
    writeln!(script, "# shell: zsh")?;
    writeln!(script)?;
    writeln!(script, "if [[ -o interactive ]]; then")?;
    writeln!(script, "  autoload -Uz add-zsh-hook")?;
    writeln!(script)?;
    write_consts(
        &mut script,
        os,
        path,
        git,
        prompt,
        transient_template,
        &os_style,
        &path_style,
        &git_clean_style,
        &git_dirty_style,
        &reset,
        &os_tail,
        &path_sep_clean,
        &path_sep_dirty,
        &path_sep_last,
        &git_sep_clean,
        &git_sep_dirty,
        &prompt_ok_style,
        &prompt_error_style,
        &transient_ok_style,
        &transient_error_style,
        config.final_space,
        &features,
    )?;
    write_body(&mut script, git, transient_enabled, &features)?;
    writeln!(script, "fi")?;
    Ok(script)
}

#[allow(clippy::too_many_arguments)]
fn write_consts(
    out: &mut String,
    os: &Span,
    path: &Span,
    git: Option<&Span>,
    prompt: &Span,
    transient_template: &str,
    os_style: &str,
    path_style: &str,
    git_clean_style: &str,
    git_dirty_style: &str,
    reset: &str,
    os_tail: &str,
    path_sep_clean: &str,
    path_sep_dirty: &str,
    path_sep_last: &str,
    git_sep_clean: &str,
    git_sep_dirty: &str,
    prompt_ok_style: &str,
    prompt_error_style: &str,
    transient_ok_style: &str,
    transient_error_style: &str,
    final_space: bool,
    features: &Features,
) -> Result<()> {
    let min_dirs = path.setting_str("min_dirs").unwrap_or("1");
    let min_dirs = min_dirs.parse::<usize>().unwrap_or(1);
    let path_url = path.setting_bool("url").unwrap_or(false)
        || path.setting_bool("hyperlink").unwrap_or(false);
    let osc7 = path.setting_bool("osc7").unwrap_or(false);
    let branch_icon = if let Some(git) = git {
        git_branch_icon(git)?
    } else {
        String::new()
    };
    let fetch_remote_interval_s = git
        .and_then(|git| git.settings.get("fetch_remote_interval_ms"))
        .and_then(|value| value.as_integer())
        .unwrap_or(60_000)
        .max(0)
        .saturating_add(999)
        / 1000;
    let fetch_remote_timeout_s = git
        .and_then(|git| git.settings.get("fetch_remote_timeout_ms"))
        .and_then(|value| value.as_integer())
        .unwrap_or(5_000)
        .max(1)
        .saturating_add(999)
        / 1000;

    writeln!(
        out,
        "  typeset -g __CAILOXO_OS_TEMPLATE={}",
        zsh_prompt_single_quote(&os.template)
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_PATH_TEMPLATE={}",
        zsh_prompt_single_quote(&path.template)
    )?;
    if let Some(git) = git {
        writeln!(
            out,
            "  typeset -g __CAILOXO_GIT_TEMPLATE={}",
            zsh_prompt_single_quote(&git.template)
        )?;
    }
    writeln!(
        out,
        "  typeset -g __CAILOXO_PROMPT_TEMPLATE={}",
        zsh_prompt_single_quote(&prompt.template)
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_TRANSIENT_TEMPLATE={}",
        zsh_prompt_single_quote(transient_template)
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_HOME_ICON={}",
        zsh_prompt_single_quote(path.setting_str("home_icon").unwrap_or("~"))
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_FOLDER_ICON={}",
        zsh_prompt_single_quote(path.setting_str("folder_icon").unwrap_or("…"))
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_EDGE_FORMAT={}",
        shell_single_quote(path.setting_str("edge_format").unwrap_or(""))
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_GITDIR_FORMAT={}",
        shell_single_quote(path.setting_str("gitdir_format").unwrap_or(""))
    )?;
    if let Some(git) = git {
        writeln!(
            out,
            "  typeset -g __CAILOXO_BRANCH_ICON={}",
            zsh_prompt_single_quote(&branch_icon)
        )?;
        if features.git_status {
            writeln!(
                out,
                "  typeset -g __CAILOXO_STATUS_SEPARATOR={}",
                shell_single_quote(status_separator(git))
            )?;
        }
        writeln!(
            out,
            "  typeset -gi __CAILOXO_GIT_STATUS={}",
            usize::from(features.git_status)
        )?;
        writeln!(
            out,
            "  typeset -gi __CAILOXO_FETCH_UPSTREAM_ICON={}",
            usize::from(features.git_upstream_icon)
        )?;
        writeln!(
            out,
            "  typeset -gi __CAILOXO_GIT_URL={}",
            usize::from(features.git_url)
        )?;
        writeln!(
            out,
            "  typeset -gi __CAILOXO_FETCH_REMOTE={}",
            usize::from(features.git_fetch)
        )?;
        if features.git_fetch {
            writeln!(
                out,
                "  typeset -gi __CAILOXO_FETCH_REMOTE_INTERVAL_S={fetch_remote_interval_s}"
            )?;
            writeln!(
                out,
                "  typeset -gi __CAILOXO_FETCH_REMOTE_TIMEOUT_S={fetch_remote_timeout_s}"
            )?;
        }
    }
    writeln!(out, "  typeset -gi __CAILOXO_MIN_DIRS={min_dirs}")?;
    writeln!(
        out,
        "  typeset -gi __CAILOXO_FINAL_SPACE={}",
        usize::from(final_space)
    )?;
    writeln!(
        out,
        "  typeset -gi __CAILOXO_PATH_URL={}",
        usize::from(path_url)
    )?;
    writeln!(out, "  typeset -gi __CAILOXO_OSC7={}", usize::from(osc7))?;
    writeln!(out, "  typeset -g __CAILOXO_OS_STYLE=$'{}'", os_style)?;
    writeln!(out, "  typeset -g __CAILOXO_PATH_STYLE=$'{}'", path_style)?;
    if git.is_some() {
        writeln!(
            out,
            "  typeset -g __CAILOXO_GIT_CLEAN_STYLE=$'{}'",
            git_clean_style
        )?;
        writeln!(
            out,
            "  typeset -g __CAILOXO_GIT_DIRTY_STYLE=$'{}'",
            git_dirty_style
        )?;
    }
    writeln!(out, "  typeset -g __CAILOXO_RESET=$'{}'", reset)?;
    writeln!(out, "  typeset -g __CAILOXO_OS_TAIL=$'{}'", os_tail)?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_PATH_SEP_CLEAN=$'{}'",
        path_sep_clean
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_PATH_SEP_DIRTY=$'{}'",
        path_sep_dirty
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_PATH_SEP_LAST=$'{}'",
        path_sep_last
    )?;
    if git.is_some() {
        writeln!(
            out,
            "  typeset -g __CAILOXO_GIT_SEP_CLEAN=$'{}'",
            git_sep_clean
        )?;
        writeln!(
            out,
            "  typeset -g __CAILOXO_GIT_SEP_DIRTY=$'{}'",
            git_sep_dirty
        )?;
    }
    writeln!(
        out,
        "  typeset -g __CAILOXO_PROMPT_OK_STYLE=$'{}'",
        prompt_ok_style
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_PROMPT_ERROR_STYLE=$'{}'",
        prompt_error_style
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_TRANSIENT_OK_STYLE=$'{}'",
        transient_ok_style
    )?;
    writeln!(
        out,
        "  typeset -g __CAILOXO_TRANSIENT_ERROR_STYLE=$'{}'",
        transient_error_style
    )?;
    Ok(())
}

fn zsh_template_conditionals(features: &Features) -> String {
    if !features.if_status {
        return String::new();
    }
    r#"    local before rest body after then_part else_part
    while [[ $tpl == *'{{ if status }}'* ]]; do
      before=${tpl%%'{{ if status }}'*}
      rest=${tpl#*'{{ if status }}'}
      body=${rest%%'{{ end }}'*}
      after=${rest#*'{{ end }}'}
      if [[ $body == *'{{ else }}'* ]]; then
        then_part=${body%%'{{ else }}'*}
        else_part=${body#*'{{ else }}'}
      else
        then_part=$body
        else_part=
      fi
      if [[ -n $git_status ]]; then
        tpl="${before}${then_part}${after}"
      else
        tpl="${before}${else_part}${after}"
      fi
    done
"#
    .to_string()
}

fn zsh_template_replacements(features: &Features) -> String {
    let vars = [
        (features.var_icon, "icon", "os_icon"),
        (features.var_path, "path", "render_path"),
        (features.var_home_icon, "home_icon", "home_icon"),
        (features.var_folder_icon, "folder_icon", "folder_icon"),
        (features.var_status, "status", "git_status"),
        (features.var_branch_icon, "branch_icon", "branch_icon"),
        (features.var_upstream_icon, "upstream_icon", "upstream_icon"),
        (features.var_upstream, "upstream", "upstream"),
        (features.var_upstream_url, "upstream_url", "upstream_url"),
        (features.var_branch, "branch", "branch"),
    ];
    let mut out = String::new();
    for (enabled, name, shell_var) in vars {
        if enabled {
            writeln!(
                out,
                "    tpl=${{tpl//\\{{\\{{ {name} \\}}\\}}/${{{shell_var}}}}}"
            )
            .unwrap();
            writeln!(
                out,
                "    tpl=${{tpl//\\{{\\{{{name}\\}}\\}}/${{{shell_var}}}}}"
            )
            .unwrap();
        }
    }
    out
}

fn zsh_style_locals(features: &Features) -> String {
    if features.tags.is_empty() {
        return String::new();
    }
    let names = features
        .tags
        .iter()
        .map(|tag| format!("{}_on {}_off", tag.zsh_name, tag.zsh_name))
        .collect::<Vec<_>>()
        .join(" ");
    format!("    local {names}\n")
}

fn zsh_style_init(features: &Features) -> String {
    let mut out = String::new();
    for tag in &features.tags {
        writeln!(
            out,
            "    {0}_on=$(__cailoxo_pansi $'{1}')\n    {0}_off=$(__cailoxo_pansi $'{2}')",
            tag.zsh_name, tag.on, tag.off
        )
        .unwrap();
    }
    out
}

fn zsh_style_replacements(features: &Features) -> String {
    let mut out = String::new();
    for tag in &features.tags {
        let close = tag.close.replace('/', "\\/");
        writeln!(
            out,
            "    text=${{text//'{}'/${}_on}}",
            tag.open, tag.zsh_name
        )
        .unwrap();
        writeln!(out, "    text=${{text//'{}'/${}_off}}", close, tag.zsh_name).unwrap();
    }
    out
}

fn zsh_plain_replacements(features: &Features) -> String {
    let mut out = String::new();
    for tag in &features.tags {
        let close = tag.close.replace('/', "\\/");
        writeln!(out, "    text=${{text//'{}'/}}", tag.open).unwrap();
        writeln!(out, "    text=${{text//'{}'/}}", close).unwrap();
    }
    out
}

fn write_body_no_git(out: &mut String, transient_enabled: bool, features: &Features) -> Result<()> {
    let os_cases = zsh_os_icon_cases()?;
    let os_unknown = zsh_os_unknown_icon()?;
    let template_replacements = zsh_template_replacements(features);
    let style_locals = zsh_style_locals(features);
    let style_init = zsh_style_init(features);
    let style_replacements = zsh_style_replacements(features);
    let plain_replacements = zsh_plain_replacements(features);
    let git_root_function = if features.gitdir_format {
        r#"  __cailoxo_git_root_name() {
    local root
    root=$(git rev-parse --show-toplevel 2>/dev/null) || return
    print -r -- "${root:t}"
  }

"#
    } else {
        ""
    };
    let git_root_local = if features.gitdir_format {
        " git_root_name"
    } else {
        ""
    };
    let git_root_set = if features.gitdir_format {
        "    git_root_name=$(__cailoxo_git_root_name)\n"
    } else {
        ""
    };
    let git_root_arg = if features.gitdir_format {
        " \"$git_root_name\""
    } else {
        ""
    };
    let body = r#"
  __cailoxo_pansi() {
    print -nr -- "%{${1}%}"
  }

  __cailoxo_style_template() {
    local text=$1
__CAILOXO_STYLE_LOCALS____CAILOXO_STYLE_INIT____CAILOXO_STYLE_REPLACEMENTS__
    print -r -- "$text"
  }

  __cailoxo_plain_template() {
    local text=$1
__CAILOXO_PLAIN_REPLACEMENTS__
    print -r -- "$text"
  }

  __cailoxo_url_escape() {
    local value=${1//\\//}
    value=${value//\%/%25}
    value=${value// /%20}
    value=${value//\#/%23}
    value=${value//\?/%3F}
    value=${value//\;/%3B}
    print -r -- "$value"
  }

  __cailoxo_file_url() {
    local path=$(__cailoxo_url_escape "$PWD") host=${HOST:-$(hostname 2>/dev/null)}
    if [[ $path == [[:alpha:]]:/* ]]; then
      print -r -- "file:///$path"
    else
      print -r -- "file://$host$path"
    fi
  }

  __cailoxo_osc7() {
    (( __CAILOXO_OSC7 )) || return
    local url=$(__cailoxo_file_url)
    [[ -n $url ]] && __cailoxo_pansi $'\e]7;'"$url"$'\e\\'
  }

  __cailoxo_path_url_start() {
    (( __CAILOXO_PATH_URL )) || return
    local url=$(__cailoxo_file_url)
    [[ -n $url ]] && __cailoxo_pansi $'\e]8;;'"$url"$'\e\\'
  }

  __cailoxo_path_url_end() {
    (( __CAILOXO_PATH_URL )) || return
    __cailoxo_pansi $'\e]8;;\e\\'
  }

  __cailoxo_format_part() {
    local value=$1 format=$2
    [[ -n $format ]] || { print -r -- "$value"; return }
    [[ $format == *%s* ]] || { print -r -- "$value"; return }
    print -r -- "${format//\%s/$value}"
  }

  __cailoxo_format_path() {
    local input=$1 git_root=$2 prefix= rest absolute=0
    if [[ $input == '~' ]]; then
      __cailoxo_format_part "$input" "$__CAILOXO_EDGE_FORMAT"
      return
    elif [[ $input == '~/'* ]]; then
      prefix='~'
      rest=${input#\~/}
    elif [[ $input == '…/'* ]]; then
      prefix='…'
      rest=${input#…/}
    elif [[ $input == /* ]]; then
      absolute=1
      rest=${input#/}
    else
      rest=$input
    fi
    local -a raw_parts parts out
    raw_parts=("${(@s:/:)rest}")
    local part i
    for part in "${raw_parts[@]}"; do
      [[ -n $part ]] && parts+=("$part")
    done
    if (( absolute )) && (( ${#parts} == 0 )); then
      __cailoxo_format_part / "$__CAILOXO_EDGE_FORMAT"
      return
    fi
    if [[ -n $prefix ]]; then
      if [[ $prefix == '…' ]]; then out+=("$prefix"); else out+=("$(__cailoxo_format_part "$prefix" "$__CAILOXO_EDGE_FORMAT")"); fi
    fi
    for (( i = 1; i <= ${#parts}; i++ )); do
      part=$parts[$i]
      if [[ -n $git_root && $part == $git_root ]]; then
        part=$(__cailoxo_format_part "$part" "$__CAILOXO_GITDIR_FORMAT")
      fi
      if (( i == ${#parts} || (i == 1 && ${#out} == 0) )); then
        part=$(__cailoxo_format_part "$part" "$__CAILOXO_EDGE_FORMAT")
      fi
      out+=("$part")
    done
    local joined=${(j:/:)out}
    (( absolute )) && joined="/$joined"
    print -r -- "$joined"
  }

__CAILOXO_GIT_ROOT_FUNCTION__
  __cailoxo_os_icon() {
    local id=unknown
    if [[ -r /etc/os-release ]]; then
      local line
      while IFS='=' read -r key value; do
        [[ $key == ID ]] || continue
        id=${value//\"/}
        break
      done < /etc/os-release
    elif [[ $OSTYPE == darwin* ]]; then
      id=macos
    elif [[ $OSTYPE == msys* || $OSTYPE == cygwin* ]]; then
      id=windows
    fi
    case ${id:l} in
__CAILOXO_OS_CASES__      *) print -r -- __CAILOXO_OS_UNKNOWN__ ;;
    esac
  }

  __cailoxo_apply_template() {
    local tpl=$1
__CAILOXO_TEMPLATE_REPLACEMENTS__
    print -r -- $tpl
  }

  __cailoxo_path_template() {
    local tpl=$__CAILOXO_PATH_TEMPLATE
    local before=${tpl%%'{{ if home }}'*}
    local rest=${tpl#*'{{ if home }}'}
    if [[ $rest == $tpl ]]; then
      __cailoxo_apply_template "$tpl"
      return
    fi
    local home_part=${rest%%'{{ else }}'*}
    rest=${rest#*'{{ else }}'}
    local other_part=${rest%%'{{ end }}'*}
    local after=${rest#*'{{ end }}'}
    if [[ $is_home == 1 ]]; then
      __cailoxo_apply_template "${before}${home_part}${after}"
    else
      __cailoxo_apply_template "${before}${other_part}${after}"
    fi
  }

  __cailoxo_shorten_path() {
    local budget=$1 display
    if [[ $PWD == $HOME ]]; then display='~'; elif [[ $PWD == $HOME/* ]]; then display="~${PWD#$HOME}"; else display=$PWD; fi
    if (( ${#display} <= budget )); then print -r -- $display; return; fi
    local prefix rest
    if [[ $display == ~/* ]]; then prefix='~'; rest=${display#\~/}; elif [[ $display == /* ]]; then prefix=''; rest=${display#/}; else prefix=''; rest=$display; fi
    local -a parts
    parts=(${(s:/:)rest})
    local count=${#parts}
    if (( count == 0 )); then print -r -- $display; return; fi
    local current=$parts[-1]
    if (( budget <= 4 )); then print -r -- "…/$current"; return; fi
    local candidate
    if (( count >= 2 )); then
      candidate="${prefix:+$prefix/}…/$parts[-2]/$current"
      if (( ${#candidate} <= budget )); then print -r -- $candidate; return; fi
    fi
    candidate="${prefix:+$prefix/}…/$current"
    if (( ${#candidate} <= budget )); then print -r -- $candidate; return; fi
    print -r -- "…/$current"
  }

  __cailoxo_render_full_prompt() {
    local last_status=${1:-$?}
    local os_icon render_path home_icon folder_icon is_home__CAILOXO_GIT_ROOT_LOCAL__
    os_icon=$(__cailoxo_os_icon)
    home_icon=$__CAILOXO_HOME_ICON
    folder_icon=$__CAILOXO_FOLDER_ICON
    [[ $PWD == $HOME ]] && is_home=1 || is_home=0
    local path_text os_text os_plain fixed budget prompt_style
__CAILOXO_GIT_ROOT_SET__
    os_text=$(__cailoxo_apply_template "$__CAILOXO_OS_TEMPLATE")
    local columns=${COLUMNS:-80}
    (( columns > 0 )) || columns=80
    os_plain=$(__cailoxo_plain_template "$os_text")
    fixed=$(( ${#os_plain} + ${#__CAILOXO_HOME_ICON} + ${#__CAILOXO_FOLDER_ICON} + 10 ))
    budget=$(( columns - fixed ))
    (( budget < 1 )) && budget=1
    render_path=$(__cailoxo_format_path "$(__cailoxo_shorten_path $budget)"__CAILOXO_GIT_ROOT_ARG__)
    path_text=$(__cailoxo_path_template)
    local first second
    first="$(__cailoxo_osc7)$(__cailoxo_pansi "$__CAILOXO_OS_STYLE")$(__cailoxo_style_template "$os_text")$(__cailoxo_pansi "$__CAILOXO_RESET")$__CAILOXO_OS_TAIL"
    first+="$(__cailoxo_pansi "$__CAILOXO_PATH_STYLE")$(__cailoxo_path_url_start)$(__cailoxo_style_template "$path_text")$(__cailoxo_path_url_end)$(__cailoxo_pansi "$__CAILOXO_RESET")$__CAILOXO_PATH_SEP_LAST"
    if (( last_status == 0 )); then prompt_style=$__CAILOXO_PROMPT_OK_STYLE; else prompt_style=$__CAILOXO_PROMPT_ERROR_STYLE; fi
    second="$(__cailoxo_pansi "$prompt_style")$(__cailoxo_style_template "$__CAILOXO_PROMPT_TEMPLATE")$(__cailoxo_pansi "$__CAILOXO_RESET")"
    (( __CAILOXO_FINAL_SPACE )) && second+=' '
    print -r -- $first$'\n'$second
  }

  __cailoxo_render_transient_prompt() {
    local last_status=${1:-$?} prompt_style
    if (( last_status == 0 )); then prompt_style=$__CAILOXO_TRANSIENT_OK_STYLE; else prompt_style=$__CAILOXO_TRANSIENT_ERROR_STYLE; fi
    print -r -- "$(__cailoxo_pansi "$prompt_style")$(__cailoxo_style_template "$__CAILOXO_TRANSIENT_TEMPLATE")$(__cailoxo_pansi "$__CAILOXO_RESET")"
  }

  __cailoxo_precmd() {
    typeset -g __CAILOXO_LAST_STATUS=$?
    setopt prompt_subst
    PROMPT='$(__cailoxo_render_full_prompt $__CAILOXO_LAST_STATUS)'
    RPROMPT=
  }

  __cailoxo_winch() {
    zle 2>/dev/null || return
    zle .reset-prompt 2>/dev/null || return
    zle -R 2>/dev/null || true
  }
"#;
    out.push_str(
        &body
            .replace("__CAILOXO_OS_CASES__", &os_cases)
            .replace("__CAILOXO_OS_UNKNOWN__", &os_unknown)
            .replace("__CAILOXO_TEMPLATE_REPLACEMENTS__", &template_replacements)
            .replace("__CAILOXO_STYLE_LOCALS__", &style_locals)
            .replace("__CAILOXO_STYLE_INIT__", &style_init)
            .replace("__CAILOXO_STYLE_REPLACEMENTS__", &style_replacements)
            .replace("__CAILOXO_PLAIN_REPLACEMENTS__", &plain_replacements)
            .replace("__CAILOXO_GIT_ROOT_FUNCTION__", git_root_function)
            .replace("__CAILOXO_GIT_ROOT_LOCAL__", git_root_local)
            .replace("__CAILOXO_GIT_ROOT_SET__", git_root_set)
            .replace("__CAILOXO_GIT_ROOT_ARG__", git_root_arg),
    );

    if transient_enabled {
        out.push_str(
            r#"
  __cailoxo_zle_line_finish() {
    setopt prompt_subst
    PROMPT='$(__cailoxo_render_transient_prompt $__CAILOXO_LAST_STATUS)'
    RPROMPT=
    zle .reset-prompt
  }

  zle -N zle-line-finish __cailoxo_zle_line_finish
"#,
        );
    }

    out.push_str(
        r#"  add-zsh-hook precmd __cailoxo_precmd
  TRAPWINCH() { __cailoxo_winch }
  __cailoxo_precmd
"#,
    );

    Ok(())
}

fn write_body(
    out: &mut String,
    git: Option<&Span>,
    transient_enabled: bool,
    features: &Features,
) -> Result<()> {
    let Some(git) = git else {
        return write_body_no_git(out, transient_enabled, features);
    };
    let status_cases = if features.git_status {
        zsh_status_template_cases(git)?
    } else {
        String::new()
    };
    let os_cases = zsh_os_icon_cases()?;
    let os_unknown = zsh_os_unknown_icon()?;
    let upstream_cases = if features.git_upstream_icon {
        zsh_upstream_icon_cases()?
    } else {
        String::new()
    };
    let template_conditionals = zsh_template_conditionals(features);
    let template_replacements = zsh_template_replacements(features);
    let style_locals = zsh_style_locals(features);
    let style_init = zsh_style_init(features);
    let style_replacements = zsh_style_replacements(features);
    let plain_replacements = zsh_plain_replacements(features);
    let body = r#"
  __cailoxo_pansi() {
    print -nr -- "%{${1}%}"
  }

  __cailoxo_style_template() {
    local text=$1
__CAILOXO_STYLE_LOCALS____CAILOXO_STYLE_INIT____CAILOXO_STYLE_REPLACEMENTS__
    print -r -- "$text"
  }

  __cailoxo_plain_template() {
    local text=$1
__CAILOXO_PLAIN_REPLACEMENTS__
    print -r -- "$text"
  }

  __cailoxo_url_escape() {
    local value=${1//\\//}
    value=${value//\%/%25}
    value=${value// /%20}
    value=${value//\#/%23}
    value=${value//\?/%3F}
    value=${value//\;/%3B}
    print -r -- "$value"
  }

  __cailoxo_file_url() {
    local path=$(__cailoxo_url_escape "$PWD") host=${HOST:-$(hostname 2>/dev/null)}
    if [[ $path == [[:alpha:]]:/* ]]; then
      print -r -- "file:///$path"
    else
      print -r -- "file://$host$path"
    fi
  }

  __cailoxo_osc7() {
    (( __CAILOXO_OSC7 )) || return
    local url=$(__cailoxo_file_url)
    [[ -n $url ]] && __cailoxo_pansi $'\e]7;'"$url"$'\e\\'
  }

  __cailoxo_path_url_start() {
    (( __CAILOXO_PATH_URL )) || return
    local url=$(__cailoxo_file_url)
    [[ -n $url ]] && __cailoxo_pansi $'\e]8;;'"$url"$'\e\\'
  }

  __cailoxo_path_url_end() {
    (( __CAILOXO_PATH_URL )) || return
    __cailoxo_pansi $'\e]8;;\e\\'
  }

  __cailoxo_format_part() {
    local value=$1 format=$2
    [[ -n $format ]] || { print -r -- "$value"; return }
    [[ $format == *%s* ]] || { print -r -- "$value"; return }
    print -r -- "${format//\%s/$value}"
  }

  __cailoxo_git_root_name() {
    local root
    root=$(git rev-parse --show-toplevel 2>/dev/null) || return
    print -r -- "${root:t}"
  }

  __cailoxo_format_path() {
    local input=$1 git_root=$2 prefix= rest absolute=0
    if [[ $input == '~' ]]; then
      __cailoxo_format_part "$input" "$__CAILOXO_EDGE_FORMAT"
      return
    elif [[ $input == '~/'* ]]; then
      prefix='~'
      rest=${input#\~/}
    elif [[ $input == '…/'* ]]; then
      prefix='…'
      rest=${input#…/}
    elif [[ $input == /* ]]; then
      absolute=1
      rest=${input#/}
    else
      rest=$input
    fi

    local -a raw_parts parts out
    raw_parts=("${(@s:/:)rest}")
    local part
    for part in "${raw_parts[@]}"; do
      [[ -n $part ]] && parts+=("$part")
    done

    if (( absolute )) && (( ${#parts} == 0 )); then
      __cailoxo_format_part / "$__CAILOXO_EDGE_FORMAT"
      return
    fi

    if [[ -n $prefix ]]; then
      if [[ $prefix == '…' ]]; then
        out+=("$prefix")
      else
        out+=("$(__cailoxo_format_part "$prefix" "$__CAILOXO_EDGE_FORMAT")")
      fi
    fi

    local i raw
    for (( i = 1; i <= ${#parts}; i++ )); do
      raw=$parts[$i]
      part=$raw
      if [[ -n $git_root && $raw == $git_root ]]; then
        part=$(__cailoxo_format_part "$part" "$__CAILOXO_GITDIR_FORMAT")
      fi
      if (( i == ${#parts} || (i == 1 && ${#out} == 0) )); then
        part=$(__cailoxo_format_part "$part" "$__CAILOXO_EDGE_FORMAT")
      fi
      out+=("$part")
    done

    local joined=${(j:/:)out}
    (( absolute )) && joined="/$joined"
    print -r -- "$joined"
  }

  __cailoxo_os_icon() {
    local id=unknown
    if [[ -r /etc/os-release ]]; then
      local line
      while IFS='=' read -r key value; do
        [[ $key == ID ]] || continue
        id=${value//\"/}
        break
      done < /etc/os-release
    elif [[ $OSTYPE == darwin* ]]; then
      id=macos
    elif [[ $OSTYPE == msys* || $OSTYPE == cygwin* ]]; then
      id=windows
    fi

    case ${id:l} in
__CAILOXO_OS_CASES__      *) print -r -- __CAILOXO_OS_UNKNOWN__ ;;
    esac
  }

  __cailoxo_upstream_provider() {
    local url=${1:l}
    if [[ $url == *github.com* ]]; then print -r -- github
    elif [[ $url == *gitlab.com* ]]; then print -r -- gitlab
    elif [[ $url == *bitbucket.org* ]]; then print -r -- bitbucket
    elif [[ $url == *codeberg.org* ]]; then print -r -- codeberg
    elif [[ $url == *gitea* ]]; then print -r -- gitea
    elif [[ $url == *dev.azure.com* || $url == *visualstudio.com* ]]; then print -r -- azure_devops
    fi
  }

  __cailoxo_clean_git_url() {
    local url=$1 rest host path
    [[ -n $url ]] || return
    url=${url%/}
    url=${url%.git}

    if [[ $url == http://* || $url == https://* ]]; then
      print -r -- "$url"
      return
    fi

    if [[ $url == git@ssh.dev.azure.com:v3/* ]]; then
      path=${url#git@ssh.dev.azure.com:v3/}
      local -a parts
      parts=(${(s:/:)path})
      if (( ${#parts} >= 3 )); then
        print -r -- "https://dev.azure.com/$parts[1]/$parts[2]/_git/$parts[3]"
        return
      fi
    fi

    if [[ $url == *://* ]]; then
      rest=${url#*://}
      rest=${rest#*@}
      host=${rest%%/*}
      path=${rest#*/}
      host=${host%%:*}
      if [[ $host == ssh.dev.azure.com && $path == v3/* ]]; then
        local -a parts
        parts=(${(s:/:)path})
        if (( ${#parts} >= 4 )); then
          print -r -- "https://dev.azure.com/$parts[2]/$parts[3]/_git/$parts[4]"
          return
        fi
      fi
      [[ $path != $rest && -n $host && -n $path ]] && print -r -- "https://$host/$path"
      return
    fi

    if [[ $url == *@*:* ]]; then
      rest=${url#*@}
      host=${rest%%:*}
      path=${rest#*:}
      [[ -n $host && -n $path ]] && print -r -- "https://$host/$path"
      return
    fi

    if [[ $url == *:* ]]; then
      host=${url%%:*}
      path=${url#*:}
      [[ -n $host && -n $path ]] && print -r -- "https://$host/$path"
      return
    fi
  }

  __cailoxo_git_url_start() {
    (( __CAILOXO_GIT_URL )) || return
    [[ -n $upstream_url ]] || return
    __cailoxo_pansi $'\e]8;;'"$upstream_url"$'\e\\'
  }

  __cailoxo_git_url_end() {
    (( __CAILOXO_GIT_URL )) || return
    [[ -n $upstream_url ]] || return
    __cailoxo_pansi $'\e]8;;\e\\'
  }

  __cailoxo_upstream_icon() {
    (( __CAILOXO_FETCH_UPSTREAM_ICON )) || return
    local remote url provider upstream_icon=
    remote=$(__cailoxo_remote_name)
    url=$(git config --get "remote.$remote.url" 2>/dev/null) || return
    provider=$(__cailoxo_upstream_provider "$url")
    case $provider in
__CAILOXO_UPSTREAM_CASES__    esac
    print -r -- "$upstream_icon"
  }

  __cailoxo_remote_name() {
    local remote
    remote=$(git config --get "branch.$branch.remote" 2>/dev/null) || remote=origin
    [[ -n $remote ]] || remote=origin
    print -r -- "$remote"
  }

  __cailoxo_now_s() {
    zmodload zsh/datetime 2>/dev/null
    print -r -- "${EPOCHSECONDS:-$(date +%s 2>/dev/null || print -r -- 0)}"
  }

  __cailoxo_start_fetch() {
    (( __CAILOXO_FETCH_REMOTE )) || return
    [[ -n $branch ]] || return
    [[ -n $__CAILOXO_FETCH_FD ]] && return

    local remote root key now upstream_ref before fd
    remote=$(__cailoxo_remote_name)
    root=$(git rev-parse --show-toplevel 2>/dev/null) || return
    [[ -n $root && -n $remote ]] || return
    key="$root|$remote"
    now=$(__cailoxo_now_s)
    if [[ $__CAILOXO_FETCH_LAST_KEY == "$key" && $(( now - __CAILOXO_FETCH_LAST_START_S )) -lt $__CAILOXO_FETCH_REMOTE_INTERVAL_S ]]; then
      return
    fi

    upstream_ref=$(git rev-parse --symbolic-full-name "${branch}@{upstream}" 2>/dev/null) || upstream_ref=
    [[ -n $upstream_ref ]] || return
    before=$(git rev-parse --verify "$upstream_ref" 2>/dev/null) || before=
    zmodload zsh/system 2>/dev/null || return
    __CAILOXO_FETCH_LAST_KEY=$key
    __CAILOXO_FETCH_LAST_START_S=$now
    sysopen -r -o cloexec -u fd <(
      cd "$root" || exit 0
      if (( __CAILOXO_FETCH_REMOTE_TIMEOUT_S > 0 )) && command -v timeout >/dev/null 2>&1; then
        command timeout "${__CAILOXO_FETCH_REMOTE_TIMEOUT_S}s" git fetch --quiet --no-tags "$remote" >/dev/null 2>&1
      else
        command git fetch --quiet --no-tags "$remote" >/dev/null 2>&1
      fi
      local after
      after=$(git rev-parse --verify "$upstream_ref" 2>/dev/null) || after=
      [[ -n $after && $before != $after ]] && print -rn -- changed
    ) || return
    __CAILOXO_FETCH_FD=$fd
    zle -F $fd __cailoxo_fetch_done 2>/dev/null || {
      exec {fd}>&-
      __CAILOXO_FETCH_FD=
    }
  }

  __cailoxo_fetch_done() {
    local fd=$1 buf=
    [[ -n $fd ]] || fd=$__CAILOXO_FETCH_FD
    [[ -n $fd ]] || return
    zle -F $fd 2>/dev/null || true
    sysread -i $fd 'buf[$#buf+1]' 2>/dev/null || true
    exec {fd}>&- 2>/dev/null || true
    __CAILOXO_FETCH_FD=
    [[ $buf == *changed* ]] && __cailoxo_winch
  }

  __cailoxo_apply_template() {
    local tpl=$1
__CAILOXO_TEMPLATE_CONDITIONALS____CAILOXO_TEMPLATE_REPLACEMENTS__
    print -r -- $tpl
  }

  __cailoxo_path_template() {
    local tpl=$__CAILOXO_PATH_TEMPLATE
    local before=${tpl%%'{{ if home }}'*}
    local rest=${tpl#*'{{ if home }}'}
    if [[ $rest == $tpl ]]; then
      __cailoxo_apply_template "$tpl"
      return
    fi
    local home_part=${rest%%'{{ else }}'*}
    rest=${rest#*'{{ else }}'}
    local other_part=${rest%%'{{ end }}'*}
    local after=${rest#*'{{ end }}'}
    if [[ $is_home == 1 ]]; then
      __cailoxo_apply_template "${before}${home_part}${after}"
    else
      __cailoxo_apply_template "${before}${other_part}${after}"
    fi
  }

  __cailoxo_shorten_path() {
    local budget=$1
    local display
    if [[ $PWD == $HOME ]]; then
      display='~'
    elif [[ $PWD == $HOME/* ]]; then
      display="~${PWD#$HOME}"
    else
      display=$PWD
    fi

    if (( ${#display} <= budget )); then
      print -r -- $display
      return
    fi

    local prefix rest
    if [[ $display == ~/* ]]; then
      prefix='~'
      rest=${display#\~/}
    elif [[ $display == /* ]]; then
      prefix=''
      rest=${display#/}
    else
      prefix=''
      rest=$display
    fi

    local -a parts
    parts=(${(s:/:)rest})
    local count=${#parts}
    if (( count == 0 )); then
      print -r -- $display
      return
    fi

    local current=$parts[-1]
    if (( budget <= 4 )); then
      print -r -- "…/$current"
      return
    fi

    local candidate
    if (( count >= 2 )); then
      candidate="${prefix:+$prefix/}…/$parts[-2]/$current"
      if (( ${#candidate} <= budget )); then
        print -r -- $candidate
        return
      fi
    fi

    candidate="${prefix:+$prefix/}…/$current"
    if (( ${#candidate} <= budget )); then
      print -r -- $candidate
      return
    fi

    print -r -- "…/$current"
  }

  __cailoxo_status_item() {
    local name=$1 template count
    case $name in
__CAILOXO_STATUS_CASES__    esac
    if [[ $name == action ]]; then
      [[ -n $action ]] || return
      print -r -- ${template//\{\{ action \}\}/$action}
      return
    fi
    (( count > 0 )) || return
    print -r -- ${template//\{\{ count \}\}/$count}
  }

  __cailoxo_git_file() {
    git rev-parse --git-path "$1" 2>/dev/null
  }

  __cailoxo_git_action_with_progress() {
    local action=$1 dir=$2 next= last=
    [[ -r $dir/msgnum ]] && next=$(<$dir/msgnum)
    [[ -r $dir/end ]] && last=$(<$dir/end)
    [[ -r $dir/next ]] && next=$(<$dir/next)
    [[ -r $dir/last ]] && last=$(<$dir/last)
    if [[ -n $next && -n $last ]]; then
      print -r -- "$action $next/$last"
    else
      print -r -- "$action"
    fi
  }

  __cailoxo_git_action() {
    local path
    path=$(__cailoxo_git_file rebase-merge)
    if [[ -d $path ]]; then
      if [[ -e $path/interactive ]]; then
        __cailoxo_git_action_with_progress rebase-i "$path"
      else
        __cailoxo_git_action_with_progress rebase-m "$path"
      fi
      return
    fi

    path=$(__cailoxo_git_file rebase-apply)
    if [[ -d $path ]]; then
      if [[ -e $path/rebasing ]]; then
        __cailoxo_git_action_with_progress rebase "$path"
      elif [[ -e $path/applying ]]; then
        __cailoxo_git_action_with_progress am "$path"
      else
        __cailoxo_git_action_with_progress am/rebase "$path"
      fi
      return
    fi

    path=$(__cailoxo_git_file MERGE_HEAD)
    [[ -e $path ]] && { print -r -- merge; return; }
    path=$(__cailoxo_git_file REVERT_HEAD)
    if [[ -e $path ]]; then
      path=$(__cailoxo_git_file sequencer)
      [[ -d $path ]] && print -r -- revert-seq || print -r -- revert
      return
    fi
    path=$(__cailoxo_git_file CHERRY_PICK_HEAD)
    if [[ -e $path ]]; then
      path=$(__cailoxo_git_file sequencer)
      [[ -d $path ]] && print -r -- cherry-seq || print -r -- cherry
      return
    fi
    path=$(__cailoxo_git_file BISECT_LOG)
    [[ -e $path ]] && print -r -- bisect
  }

  __cailoxo_git_info() {
    branch=
    git_status=
    git_dirty=0
    upstream=
    upstream_icon=
    upstream_url=
    local inside
    inside=$(git rev-parse --is-inside-work-tree 2>/dev/null) || return
    [[ $inside == true ]] || return

    branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null) || branch=
    [[ -n $branch ]] || return
    if (( __CAILOXO_FETCH_UPSTREAM_ICON || __CAILOXO_GIT_URL )); then
      local remote
      remote=$(__cailoxo_remote_name)
      upstream_url=$(git config --get "remote.$remote.url" 2>/dev/null) || upstream_url=
      upstream=$(__cailoxo_upstream_provider "$upstream_url")
      if (( __CAILOXO_FETCH_UPSTREAM_ICON )); then
        upstream_icon=$(__cailoxo_upstream_icon)
      fi
      upstream_url=$(__cailoxo_clean_git_url "$upstream_url")
    fi
    __cailoxo_start_fetch
    (( __CAILOXO_GIT_STATUS )) || return

    local ahead=0 behind=0 action= conflicted=0 untracked=0 modified=0 staged=0 renamed=0 deleted=0 stashed=0
    local out line code x y
    out=$(git status --porcelain=v1 2>/dev/null)
    while IFS= read -r line; do
      [[ -n $line ]] || continue
      code=${line[1,2]}
      if [[ $code == '??' ]]; then
        (( untracked++ ))
        continue
      fi
      case $code in
        DD|AU|UD|UA|DU|AA|UU) (( conflicted++ )); continue ;;
      esac
      x=${line[1,1]}
      y=${line[2,2]}
      [[ $x == R ]] && (( renamed++ ))
      [[ $x == D || $y == D ]] && (( deleted++ ))
      [[ $x != ' ' && $x != R && $x != D ]] && (( staged++ ))
      [[ $y != ' ' && $y != D ]] && (( modified++ ))
    done <<< $out

    ahead=$(git rev-list --count '@{upstream}..HEAD' 2>/dev/null || print -r -- 0)
    behind=$(git rev-list --count 'HEAD..@{upstream}' 2>/dev/null || print -r -- 0)
    action=$(__cailoxo_git_action)
    stashed=$(git stash list 2>/dev/null | wc -l | tr -d ' ')
    (( conflicted || untracked || modified || staged || renamed || deleted )) && git_dirty=1

    local -a items
    local item name
    for name in behind ahead stashed action conflicted staged modified untracked renamed deleted; do
      item=$(__cailoxo_status_item $name)
      [[ -n $item ]] && items+=("$item")
    done
    git_status=
    for item in "${items[@]}"; do
      if [[ -z $git_status ]]; then
        git_status=$item
      else
        git_status+="${__CAILOXO_STATUS_SEPARATOR}$item"
      fi
    done
  }

  __cailoxo_render_full_prompt() {
    local last_status=${1:-$?}
    local os_icon render_path home_icon folder_icon is_home branch branch_icon upstream upstream_icon upstream_url git_status git_dirty git_root_name
    os_icon=$(__cailoxo_os_icon)
    home_icon=$__CAILOXO_HOME_ICON
    folder_icon=$__CAILOXO_FOLDER_ICON
    branch_icon=$__CAILOXO_BRANCH_ICON
    [[ $PWD == $HOME ]] && is_home=1 || is_home=0
    __cailoxo_git_info
    git_root_name=$(__cailoxo_git_root_name)

    local git_text path_text os_text git_plain os_plain fixed budget git_style path_sep git_sep
    git_text=$(__cailoxo_apply_template "$__CAILOXO_GIT_TEMPLATE")
    if [[ -z $branch ]]; then
      git_text=
    fi

    os_text=$(__cailoxo_apply_template "$__CAILOXO_OS_TEMPLATE")
    # Budget applies only to {{ path }}. Account for icons, spaces and edge glyphs.
    local columns=${COLUMNS:-80}
    (( columns > 0 )) || columns=80
    os_plain=$(__cailoxo_plain_template "$os_text")
    git_plain=$(__cailoxo_plain_template "$git_text")
    fixed=$(( ${#os_plain} + ${#git_plain} + ${#__CAILOXO_HOME_ICON} + ${#__CAILOXO_FOLDER_ICON} + 10 ))
    budget=$(( columns - fixed ))
    (( budget < 1 )) && budget=1
    render_path=$(__cailoxo_format_path "$(__cailoxo_shorten_path $budget)" "$git_root_name")
    path_text=$(__cailoxo_path_template)

    if [[ -z $git_text ]]; then
      path_sep=$__CAILOXO_PATH_SEP_LAST
      git_sep=
    elif (( git_dirty )); then
      git_style=$__CAILOXO_GIT_DIRTY_STYLE
      path_sep=$__CAILOXO_PATH_SEP_DIRTY
      git_sep=$__CAILOXO_GIT_SEP_DIRTY
    else
      git_style=$__CAILOXO_GIT_CLEAN_STYLE
      path_sep=$__CAILOXO_PATH_SEP_CLEAN
      git_sep=$__CAILOXO_GIT_SEP_CLEAN
    fi

    local first second prompt_style
    first="$(__cailoxo_osc7)$(__cailoxo_pansi "$__CAILOXO_OS_STYLE")$(__cailoxo_style_template "$os_text")$(__cailoxo_pansi "$__CAILOXO_RESET")$__CAILOXO_OS_TAIL"
    first+="$(__cailoxo_pansi "$__CAILOXO_PATH_STYLE")$(__cailoxo_path_url_start)$(__cailoxo_style_template "$path_text")$(__cailoxo_path_url_end)$(__cailoxo_pansi "$__CAILOXO_RESET")$path_sep"
    if [[ -n $git_text ]]; then
      first+="$(__cailoxo_pansi "$git_style")$(__cailoxo_git_url_start)$(__cailoxo_style_template "$git_text")$(__cailoxo_git_url_end)$(__cailoxo_pansi "$__CAILOXO_RESET")$git_sep"
    fi

    if (( last_status == 0 )); then
      prompt_style=$__CAILOXO_PROMPT_OK_STYLE
    else
      prompt_style=$__CAILOXO_PROMPT_ERROR_STYLE
    fi
    second="$(__cailoxo_pansi "$prompt_style")$(__cailoxo_style_template "$__CAILOXO_PROMPT_TEMPLATE")$(__cailoxo_pansi "$__CAILOXO_RESET")"
    (( __CAILOXO_FINAL_SPACE )) && second+=' '
    print -r -- $first$'\n'$second
  }

  __cailoxo_render_transient_prompt() {
    local last_status=${1:-$?}
    local prompt_style
    if (( last_status == 0 )); then
      prompt_style=$__CAILOXO_TRANSIENT_OK_STYLE
    else
      prompt_style=$__CAILOXO_TRANSIENT_ERROR_STYLE
    fi
    print -r -- "$(__cailoxo_pansi "$prompt_style")$(__cailoxo_style_template "$__CAILOXO_TRANSIENT_TEMPLATE")$(__cailoxo_pansi "$__CAILOXO_RESET")"
  }

  __cailoxo_precmd() {
    typeset -g __CAILOXO_LAST_STATUS=$?
    setopt prompt_subst
    PROMPT='$(__cailoxo_render_full_prompt $__CAILOXO_LAST_STATUS)'
    RPROMPT=
  }

  __cailoxo_winch() {
    zle 2>/dev/null || return
    zle .reset-prompt 2>/dev/null || return
    zle -R 2>/dev/null || true
  }
"#;
    out.push_str(
        &body
            .replace("__CAILOXO_STATUS_CASES__", &status_cases)
            .replace("__CAILOXO_OS_CASES__", &os_cases)
            .replace("__CAILOXO_OS_UNKNOWN__", &os_unknown)
            .replace("__CAILOXO_UPSTREAM_CASES__", &upstream_cases)
            .replace("__CAILOXO_TEMPLATE_CONDITIONALS__", &template_conditionals)
            .replace("__CAILOXO_TEMPLATE_REPLACEMENTS__", &template_replacements)
            .replace("__CAILOXO_STYLE_LOCALS__", &style_locals)
            .replace("__CAILOXO_STYLE_INIT__", &style_init)
            .replace("__CAILOXO_STYLE_REPLACEMENTS__", &style_replacements)
            .replace("__CAILOXO_PLAIN_REPLACEMENTS__", &plain_replacements),
    );

    if transient_enabled {
        out.push_str(
            r#"
  __cailoxo_zle_line_finish() {
    setopt prompt_subst
    PROMPT='$(__cailoxo_render_transient_prompt $__CAILOXO_LAST_STATUS)'
    RPROMPT=
    zle .reset-prompt
  }

  zle -N zle-line-finish __cailoxo_zle_line_finish
"#,
        );
    }

    out.push_str(
        r#"  add-zsh-hook precmd __cailoxo_precmd
  TRAPWINCH() { __cailoxo_winch }
  __cailoxo_precmd
"#,
    );

    Ok(())
}

fn edge(
    glyph: Option<&str>,
    fg: Option<&String>,
    bg: Option<&String>,
    invert: bool,
) -> Result<String> {
    let Some(glyph) = glyph else {
        return Ok(String::new());
    };
    let (fg, bg) = if invert { (bg, fg) } else { (fg, bg) };
    let mut style = String::new();
    style.push_str(&color_escape(fg, false)?);
    style.push_str(&color_escape(bg, true)?);
    Ok(format!(
        "%{{{}%}}{}%{{{}%}}",
        zsh_prompt_ansi(&style),
        glyph.replace('%', "%%"),
        zsh_prompt_ansi(RESET)
    ))
}

fn zsh_ansi(input: &str) -> String {
    zsh_prompt_ansi(input)
}

#[cfg(test)]
mod tests {
    use crate::config::Config;

    use super::*;

    #[test]
    fn generates_zsh() {
        let config = Config::parse(include_str!("../../cailoxo.toml")).unwrap();
        let script = generate(&config).unwrap();
        assert!(script.contains("__cailoxo_git_info"));
        assert!(script.contains("__cailoxo_style_template"));
        assert!(script.contains("__cailoxo_format_path"));
        assert!(script.contains("__cailoxo_path_url_start"));
        assert!(script.contains("__cailoxo_git_url_start"));
        assert!(script.contains("typeset -gi __CAILOXO_OSC7=1"));
        assert!(script.contains("__cailoxo_start_fetch"));
        assert!(script.contains("__cailoxo_git_action"));
        assert!(script.contains("action) template='{{ action }}'"));
        assert!(script.contains("for name in behind ahead stashed action conflicted"));
        assert!(script.contains("text=${text//'<b>'/$bold_on}"));
        assert!(script.contains(
            "(( conflicted || untracked || modified || staged || renamed || deleted )) && git_dirty=1"
        ));
        assert!(!script.contains("(( ${#items} > 0 )) && git_dirty=1"));
        assert!(script.contains("zle-line-finish"));
    }

    #[test]
    fn zsh_no_git_span_omits_git_runtime() {
        let config = Config::parse(NO_GIT_CONFIG).unwrap();
        let script = generate(&config).unwrap();
        assert!(!script.contains("__cailoxo_git_info"));
        assert!(!script.contains("git status"));
        assert!(!script.contains("git rev-parse"));
        assert!(!script.contains("__cailoxo_start_fetch"));
    }

    #[test]
    fn zsh_no_git_span_keeps_gitdir_probe_when_format_used() {
        let config = Config::parse(NO_GIT_GITDIR_CONFIG).unwrap();
        let script = generate(&config).unwrap();
        assert!(script.contains("__cailoxo_git_root_name"));
        assert!(script.contains("git rev-parse --show-toplevel"));
        assert!(!script.contains("__cailoxo_git_info"));
        assert!(!script.contains("git status"));
    }

    #[test]
    fn zsh_omits_unused_style_tags() {
        let config = Config::parse(include_str!("../../cailoxo.toml")).unwrap();
        let script = generate(&config).unwrap();
        assert!(script.contains("bold_on"));
        assert!(script.contains("italic_on"));
        assert!(!script.contains("strike_on"));
        assert!(!script.contains("'<s>'"));
    }

    #[test]
    fn zsh_omits_upstream_icon_cases_when_unused() {
        let config = Config::parse(BRANCH_ONLY_GIT_CONFIG).unwrap();
        let script = generate(&config).unwrap();
        assert!(!script.contains("github) upstream_icon"));
        assert!(!script.contains("gitlab) upstream_icon"));
    }

    const NO_GIT_CONFIG: &str = r#"
version = 1
final_space = true

[[line]]
[[line.span]]
type = "os"
template = " {{ icon }} "
background = "7"
tail = ">"

[[line.span]]
type = "path"
template = " {{ path }} "
background = "4"
separator = ">"

[[line]]
[[line.span]]
type = "text"
template = ">"
success_foreground = "2"
error_foreground = "1"
"#;

    const BRANCH_ONLY_GIT_CONFIG: &str = r#"
version = 1

[[line]]
[[line.span]]
type = "os"
template = " {{ icon }} "
background = "7"
tail = ">"

[[line.span]]
type = "path"
template = " {{ path }} "
background = "4"
separator = ">"

[[line.span]]
type = "git"
template = " {{ branch }} "
background = "2"
separator = ">"

[line.span.settings]
fetch_upstream_icon = true

[[line]]
[[line.span]]
type = "text"
template = ">"
"#;

    const NO_GIT_GITDIR_CONFIG: &str = r#"
version = 1

[[line]]
[[line.span]]
type = "os"
template = " {{ icon }} "
background = "7"
tail = ">"

[[line.span]]
type = "path"
template = " {{ path }} "
background = "4"
separator = ">"

[line.span.settings]
gitdir_format = "<b>%s</b>"

[[line]]
[[line.span]]
type = "text"
template = ">"
"#;
}
