use std::fmt::Write;

use anyhow::Result;

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

use super::shared::{
    ansi_pair, color_escape, git_branch_icon, layout, nu_os_icon_conditions, nu_status_templates,
    nu_string, nu_upstream_icons, status_separator,
};

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 os_style = ansi_pair(os.foreground.as_ref(), os.background.as_ref())?;
    let path_style = ansi_pair(path.foreground.as_ref(), path.background.as_ref())?;
    let git_clean_style = ansi_pair(git.foreground.as_ref(), git.background.as_ref())?;
    let git_dirty_style = ansi_pair(
        git.foreground.as_ref(),
        git.dirty_background.as_ref().or(git.background.as_ref()),
    )?;
    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.background.as_ref(),
        path.invert_separator,
    )?;
    let path_sep_dirty = edge(
        path.separator.as_deref(),
        path.background.as_ref(),
        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.separator.as_deref(),
        git.background.as_ref(),
        None,
        git.invert_separator,
    )?;
    let git_sep_dirty = edge(
        git.separator.as_deref(),
        git.dirty_background.as_ref().or(git.background.as_ref()),
        None,
        git.invert_separator,
    )?;
    let prompt_ok_style = ansi_pair(prompt.success_foreground.as_ref(), None)?;
    let prompt_error_style = 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 = 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 = 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: nu")?;
    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,
    )?;
    write_body(&mut script, git, transient_enabled)?;
    Ok(script)
}

#[allow(clippy::too_many_arguments)]
fn write_consts(
    out: &mut String,
    os: &Span,
    path: &Span,
    git: &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,
) -> Result<()> {
    let min_dirs = path.setting_str("min_dirs").unwrap_or("1");
    let min_dirs = min_dirs.parse::<usize>().unwrap_or(1);
    let branch_icon = git_branch_icon(git)?;
    let fetch_upstream_icon = git.setting_bool("fetch_upstream_icon").unwrap_or(false);
    let fetch_remote = git.setting_bool("fetch_remote").unwrap_or(false);
    let fetch_remote_interval_ms = git
        .settings
        .get("fetch_remote_interval_ms")
        .and_then(|value| value.as_integer())
        .unwrap_or(60_000)
        .max(0);

    writeln!(
        out,
        "const CAILOXO_OS_TEMPLATE = {}",
        nu_string(&os.template)
    )?;
    writeln!(
        out,
        "const CAILOXO_PATH_TEMPLATE = {}",
        nu_string(&path.template)
    )?;
    writeln!(
        out,
        "const CAILOXO_GIT_TEMPLATE = {}",
        nu_string(&git.template)
    )?;
    writeln!(
        out,
        "const CAILOXO_PROMPT_TEMPLATE = {}",
        nu_string(&prompt.template)
    )?;
    writeln!(
        out,
        "const CAILOXO_TRANSIENT_TEMPLATE = {}",
        nu_string(transient_template)
    )?;
    writeln!(
        out,
        "const CAILOXO_HOME_ICON = {}",
        nu_string(path.setting_str("home_icon").unwrap_or("~"))
    )?;
    writeln!(
        out,
        "const CAILOXO_FOLDER_ICON = {}",
        nu_string(path.setting_str("folder_icon").unwrap_or("…"))
    )?;
    writeln!(
        out,
        "const CAILOXO_EDGE_FORMAT = {}",
        nu_string(path.setting_str("edge_format").unwrap_or(""))
    )?;
    writeln!(
        out,
        "const CAILOXO_GITDIR_FORMAT = {}",
        nu_string(path.setting_str("gitdir_format").unwrap_or(""))
    )?;
    writeln!(
        out,
        "const CAILOXO_BRANCH_ICON = {}",
        nu_string(&branch_icon)
    )?;
    writeln!(
        out,
        "const CAILOXO_STATUS_SEPARATOR = {}",
        nu_string(status_separator(git))
    )?;
    writeln!(
        out,
        "const CAILOXO_FETCH_UPSTREAM_ICON = {}",
        if fetch_upstream_icon { "true" } else { "false" }
    )?;
    writeln!(
        out,
        "const CAILOXO_FETCH_REMOTE = {}",
        if fetch_remote { "true" } else { "false" }
    )?;
    writeln!(
        out,
        "const CAILOXO_FETCH_REMOTE_INTERVAL_MS = {fetch_remote_interval_ms}"
    )?;
    writeln!(out, "const CAILOXO_MIN_DIRS = {min_dirs}")?;
    writeln!(
        out,
        "const CAILOXO_FINAL_SPACE = {}",
        if final_space { "true" } else { "false" }
    )?;
    writeln!(out, "const CAILOXO_OS_STYLE = {}", nu_string(os_style))?;
    writeln!(out, "const CAILOXO_PATH_STYLE = {}", nu_string(path_style))?;
    writeln!(
        out,
        "const CAILOXO_GIT_CLEAN_STYLE = {}",
        nu_string(git_clean_style)
    )?;
    writeln!(
        out,
        "const CAILOXO_GIT_DIRTY_STYLE = {}",
        nu_string(git_dirty_style)
    )?;
    writeln!(out, "const CAILOXO_RESET = {}", nu_string(reset))?;
    writeln!(out, "const CAILOXO_OS_TAIL = {}", nu_string(os_tail))?;
    writeln!(
        out,
        "const CAILOXO_PATH_SEP_CLEAN = {}",
        nu_string(path_sep_clean)
    )?;
    writeln!(
        out,
        "const CAILOXO_PATH_SEP_DIRTY = {}",
        nu_string(path_sep_dirty)
    )?;
    writeln!(
        out,
        "const CAILOXO_PATH_SEP_LAST = {}",
        nu_string(path_sep_last)
    )?;
    writeln!(
        out,
        "const CAILOXO_GIT_SEP_CLEAN = {}",
        nu_string(git_sep_clean)
    )?;
    writeln!(
        out,
        "const CAILOXO_GIT_SEP_DIRTY = {}",
        nu_string(git_sep_dirty)
    )?;
    writeln!(
        out,
        "const CAILOXO_PROMPT_OK_STYLE = {}",
        nu_string(prompt_ok_style)
    )?;
    writeln!(
        out,
        "const CAILOXO_PROMPT_ERROR_STYLE = {}",
        nu_string(prompt_error_style)
    )?;
    writeln!(
        out,
        "const CAILOXO_TRANSIENT_OK_STYLE = {}",
        nu_string(transient_ok_style)
    )?;
    writeln!(
        out,
        "const CAILOXO_TRANSIENT_ERROR_STYLE = {}",
        nu_string(transient_error_style)
    )?;
    Ok(())
}

