#!/bin/bash

# omarchy:summary=Generate themed config files from Omarchy templates
# omarchy:hidden=true

TEMPLATES_DIR="$OMARCHY_PATH/default/themed"
USER_TEMPLATES_DIR="$HOME/.config/omarchy/themed"
NEXT_THEME_DIR="$HOME/.local/state/omarchy/current/next-theme"
COLORS_FILE="$NEXT_THEME_DIR/colors.toml"

# Theme switching waits on this script before the transition starts, so it
# avoids a process per value: helpers return through REPLY rather than $(...),
# and one awk pass computes the mixes and renders every template.

declare -A THEME_COLORS

MIX_TOKEN_PATTERN='\{\{[[:space:]]*mix(_strip|_rgb)?[[:space:]]+[A-Za-z0-9_]+[[:space:]]+[A-Za-z0-9_]+[[:space:]]+[0-9]+([.][0-9]+)?%?[[:space:]]*\}\}'
GRADIENT_TOKEN_PATTERN='\{\{[[:space:]]*(hypr_gradient|gradient_start|shell_gradient)[[:space:]]+[^}]+[[:space:]]*\}\}'

# Convert hex color to decimal RGB (e.g., "#1e1e2e" -> "30,30,46")
hex_to_rgb() {
  local hex="${1#\#}"
  printf -v REPLY "%d,%d,%d" "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
}

