#!/usr/bin/env bash

import \
  "$SHELLOPS_DIR/lib/versions.api.sh" \
  "$SHELLOPS_DIR/lib/manifest.api.sh"

declare -gA __bump_api__CHANNEL_ORDER=(
  [dev]=0
  [alpha]=1
  [beta]=2
  [rc]=3
)

__bump_api__CHANNELS=("dev" "alpha" "beta" "rc")

function plan_bump() {
  local options="$1"
  # shellcheck disable=SC2034
  local -A version
  local current_version planned

  if current_version="$(use_project_manifest)"; then
    planned="$current_version"
  else
    if ! is_valid_semver "$current_version"; then
      error "Invalid version: $current_version"
      return 1
    fi

    parse_version "$current_version" version

    if __bump_api__is_prerelease_bump "$options"; then
      __bump_api__plan_flag_prerelease version "$options"
    elif __bump_api__is_release_bump "$options" || __bump_api__is_releasing_prerelease version "$options"; then
      __bump_api__apply_bump version "$(__bump_api__release_type "$options")"
      __bump_api__clear_prerelease version
    else
      __bump_api__plan_auto_bump version
    fi

    planned="$(__bump_api__format_version version)"
  fi

  __bump_api__ensure_tag_available "$planned" "$options"
}

function use_project_manifest() {
  local git_latest manifest_latest comparison

  git_latest="$(latest_version)"
  manifest_latest="$(manifest_get latest)"

  comparison="$(compare_versions "$git_latest" "$manifest_latest")"
  if ((comparison > 0)); then
    echo "$manifest_latest"
    return 0
  fi

  echo "$git_latest"
  return 1
}

# ── Validation ────────────────────────────────────────────────────────

:<<'DOC'
Validates that no contradictory meta labels are present.

Returns 0 on success, 1 on conflict with a message describing the issue.

Usage: validate_meta_labels "$labels_csv"
DOC
function validate_meta_labels() {
  local labels_csv="$1"
  local meta_count=0
  local found=()

  __bump_api__has_label "$labels_csv" "meta/experimental" && { found+=("meta/experimental"); ((meta_count++)); }
  __bump_api__has_label "$labels_csv" "meta/promote"      && { found+=("meta/promote"); ((meta_count++)); }
  __bump_api__has_label "$labels_csv" "meta/release"      && { found+=("meta/release"); ((meta_count++)); }

  if ((meta_count > 1)); then
    local IFS=","
    error "Contradictory meta labels: ${found[*]}. Only one version-transition label is allowed per PR."
    return 1
  fi
}

# ── Core state machine ────────────────────────────────────────────────

:<<'DOC'
Computes the next version from the current version and PR labels.

Pure function: no side effects, no API calls.

Usage: compute_next_version "$latest" "$labels_csv"

  latest:     current version string (e.g. "1.0.0" or "1.1.0-dev.3")
  labels_csv: comma-separated PR label names

Outputs the next version string.
DOC
function compute_next_version() {
  local latest="$1" labels_csv="$2"
  local -A ver

  validate_meta_labels "$labels_csv" || return 1
  parse_version "$latest" ver || return 1

  local bump_level
  bump_level="$(__bump_api__infer_bump_level "$labels_csv" ver)"

  if [[ -n "${ver[pre_type]}" ]]; then
    __bump_api__plan_prerelease ver "$labels_csv" "$bump_level"
  else
    __bump_api__plan_release ver "$labels_csv" "$bump_level"
  fi

  __bump_api__format_version ver
}

# ── Bump level inference ──────────────────────────────────────────────

function __bump_api__infer_bump_level() {
  local labels_csv="$1"
  local -n __ver_ref__="$2"

  local raw_level="patch"

  if __bump_api__has_any_label "$labels_csv" "crit/breaking" "crit/removal"; then
    raw_level="major"
  elif __bump_api__has_label "$labels_csv" "type/feature"; then
    raw_level="minor"
  fi

  __bump_api__apply_pre10_ceiling "$raw_level" __ver_ref__
}

function __bump_api__apply_pre10_ceiling() {
  local level="$1"
  local -n __ceil_ver__="$2"

  if [[ "$level" == "major" && "${__ceil_ver__[major]}" == "0" ]]; then
    echo "minor"
    return
  fi

  echo "$level"
}