fn write_body(out: &mut String, git: &Span, transient_enabled: bool) -> Result<()> {
    let templates = nu_status_templates(git)?;
    let os_icon_conditions = nu_os_icon_conditions()?;
    let upstream_icons = nu_upstream_icons()?;
    writeln!(out, "const CAILOXO_STATUS_TEMPLATES = {templates}")?;
    writeln!(out, "const CAILOXO_UPSTREAM_ICONS = {upstream_icons}")?;
    let body = r#"
def cailoxo-os-icon [] {
  let os = (sys host | get name | str downcase)
  __CAILOXO_OS_ICON_CONDITIONS__
}

def cailoxo-upstream-provider [url: string] {
  let normalized = ($url | str downcase)
  if ($normalized | str contains "github.com") { "github" } else if ($normalized | str contains "gitlab.com") { "gitlab" } else if ($normalized | str contains "bitbucket.org") { "bitbucket" } else if ($normalized | str contains "codeberg.org") { "codeberg" } else if ($normalized | str contains "gitea") { "gitea" } else if ($normalized | str contains "dev.azure.com") or ($normalized | str contains "visualstudio.com") { "azure_devops" } else { "" }
}

def cailoxo-remote-name [branch: string] {
  let remote = (git config --get $"branch.($branch).remote" | complete)
  if $remote.exit_code == 0 and ($remote.stdout | str trim) != "" { $remote.stdout | str trim } else { "origin" }
}

def cailoxo-start-fetch [branch: string] {
  if not $CAILOXO_FETCH_REMOTE { return }
  let remote = (cailoxo-remote-name $branch)
  let root_out = (git rev-parse --show-toplevel | complete)
  if $root_out.exit_code != 0 { return }
  let root = ($root_out.stdout | str trim)
  if $root == "" or $remote == "" { return }

  let key = $"($root)|($remote)"
  let now = (date now | into int)
  let last_key = ($env.CAILOXO_FETCH_LAST_KEY? | default "")
  let last_start = (($env.CAILOXO_FETCH_LAST_START_NS? | default 0) | into int)
  let interval = ($CAILOXO_FETCH_REMOTE_INTERVAL_MS * 1000000)
  if $last_key == $key and (($now - $last_start) < $interval) { return }
  $env.CAILOXO_FETCH_LAST_KEY = $key
  $env.CAILOXO_FETCH_LAST_START_NS = $now

  let repo_root = $root
  let remote_name = $remote
  job spawn --description "cailoxo git fetch" {||
    cd $repo_root
    git fetch --quiet --no-tags $remote_name | complete | ignore
  } | ignore
}

def cailoxo-upstream-info [branch: string] {
  if not $CAILOXO_FETCH_UPSTREAM_ICON { return {upstream: "", upstream_icon: "", upstream_url: ""} }
  let remote_name = (cailoxo-remote-name $branch)
  let url_out = (git config --get $"remote.($remote_name).url" | complete)
  let upstream_url = if $url_out.exit_code == 0 { $url_out.stdout | str trim } else { "" }
  let upstream = (cailoxo-upstream-provider $upstream_url)
  let upstream_icon = if $upstream != "" and ($upstream in ($CAILOXO_UPSTREAM_ICONS | columns)) { $CAILOXO_UPSTREAM_ICONS | get $upstream } else { "" }
  {upstream: $upstream, upstream_icon: $upstream_icon, upstream_url: $upstream_url}
}

def cailoxo-apply-template [template: string, vars: record] {
  let resolved = if not ($template | str contains "{{ if status }}") {
    $template
  } else {
    let parts = ($template | split row "{{ if status }}")
    let before = ($parts | get 0)
    let rest = ($parts | get 1)
    let end_parts = ($rest | split row "{{ end }}")
    let body = ($end_parts | get 0)
    let after = ($end_parts | skip 1 | str join "{{ end }}")
    let branches = ($body | split row "{{ else }}")
    let then_part = ($branches | get 0)
    let else_part = if (($branches | length) > 1) { $branches | skip 1 | str join "{{ else }}" } else { "" }
    let status = ($vars.status? | default "")
    $before + (if $status != "" { $then_part } else { $else_part }) + $after
  }

  $resolved
  | str replace --all "{{ icon }}" ($vars.icon? | default "")
  | str replace --all "{{ path }}" ($vars.path? | default "")
  | str replace --all "{{ home_icon }}" ($vars.home_icon? | default "")
  | str replace --all "{{ folder_icon }}" ($vars.folder_icon? | default "")
  | str replace --all "{{ status }}" ($vars.status? | default "")
  | str replace --all "{{ branch_icon }}" ($vars.branch_icon? | default "")
  | str replace --all "{{ upstream_icon }}" ($vars.upstream_icon? | default "")
  | str replace --all "{{ upstream }}" ($vars.upstream? | default "")
  | str replace --all "{{ upstream_url }}" ($vars.upstream_url? | default "")
  | str replace --all "{{ branch }}" ($vars.branch? | default "")
}

def cailoxo-style-template [text: string] {
  $text
  | str replace --all "<b>" "\u{1b}[1m"
  | str replace --all "</b>" "\u{1b}[22m"
  | str replace --all "<u>" "\u{1b}[4m"
  | str replace --all "</u>" "\u{1b}[24m"
  | str replace --all "<o>" "\u{1b}[53m"
  | str replace --all "</o>" "\u{1b}[55m"
  | str replace --all "<i>" "\u{1b}[3m"
  | str replace --all "</i>" "\u{1b}[23m"
  | str replace --all "<s>" "\u{1b}[9m"
  | str replace --all "</s>" "\u{1b}[29m"
  | str replace --all "<d>" "\u{1b}[2m"
  | str replace --all "</d>" "\u{1b}[22m"
  | str replace --all "<f>" "\u{1b}[5m"
  | str replace --all "</f>" "\u{1b}[25m"
  | str replace --all "<r>" "\u{1b}[7m"
  | str replace --all "</r>" "\u{1b}[27m"
}

def cailoxo-plain-template [text: string] {
  $text
  | str replace --all "<b>" ""
  | str replace --all "</b>" ""
  | str replace --all "<u>" ""
  | str replace --all "</u>" ""
  | str replace --all "<o>" ""
  | str replace --all "</o>" ""
  | str replace --all "<i>" ""
  | str replace --all "</i>" ""
  | str replace --all "<s>" ""
  | str replace --all "</s>" ""
  | str replace --all "<d>" ""
  | str replace --all "</d>" ""
  | str replace --all "<f>" ""
  | str replace --all "</f>" ""
  | str replace --all "<r>" ""
  | str replace --all "</r>" ""
}

def cailoxo-format-part [value: string, format: string] {
  if $format == "" { return $value }
  if ($format | str contains "%s") { return ($format | str replace --all "%s" $value) }
  $value
}

def cailoxo-git-root-name [] {
  let root = (git rev-parse --show-toplevel | complete)
  if $root.exit_code == 0 { $root.stdout | str trim | path basename } else { "" }
}

def cailoxo-normalize-path [path: string] {
  $path | str replace --all '\' '/'
}

def cailoxo-path-separator [] {
  let os = (sys host | get name | str downcase)
  if ($os | str contains "windows") { '\' } else { '/' }
}

def cailoxo-format-path [path: string, git_root: string] {
  let separator = (cailoxo-path-separator)
  let parsed = if $path == "~" {
    {prefix: "~", rest: "", absolute: false}
  } else if ($path | str starts-with "~/") {
    {prefix: "~", rest: ($path | str replace "~/" ""), absolute: false}
  } else if ($path | str starts-with "…/") {
    {prefix: "…", rest: ($path | str replace "…/" ""), absolute: false}
  } else if ($path | str starts-with "/") {
    {prefix: "", rest: ($path | str replace "/" ""), absolute: true}
  } else {
    {prefix: "", rest: $path, absolute: false}
  }

  let parts = ($parsed.rest | split row "/" | where {|part| $part != "" })
  if $parsed.absolute and (($parts | length) == 0) {
    return (cailoxo-format-part $separator $CAILOXO_EDGE_FORMAT)
  }

  mut out = []
  if $parsed.prefix != "" {
    let prefix = if $parsed.prefix == "…" { $parsed.prefix } else { cailoxo-format-part $parsed.prefix $CAILOXO_EDGE_FORMAT }
    $out = ($out | append $prefix)
  }

  let count = ($parts | length)
  for index in 0..<$count {
    let raw = ($parts | get $index)
    mut part = $raw
    if $git_root != "" and $raw == $git_root {
      $part = (cailoxo-format-part $part $CAILOXO_GITDIR_FORMAT)
    }
    if $index == ($count - 1) or ($index == 0 and ($out | length) == 0) {
      $part = (cailoxo-format-part $part $CAILOXO_EDGE_FORMAT)
    }
    $out = ($out | append $part)
  }

  let joined = ($out | str join $separator)
  if $parsed.absolute { $separator + $joined } else { $joined }
}

def cailoxo-path-template [vars: record] {
  let marker = "{{ if home }}"
  if not ($CAILOXO_PATH_TEMPLATE | str contains $marker) {
    cailoxo-apply-template $CAILOXO_PATH_TEMPLATE $vars
  } else {
    let parts = ($CAILOXO_PATH_TEMPLATE | split row $marker)
    let before = ($parts | get 0)
    let rest = ($parts | get 1)
    let branches = ($rest | split row "{{ else }}")
    let home_part = ($branches | get 0)
    let end_parts = (($branches | get 1) | split row "{{ end }}")
    let other_part = ($end_parts | get 0)
    let after = ($end_parts | skip 1 | str join "{{ end }}")
    let body = if $vars.home { $home_part } else { $other_part }
    cailoxo-apply-template ($before + $body + $after) $vars
  }
}

def cailoxo-shorten-path [budget: int] {
  let cwd = (cailoxo-normalize-path (pwd))
  let home = (cailoxo-normalize-path ($nu.home-dir | path expand))
  let display = if ($cwd | str starts-with $home) { "~" + ($cwd | str replace $home "") } else { $cwd }
  if (($display | str length --chars) <= $budget) { return $display }

  let parts = ($display | split row "/" | where {|p| $p != "" })
  let count = ($parts | length)
  if $count == 0 { return $display }
  let current = ($parts | last)
  if $budget <= 4 { return $"…/($current)" }
  let prefix = if ($display | str starts-with "~/") { "~/" } else if ($display | str starts-with "/") { "/" } else { "" }
  if $count >= 2 {
    let parent = ($parts | get (($count - 2)))
    let candidate = $"($prefix)…/($parent)/($current)"
    if (($candidate | str length --chars) <= $budget) { return $candidate }
  }
  let candidate = $"($prefix)…/($current)"
  if (($candidate | str length --chars) <= $budget) { return $candidate }
  $"…/($current)"
}

def cailoxo-status-item [name: string, count: int] {
  if $count <= 0 { return "" }
  let template = ($CAILOXO_STATUS_TEMPLATES | get $name)
  $template | str replace --all "{{ count }}" ($count | into string)
}

def cailoxo-git-info [] {
  let inside = (git rev-parse --is-inside-work-tree | complete)
  if $inside.exit_code != 0 or ($inside.stdout | str trim) != "true" {
    return {branch: "", status: "", dirty: false, upstream: "", upstream_icon: "", upstream_url: ""}
  }

  let branch_out = (git symbolic-ref --quiet --short HEAD | complete)
  let branch = if $branch_out.exit_code == 0 {
    $branch_out.stdout | str trim
  } else {
    (git rev-parse --short HEAD | complete).stdout | str trim
  }
  if $branch == "" { return {branch: "", status: "", dirty: false, upstream: "", upstream_icon: "", upstream_url: ""} }
  let upstream = (cailoxo-upstream-info $branch)
  cailoxo-start-fetch $branch

  mut counts = {ahead: 0, behind: 0, conflicted: 0, untracked: 0, modified: 0, staged: 0, renamed: 0, deleted: 0, stashed: 0}
  let status_out = (git status --porcelain=v1 | complete)
  for line in ($status_out.stdout | lines) {
    if $line == "" { continue }
    let code = ($line | str substring 0..<2)
    if $code == "??" {
      $counts = ($counts | upsert untracked ($counts.untracked + 1))
      continue
    }
    if $code in [DD AU UD UA DU AA UU] {
      $counts = ($counts | upsert conflicted ($counts.conflicted + 1))
      continue
    }
    let x = ($line | str substring 0..<1)
    let y = ($line | str substring 1..<2)
    if $x == "R" { $counts = ($counts | upsert renamed ($counts.renamed + 1)) }
    if $x == "D" or $y == "D" { $counts = ($counts | upsert deleted ($counts.deleted + 1)) }
    if $x != " " and $x != "R" and $x != "D" { $counts = ($counts | upsert staged ($counts.staged + 1)) }
    if $y != " " and $y != "D" { $counts = ($counts | upsert modified ($counts.modified + 1)) }
  }

  let ahead_out = (git rev-list --count '@{upstream}..HEAD' | complete)
  if $ahead_out.exit_code == 0 { $counts = ($counts | upsert ahead (($ahead_out.stdout | str trim | into int) | default 0)) }
  let behind_out = (git rev-list --count 'HEAD..@{upstream}' | complete)
  if $behind_out.exit_code == 0 { $counts = ($counts | upsert behind (($behind_out.stdout | str trim | into int) | default 0)) }
  let stash_out = (git stash list | complete)
  if $stash_out.exit_code == 0 { $counts = ($counts | upsert stashed (($stash_out.stdout | lines | length) | default 0)) }

  mut items = []
  for name in [ahead behind conflicted untracked modified staged renamed deleted stashed] {
    let item = (cailoxo-status-item $name ($counts | get $name))
    if $item != "" { $items = ($items | append $item) }
  }
  {branch: $branch, status: ($items | str join $CAILOXO_STATUS_SEPARATOR), dirty: (($items | length) > 0), upstream: $upstream.upstream, upstream_icon: $upstream.upstream_icon, upstream_url: $upstream.upstream_url}
}

def cailoxo-render-main [] {
  let os_icon = (cailoxo-os-icon)
  let git = (cailoxo-git-info)
  let git_root = (cailoxo-git-root-name)
  let git_vars = {status: $git.status, branch_icon: $CAILOXO_BRANCH_ICON, upstream_icon: $git.upstream_icon, upstream: $git.upstream, upstream_url: $git.upstream_url, branch: $git.branch}
  let git_text = if $git.branch == "" { "" } else { cailoxo-apply-template $CAILOXO_GIT_TEMPLATE $git_vars }
  let os_text = (cailoxo-apply-template $CAILOXO_OS_TEMPLATE {icon: $os_icon})

  let term_width = ((term size).columns | default 80)
  let fixed = (((cailoxo-plain-template $os_text) | str length --chars) + ((cailoxo-plain-template $git_text) | str length --chars) + ($CAILOXO_HOME_ICON | str length --chars) + ($CAILOXO_FOLDER_ICON | str length --chars) + 10)
  let budget = [1, ($term_width - $fixed)] | math max
  let path = (cailoxo-format-path (cailoxo-shorten-path $budget) $git_root)
  let is_home = ((pwd | path expand) == ($nu.home-dir | path expand))
  let path_vars = {path: $path, home: $is_home, home_icon: $CAILOXO_HOME_ICON, folder_icon: $CAILOXO_FOLDER_ICON}
  let path_text = (cailoxo-path-template $path_vars)

  let git_style = if $git.dirty { $CAILOXO_GIT_DIRTY_STYLE } else { $CAILOXO_GIT_CLEAN_STYLE }
  let path_sep = if $git_text == "" { $CAILOXO_PATH_SEP_LAST } else if $git.dirty { $CAILOXO_PATH_SEP_DIRTY } else { $CAILOXO_PATH_SEP_CLEAN }
  let git_sep = if $git.dirty { $CAILOXO_GIT_SEP_DIRTY } else { $CAILOXO_GIT_SEP_CLEAN }
  let first = ($CAILOXO_OS_STYLE + (cailoxo-style-template $os_text) + $CAILOXO_RESET + $CAILOXO_OS_TAIL
    + $CAILOXO_PATH_STYLE + (cailoxo-style-template $path_text) + $CAILOXO_RESET + $path_sep
    + (if $git_text == "" { "" } else { $git_style + (cailoxo-style-template $git_text) + $CAILOXO_RESET + $git_sep }))
  $first + "\n"
}

def cailoxo-render-indicator [] {
  let prompt_style = if (($env.LAST_EXIT_CODE? | default 0) == 0) { $CAILOXO_PROMPT_OK_STYLE } else { $CAILOXO_PROMPT_ERROR_STYLE }
  let suffix = if $CAILOXO_FINAL_SPACE { " " } else { "" }
  $prompt_style + (cailoxo-style-template $CAILOXO_PROMPT_TEMPLATE) + $CAILOXO_RESET + $suffix
}

def cailoxo-render-full [] {
  (cailoxo-render-main) + (cailoxo-render-indicator)
}

def cailoxo-render-transient [] {
  let prompt_style = if (($env.LAST_EXIT_CODE? | default 0) == 0) { $CAILOXO_TRANSIENT_OK_STYLE } else { $CAILOXO_TRANSIENT_ERROR_STYLE }
  $prompt_style + (cailoxo-style-template $CAILOXO_TRANSIENT_TEMPLATE) + $CAILOXO_RESET
}

$env.PROMPT_COMMAND = {|| cailoxo-render-main }
$env.PROMPT_INDICATOR = {|| cailoxo-render-indicator }
$env.PROMPT_COMMAND_RIGHT = ""
$env.PROMPT_INDICATOR_VI_INSERT = ""
$env.PROMPT_INDICATOR_VI_NORMAL = ""
$env.PROMPT_MULTILINE_INDICATOR = ""
"#;
    out.push_str(&body.replace("__CAILOXO_OS_ICON_CONDITIONS__", &os_icon_conditions));

    if transient_enabled {
        out.push_str(
            r#"$env.TRANSIENT_PROMPT_COMMAND = ""
$env.TRANSIENT_PROMPT_INDICATOR = {|| cailoxo-render-transient }
$env.TRANSIENT_PROMPT_COMMAND_RIGHT = ""
$env.TRANSIENT_PROMPT_MULTILINE_INDICATOR = ""
"#,
        );
    }

    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 mut out = String::new();
    let (fg, bg) = if invert { (bg, fg) } else { (fg, bg) };
    out.push_str(&color_escape(fg, false)?);
    out.push_str(&color_escape(bg, true)?);
    out.push_str(glyph);
    out.push_str(RESET);
    Ok(out)
}

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

    use super::*;

    #[test]
    fn generates_nu() {
        let config = Config::parse(include_str!("../../cailoxo.toml")).unwrap();
        let script = generate(&config).unwrap();
        assert!(script.contains("def cailoxo-git-info"));
        assert!(script.contains("def cailoxo-style-template"));
        assert!(script.contains("def cailoxo-format-path"));
        assert!(script.contains("def cailoxo-start-fetch"));
        assert!(script.contains("str replace --all \"<b>\""));
        assert!(script.contains("$env.PROMPT_INDICATOR = {|| cailoxo-render-indicator }"));
        assert!(
            script.contains("$env.TRANSIENT_PROMPT_INDICATOR = {|| cailoxo-render-transient }")
        );
    }
}