trim() {
  local value="$1"

  value="${value#"${value%%[![:space:]]*}"}"
  value="${value%"${value##*[![:space:]]}"}"
  REPLY="$value"
}

resolve_theme_ref() {
  local ref="$1"
  local fallback="${2:-}"

  if [[ -n ${THEME_COLORS[$ref]+_} ]]; then
    REPLY="${THEME_COLORS[$ref]}"
  elif [[ -n $fallback && -n ${THEME_COLORS[$fallback]+_} ]]; then
    REPLY="${THEME_COLORS[$fallback]}"
  elif [[ -n $fallback ]]; then
    REPLY="$fallback"
  else
    REPLY="$ref"
  fi
}

resolve_gradient_color() {
  trim "$1"
  if [[ -n ${THEME_COLORS[$REPLY]+_} ]]; then
    REPLY="${THEME_COLORS[$REPLY]}"
  fi
}

parse_gradient() {
  local spec="$1"
  local part
  local -a parts

  GRADIENT_COLORS=()
  GRADIENT_ANGLE=""

  read -ra parts <<<"$spec"
  for part in "${parts[@]}"; do
    [[ -n $part ]] || continue

    if [[ $part =~ ^-?[0-9]+([.][0-9]+)?deg$ ]]; then
      GRADIENT_ANGLE="${part%deg}"
    else
      resolve_gradient_color "$part"
      GRADIENT_COLORS+=("$REPLY")
    fi
  done
}

color_to_shell_hex() {
  local color r g b

  resolve_gradient_color "$1"
  color="$REPLY"

  if [[ $color =~ ^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$ ]]; then
    REPLY="#${color:1:6}"
  elif [[ $color =~ ^[Rr][Gg][Bb][Aa]?\(([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?\)$ ]]; then
    REPLY="#${BASH_REMATCH[1]}"
  elif [[ $color =~ ^[Rr][Gg][Bb][Aa]?\(([0-9]+),([0-9]+),([0-9]+)(,[0-9.]+)?\)$ ]]; then
    r=${BASH_REMATCH[1]}
    g=${BASH_REMATCH[2]}
    b=${BASH_REMATCH[3]}
    (( r > 255 )) && r=255
    (( g > 255 )) && g=255
    (( b > 255 )) && b=255
    printf -v REPLY "#%02x%02x%02x" "$r" "$g" "$b"
  elif [[ $color =~ ^0x[0-9A-Fa-f]{8}$ ]]; then
    REPLY="#${color:4:6}"
  else
    REPLY="$color"
  fi
}

hypr_gradient_value() {
  local spec index value

  resolve_theme_ref "$1" "${2:-}"
  spec="$REPLY"
  parse_gradient "$spec"

  if (( ${#GRADIENT_COLORS[@]} == 0 )); then
    REPLY="\"$spec\""
  elif (( ${#GRADIENT_COLORS[@]} == 1 )); then
    REPLY="\"${GRADIENT_COLORS[0]}\""
  else
    value='{ colors = {'
    for index in "${!GRADIENT_COLORS[@]}"; do
      (( index > 0 )) && value+=','
      value+=" \"${GRADIENT_COLORS[$index]}\""
    done
    value+=' }'
    [[ -n $GRADIENT_ANGLE ]] && value+=", angle = $GRADIENT_ANGLE"
    REPLY="$value }"
  fi
}

gradient_start_value() {
  local spec

  resolve_theme_ref "$1" "${2:-}"
  spec="$REPLY"
  parse_gradient "$spec"

  if (( ${#GRADIENT_COLORS[@]} == 0 )); then
    color_to_shell_hex "$spec"
  else
    color_to_shell_hex "${GRADIENT_COLORS[0]}"
  fi
}

shell_gradient_value() {
  local spec index value=""

  resolve_theme_ref "$1" "${2:-}"
  spec="$REPLY"
  parse_gradient "$spec"

  if (( ${#GRADIENT_COLORS[@]} == 0 )); then
    REPLY="$spec"
    return
  fi

  for index in "${!GRADIENT_COLORS[@]}"; do
    (( index > 0 )) && value+=" "
    value+="${GRADIENT_COLORS[$index]}"
  done

  [[ -n $GRADIENT_ANGLE ]] && value+=" ${GRADIENT_ANGLE}deg"
  REPLY="$value"
}

# The value table holds token, unit separator, value lines for the renderer to
# substitute. Tokens may contain tabs, so a tab cannot delimit them.
add_template_value() {
  local key="$1"
  local value="$2"

  printf '{{ %s }}\037%s\n{{ %s_strip }}\037%s\n' "$key" "$value" "$key" "${value#\#}" >>"$value_table"

  if [[ $value =~ ^#[0-9A-Fa-f]{6}$ ]]; then
    hex_to_rgb "$value"
    printf '{{ %s_rgb }}\037%s\n' "$key" "$REPLY" >>"$value_table"
  fi
}

# A mix of two hex colors goes to the renderer as mix, token, function, start,
# end and amount, split by unit separators, since awk computes it.
add_mix_value() {
  local token="$1"
  local content fn start_key end_key amount start end

  content="${token#\{\{}"
  content="${content%\}\}}"
  read -r fn start_key end_key amount <<<"$content"

  start="${THEME_COLORS[$start_key]:-}"
  end="${THEME_COLORS[$end_key]:-}"

  [[ $start =~ ^#[0-9A-Fa-f]{6}$ && $end =~ ^#[0-9A-Fa-f]{6}$ ]] || return
  [[ $fn == "mix" || $fn == "mix_strip" || $fn == "mix_rgb" ]] || return

  printf 'mix\037%s\037%s\037%s\037%s\037%s\n' "$token" "$fn" "$start" "$end" "$amount" >>"$mix_table"
}

add_gradient_function_value() {
  local token="$1"
  local content fn key fallback

  content="${token#\{\{}"
  content="${content%\}\}}"
  read -r fn key fallback <<<"$content"

  case "$fn" in
    hypr_gradient)
      hypr_gradient_value "$key" "$fallback"
      ;;
    gradient_start)
      gradient_start_value "$key" "$fallback"
      ;;
    shell_gradient)
      shell_gradient_value "$key" "$fallback"
      ;;
    *)
      return
      ;;
  esac

  printf '%s\037%s\n' "$token" "$REPLY" >>"$value_table"
}

# Scan every template once for function tokens, instead of a grep per template
# per function family.
add_function_values() {
  local token
  local -A seen=()

  (( ${#template_files[@]} > 0 )) || return

  while IFS= read -r token; do
    [[ -n ${seen[$token]:-} ]] && continue
    seen[$token]=1

    if [[ $token =~ ^$MIX_TOKEN_PATTERN$ ]]; then
      add_mix_value "$token"
    else
      add_gradient_function_value "$token"
    fi
  done < <(grep -hEo "$MIX_TOKEN_PATTERN|$GRADIENT_TOKEN_PATTERN" "${template_files[@]}" 2>/dev/null || true)
}

strip_shell_section_header() {
  local section="$1"
  local file="$2"

  awk -v section="$section" '
    BEGIN { skipping_header = 0 }
    /^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
      if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
        skipping_header = 1
        next
      }
    }
    { print }
  ' "$file"
}

apply_shell_section_override() {
  local override="$1"
  local section tmp body

  [[ -f $NEXT_THEME_DIR/shell.toml ]] || return
  [[ -f $override ]] || return

  section=$(basename "$override")
  section="${section#shell.}"
  section="${section%.toml}"
  [[ $section =~ ^[A-Za-z0-9_-]+$ ]] || return

  tmp=$(mktemp)
  body=$(mktemp)

  strip_shell_section_header "$section" "$override" >"$body"

  awk -v section="$section" -v body="$body" '
    function emit_override() {
      if (emitted) return
      print "[" section "]"
      while ((getline line < body) > 0) print line
      close(body)
      emitted = 1
    }

    /^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
      if (in_section) {
        emit_override()
        in_section = 0
      }
      if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
        in_section = 1
        next
      }
    }

    !in_section { print }

    END {
      if (in_section || !emitted) {
        if (NR > 0) print ""
        emit_override()
      }
    }
  ' "$NEXT_THEME_DIR/shell.toml" >"$tmp"

  mv "$tmp" "$NEXT_THEME_DIR/shell.toml"
  rm "$body"
}

apply_shell_section_overrides() {
  local override

  shopt -s nullglob
  for override in "$NEXT_THEME_DIR"/shell.*.toml; do
    [[ $(basename "$override") != "shell.toml" ]] || continue
    apply_shell_section_override "$override"
  done
}

# Substitute every template in one awk process. Each {{ ... }} span is looked
# up whole, as the per-template sed script used to match it. A mix is rounded
# with awk's floating point exactly as before, so no generated color moves.
render_templates() {
  local tpl filename
  local -a pairs=()

  # Process user templates first, then built-in templates (user overrides
  # built-in). Don't overwrite configs the theme already ships.
  for tpl in "${template_files[@]}"; do
    filename=${tpl##*/}
    filename=${filename%.tpl}
    if [[ ! -f $NEXT_THEME_DIR/$filename && -z ${rendered[$filename]:-} ]]; then
      rendered[$filename]=1
      pairs+=("$tpl" "$NEXT_THEME_DIR/$filename")
    fi
  done

  (( ${#pairs[@]} > 0 )) || return

  awk -v value_table="$value_table" -v mix_table="$mix_table" '
    function hex_value(char) {
      return index("0123456789abcdef", tolower(char)) - 1
    }

    function hex_pair_to_int(hex, idx) {
      return hex_value(substr(hex, idx, 1)) * 16 + hex_value(substr(hex, idx + 1, 1))
    }

    function mix_color(start, end, amount,    start_r, start_g, start_b, end_r, end_g, end_b, red, green, blue) {
      start = substr(start, 2)
      end = substr(end, 2)

      if (amount ~ /%$/) {
        sub(/%$/, "", amount)
        amount = amount / 100
      } else {
        amount += 0
        if (amount > 1) amount = amount / 100
      }

      if (amount < 0) amount = 0
      if (amount > 1) amount = 1

      start_r = hex_pair_to_int(start, 1)
      start_g = hex_pair_to_int(start, 3)
      start_b = hex_pair_to_int(start, 5)
      end_r = hex_pair_to_int(end, 1)
      end_g = hex_pair_to_int(end, 3)
      end_b = hex_pair_to_int(end, 5)

      red = int(start_r * (1 - amount) + end_r * amount + 0.5)
      green = int(start_g * (1 - amount) + end_g * amount + 0.5)
      blue = int(start_b * (1 - amount) + end_b * amount + 0.5)

      return sprintf("#%02x%02x%02x", red, green, blue)
    }

    function render(line,    out, open, span, token) {
      out = ""
      while ((open = index(line, "{{")) > 0) {
        span = index(substr(line, open + 2), "}}")
        if (span == 0) break

        token = substr(line, open, span + 3)
        if (token in values) {
          out = out substr(line, 1, open - 1) values[token]
          line = substr(line, open + length(token))
        } else {
          out = out substr(line, 1, open)
          line = substr(line, open + 1)
        }
      }
      return out line
    }

    BEGIN {
      while ((getline row < value_table) > 0) {
        sep = index(row, "\037")
        values[substr(row, 1, sep - 1)] = substr(row, sep + 1)
      }
      close(value_table)

      while ((getline row < mix_table) > 0) {
        split(row, field, "\037")
        value = mix_color(field[4], field[5], field[6])
        if (field[3] == "mix_strip") {
          value = substr(value, 2)
        } else if (field[3] == "mix_rgb") {
          value = sprintf("%d,%d,%d", hex_pair_to_int(substr(value, 2), 1), hex_pair_to_int(substr(value, 2), 3), hex_pair_to_int(substr(value, 2), 5))
        }
        values[field[2]] = value
      }
      close(mix_table)

      for (i = 1; i < ARGC; i += 2) {
        template = ARGV[i]
        output = ARGV[i + 1]
        # RT holds the terminator each line was read with, so a template that
        # ends without a newline renders without one, as sed left it.
        while ((getline line < template) > 0) printf "%s%s", render(line), RT > output
        close(template)
        close(output)
        # A template with no lines still leaves its output file behind.
        printf "" >> output
        close(output)
      }
      exit
    }
  ' "${pairs[@]}"
}

# Only generate dynamic templates for themes with a colors.toml definition
if [[ -f $COLORS_FILE ]]; then
  value_table=$(mktemp)
  mix_table=$(mktemp)
  declare -A rendered=()

  shopt -s nullglob
  template_files=("$USER_TEMPLATES_DIR"/*.tpl "$TEMPLATES_DIR"/*.tpl)

  # Parsing and alias/fallback resolution (legacy colorN names, derived
  # shades, mode/theme_type) is shared with every other colors.toml consumer.
  while IFS=$'\t' read -r key value; do
    THEME_COLORS[$key]="$value"
  done < <(omarchy-theme-color --file "$COLORS_FILE" --all)

  for key in "${!THEME_COLORS[@]}"; do
    add_template_value "$key" "${THEME_COLORS[$key]}"
  done

  add_function_values
  render_templates

  rm "$value_table" "$mix_table"
fi

apply_shell_section_overrides