function __bump_api__plan_flag_prerelease() {
  local -n __flag_ver__="$1"
  local options="$2"
  local target_type

  target_type="$(__bump_api__compare_prerelease_types \
    "$(__bump_api__prerelease_type "$options")" \
    "${__flag_ver__[pre_type]}")"

  if [[ "$target_type" == "${__flag_ver__[pre_type]}" ]]; then
    __flag_ver__[pre_inc]="$(( ${__flag_ver__[pre_inc]:-0} + 1 ))"
    return
  fi

  if [[ -z "${__flag_ver__[pre_type]}" ]] && ! __bump_api__is_release_bump "$options"; then
    __flag_ver__[patch]="$(( ${__flag_ver__[patch]:-0} + 1 ))"
  fi

  if __bump_api__is_release_bump "$options"; then
    __bump_api__apply_bump __flag_ver__ "$(__bump_api__release_type "$options")"
  fi

  __flag_ver__[pre_type]="$target_type"
  __flag_ver__[pre_inc]=1
}

function __bump_api__plan_auto_bump() {
  local -n __auto_ver__="$1"

  if [[ -n "${__auto_ver__[pre_type]}" ]]; then
    __auto_ver__[pre_inc]="$(( ${__auto_ver__[pre_inc]:-0} + 1 ))"
    return
  fi

  __auto_ver__[patch]="$(( ${__auto_ver__[patch]:-0} + 1 ))"
}

function __bump_api__compare_prerelease_types() {
  local bump_type="$1" cur_type="$2"
  local bump_precedence cur_precedence

  if [[ "$bump_type" == "$cur_type" ]]; then
    echo "$bump_type"
    return
  fi

  bump_precedence="$(__bump_api__channel_order "$bump_type")"
  cur_precedence="$(__bump_api__channel_order "$cur_type")"

  if ((bump_precedence > cur_precedence)); then
    echo "$bump_type"
    return
  fi

  echo "$cur_type"
}

function __bump_api__channel_order() {
  local channel="$1"

  if [[ -z "$channel" ]]; then
    echo -1
    return
  fi

  echo "${__bump_api__CHANNEL_ORDER[$channel]:--1}"
}

# ── Release planning ──────────────────────────────────────────────────

function __bump_api__plan_release() {
  local -n __rel_ver__="$1"
  local labels_csv="$2" bump_level="$3"

  if __bump_api__has_label "$labels_csv" "meta/experimental"; then
    __bump_api__apply_bump __rel_ver__ "$bump_level"
    __rel_ver__[pre_type]="dev"
    __rel_ver__[pre_inc]=1
    return
  fi

  __bump_api__apply_bump __rel_ver__ "$bump_level"
}

# ── Prerelease planning ──────────────────────────────────────────────

function __bump_api__plan_prerelease() {
  local -n __pre_ver__="$1"
  local labels_csv="$2" bump_level="$3"
  local retargeted=false

  if __bump_api__needs_retarget __pre_ver__ "$bump_level"; then
    __bump_api__retarget __pre_ver__ "$bump_level"
    retargeted=true
  fi

  if __bump_api__has_label "$labels_csv" "meta/release"; then
    __pre_ver__[pre_type]=""
    __pre_ver__[pre_inc]=""
    return
  fi

  if __bump_api__has_label "$labels_csv" "meta/promote"; then
    __bump_api__promote_channel __pre_ver__
    return
  fi

  if [[ "$retargeted" == true ]]; then
    return
  fi

  __pre_ver__[pre_inc]=$(( ${__pre_ver__[pre_inc]:-0} + 1 ))
}

function __bump_api__promote_channel() {
  local -n __prom_ver__="$1"
  local cur_channel="${__prom_ver__[pre_type]}"
  local cur_order="${__bump_api__CHANNEL_ORDER[$cur_channel]:-0}"
  local next_idx=$((cur_order + 1))

  if ((next_idx > 3)); then
    __prom_ver__[pre_type]=""
    __prom_ver__[pre_inc]=""
    return
  fi

  __prom_ver__[pre_type]="${__bump_api__CHANNELS[$next_idx]}"
  __prom_ver__[pre_inc]=1
}

function __bump_api__needs_retarget() {
  local -n __rt_ver__="$1"
  local bump_level="$2"

  local current_target_level
  current_target_level="$(__bump_api__current_target_level __rt_ver__)"

  local bump_rank current_rank
  bump_rank="$(__bump_api__level_rank "$bump_level")"
  current_rank="$(__bump_api__level_rank "$current_target_level")"

  ((bump_rank > current_rank))
}

function __bump_api__retarget() {
  local -n __retarget_ver__="$1"
  local bump_level="$2"

  __retarget_ver__[pre_type]=""
  __retarget_ver__[pre_inc]=""

  __bump_api__reset_to_base __retarget_ver__
  __bump_api__apply_bump __retarget_ver__ "$bump_level"

  __retarget_ver__[pre_type]="dev"
  __retarget_ver__[pre_inc]=1
}

