#!/usr/bin/env bash
set -euo pipefail
source "$SHELLOPS_DIR/.envrc"

: <<'DOC'
Synchronizes repository labels on Forgejo to match the merged label manifest.

Creates missing labels, updates drifted colors/descriptions,
and archives labels that are no longer in the manifest.
DOC

import \
  "$SHELLOPS_DIR/lib/manifest.api.sh" \
  "$SHELLOPS_DIR/lib/labels.api.sh" \
  "$SHELLOPS_DIR/lib/forgejo.api.sh"

USAGE="$(cat <<EOF
Synchronizes Forgejo labels with the label manifest.

Usage: ops sync-labels [-h,--help] [-n,--dry-run]

Flags:
  -n, --dry-run   Preview changes without applying them.
  -h, --help      Show this help text.

Environment:
  FORGE_ADMIN_TOKEN   Required. API token for Forgejo label management.

EOF
)"

FLAG_DRY_RUN=false

function main() {
  parse_args "$@"

  local token="${FORGE_ADMIN_TOKEN:-}"
  if [[ -z "$token" ]]; then
    error "FORGE_ADMIN_TOKEN is not set"
    exit 1
  fi

  local options desired actual diff
  options="$(jq_set "$(new_label_options)" '.token' "$token")"
  desired="$(load_desired)"
  actual="$(forgejo_list_labels "$options")"
  diff="$(diff_labels "$desired" "$actual")"

  if is_in_sync "$diff"; then
    log "Labels are in sync."
    exit 0
  fi

  print_summary "$diff"

  if [[ "$FLAG_DRY_RUN" == "true" ]]; then
    log "(dry run -- no changes applied)"
    exit 0
  fi

  apply_changes "$options" "$diff"
  log "Label sync complete."
}

function load_desired() {
  local merged
  merged="$(manifest_get labels)"
  flatten_labels "$merged"
}

function is_in_sync() {
  local diff="$1"
  local total

  total=$(( $(jq_len "$(jq_get "$diff" '.create')") \
          + $(jq_len "$(jq_get "$diff" '.update')") \
          + $(jq_len "$(jq_get "$diff" '.archive')") ))

  (( total == 0 ))
}

function print_summary() {
  local diff="$1"

  print_section "Create" "$(jq_get "$diff" '.create')" '.name'
  print_section "Update" "$(jq_get "$diff" '.update')" '.name'
  print_section "Archive" "$(jq_get "$diff" '.archive')" '.name'
}

function print_section() {
  local heading="$1" items="$2" field="$3"
  local count

  count="$(jq_len "$items")"
  if (( count == 0 )); then
    return
  fi

  log ""
  log "$heading ($count):"
  jq_foreach __sync_labels__print_item "$items" "$field"
}

function __sync_labels__print_item() {
  local item="$1" field="$2"
  log "  $(jq_get "$item" "$field")"
}

function apply_changes() {
  local options="$1" diff="$2"

  jq_foreach __sync_labels__apply_create "$(jq_get "$diff" '.create')" "$options"
  jq_foreach __sync_labels__apply_update "$(jq_get "$diff" '.update')" "$options"
  jq_foreach __sync_labels__apply_archive "$(jq_get "$diff" '.archive')" "$options"
}

function __sync_labels__apply_create() {
  local label="$1" options="$2"
  local name

  name="$(jq_get "$label" '.name')"
  log "  Creating $name"
  forgejo_create_label "$options" "$label" > /dev/null
}

function __sync_labels__apply_update() {
  local label="$1" options="$2"
  local name label_id edit_options

  name="$(jq_get "$label" '.name')"
  label_id="$(jq_get "$label" '.id')"
  edit_options="$(jq_set "$options" '.label_id' "$label_id")"

  log "  Updating $name"
  forgejo_edit_label "$edit_options" "$label" > /dev/null
}

function __sync_labels__apply_archive() {
  local label="$1" options="$2"
  local name label_id edit_options archive_body

  name="$(jq_get "$label" '.name')"
  label_id="$(jq_get "$label" '.id')"
  edit_options="$(jq_set "$options" '.label_id' "$label_id")"
  archive_body='{"is_archived":true}'

  log "  Archiving $name"
  forgejo_edit_label "$edit_options" "$archive_body" > /dev/null
}

function parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -h|--help) log "$USAGE" && exit 0 ;;
      -n|--dry-run) FLAG_DRY_RUN=true ;;
      -*)
        log "Unknown option: $1"
        log "$USAGE"
        exit 1
        ;;
      *)
        log "Unexpected argument: $1"
        log "$USAGE"
        exit 1
        ;;
    esac
    shift
  done
}

main "$@"