function __bump_api__reset_to_base() {
  local -n __base_ver__="$1"
  local cur_target_level
  cur_target_level="$(__bump_api__current_target_level __base_ver__)"

  case "$cur_target_level" in
    patch) __base_ver__[patch]=$(( ${__base_ver__[patch]:-0} - 1 )) ;;
    minor) __base_ver__[minor]=$(( ${__base_ver__[minor]:-0} - 1 )); __base_ver__[patch]=0 ;;
    major) __base_ver__[major]=$(( ${__base_ver__[major]:-0} - 1 )); __base_ver__[minor]=0; __base_ver__[patch]=0 ;;
  esac
}

function __bump_api__current_target_level() {
  local -n __tl_ver__="$1"

  if (( ${__tl_ver__[patch]:-0} > 0 )); then
    echo "patch"
  elif (( ${__tl_ver__[minor]:-0} > 0 )); then
    echo "minor"
  else
    echo "major"
  fi
}

function __bump_api__level_rank() {
  case "$1" in
    patch) echo 1 ;;
    minor) echo 2 ;;
    major) echo 3 ;;
    *)     echo 0 ;;
  esac
}

# ── Shared helpers ────────────────────────────────────────────────────

function __bump_api__is_release_bump() {
  [[ -n "$(__bump_api__release_type "$1")" ]]
}

function __bump_api__is_prerelease_bump() {
  [[ -n "$(__bump_api__prerelease_type "$1")" ]]
}

function __bump_api__is_releasing_prerelease() {
  local -n __release_ver__="$1"
  local options="$2"

  [[ -n "${__release_ver__[pre_type]}" && "$(__bump_api__release_requested "$options")" == "true" ]]
}

function __bump_api__release_type() {
  jq_get "$1" '.release_type' ''
}

function __bump_api__prerelease_type() {
  jq_get "$1" '.prerelease_type' ''
}

function __bump_api__release_requested() {
  jq_get "$1" '.release' 'false'
}

function __bump_api__apply_bump() {
  local -n __bump_ver__="$1"
  local level="$2"

  case "$level" in
    major) __bump_ver__[major]=$(( ${__bump_ver__[major]:-0} + 1 )); __bump_ver__[minor]=0; __bump_ver__[patch]=0 ;;
    minor) __bump_ver__[minor]=$(( ${__bump_ver__[minor]:-0} + 1 )); __bump_ver__[patch]=0 ;;
    patch) __bump_ver__[patch]=$(( ${__bump_ver__[patch]:-0} + 1 )) ;;
  esac
}

function __bump_api__clear_prerelease() {
  local -n __clear_ver__="$1"

  __clear_ver__[pre_type]=""
  __clear_ver__[pre_inc]=""
}

function __bump_api__format_version() {
  local -n __fmt_ver__="$1"

  if [[ -n "${__fmt_ver__[pre_type]}" ]]; then
    echo "${__fmt_ver__[major]}.${__fmt_ver__[minor]}.${__fmt_ver__[patch]}-${__fmt_ver__[pre_type]}.${__fmt_ver__[pre_inc]}"
  else
    echo "${__fmt_ver__[major]}.${__fmt_ver__[minor]}.${__fmt_ver__[patch]}"
  fi
}

: <<'DOC'
Advances past any existing tags to find an available version slot.
Handles orphaned tags that git describe can't see but git tag -l can.
Respects the requested bump level when incrementing.
DOC
function __bump_api__ensure_tag_available() {
  local version="$1"
  local options="$2"
  local level

  local -A ver
  parse_version "$version" ver

  if [[ -n "${ver[pre_type]}" ]]; then
    level="pre"
  else
    level="$(__bump_api__release_type "$options")"
    [[ -z "$level" ]] && level="patch"
  fi

  while git tag -l "v${version}" | grep -q .; do
    log "Tag v${version} already exists, advancing..."
    case "$level" in
      major)
        ver[major]=$(( ver[major] + 1 ))
        ver[minor]=0
        ver[patch]=0
        ;;
      minor)
        ver[minor]=$(( ver[minor] + 1 ))
        ver[patch]=0
        ;;
      pre)
        ver[pre_inc]=$(( ver[pre_inc] + 1 ))
        ;;
      *)
        ver[patch]=$(( ver[patch] + 1 ))
        ;;
    esac

    if [[ -n "${ver[pre_type]}" ]]; then
      version="${ver[major]}.${ver[minor]}.${ver[patch]}-${ver[pre_type]}.${ver[pre_inc]}"
    else
      version="${ver[major]}.${ver[minor]}.${ver[patch]}"
    fi
  done

  echo "$version"
}

function __bump_api__has_label() {
  local csv="$1" target="$2"
  [[ ",$csv," == *",$target,"* ]]
}

function __bump_api__has_any_label() {
  local csv="$1"
  shift
  for label in "$@"; do
    if __bump_api__has_label "$csv" "$label"; then
      return 0
    fi
  done
  return 1
}
