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

USAGE="$(cat <<EOF
Creates a release for the latest tag marked in the project manifest.

Usage: ops publish-release [-h,--help] [-g,--github] [--artifacts <file>] [artifact...]

Arguments:
  artifact        Optional artifact files to attach to the release.

Flags:
  --artifacts <f> Load artifact paths from a JSON array file (e.g. artifacts.json).
  -d, --dry-run   Puts together a dry run of a release for review.
  -g, --github    Publish to GitHub instead of Forgejo.
  -h, --help      Show this help text.

Environment:
  FORGE_ADMIN_TOKEN   Required (Forgejo mode). API token for Forgejo release creation.
  GITHUB_TOKEN    Required (GitHub mode). API token for GitHub release creation.

EOF
)"

ARG_ARTIFACTS=()
FLAG_DRY_RUN=false
FLAG_GITHUB=false
FLAG_ARTIFACTS_FILE=""

import \
  "$SHELLOPS_DIR/lib/manifest.api.sh" \
  "$SHELLOPS_DIR/lib/versions.api.sh" \
  "$SHELLOPS_DIR/lib/forgejo.api.sh" \
  "$SHELLOPS_DIR/lib/github.api.sh" \
  "$SHELLOPS_DIR/lib/markdown.api.sh" \
  "$SHELLOPS_DIR/lib/releases.api.sh" \
  "$SHELLOPS_DIR/lib/tarball/api.sh" \
  "$SHELLOPS_DIR/lib/get_date.func.sh"

function main() {
  parse_args "$@"
  require_token

  local tag title body prerelease
  local -A version

  tag="v$(manifest_get latest)"
  parse_version "$tag" version

  prerelease=false
  if [[ -n "${version[pre_type]}" ]]; then
    prerelease=true
  fi

  title="$(build_title "$tag")"

  if [[ $FLAG_GITHUB = true ]]; then
    body="$(build_github_body "$tag")"
  else
    body="$(build_body "$tag")"
  fi

  if [[ $FLAG_DRY_RUN = true ]]; then
    print_dry_run "$tag" "$title" "$body" "$prerelease"
    exit 0
  fi

  generate_checksums

  if [[ $FLAG_GITHUB = true ]]; then
    publish_github "$tag" "$title" "$body" "$prerelease"
  else
    publish "$tag" "$title" "$body" "$prerelease"
  fi
}

function parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --artifacts) FLAG_ARTIFACTS_FILE="$2"; shift ;;
      -d|--dry-run) FLAG_DRY_RUN=true ;;
      -g|--github) FLAG_GITHUB=true ;;
      -h|--help) log "$USAGE" && exit 0 ;;
      -*) log "Unknown option: $1" && log "$USAGE" && exit 1 ;;
      *) ARG_ARTIFACTS+=("$1") ;;
    esac
    shift
  done

  if [[ -n "$FLAG_ARTIFACTS_FILE" ]]; then
    load_artifacts_from_json "$FLAG_ARTIFACTS_FILE"
  fi
}

function load_artifacts_from_json() {
  local file="$1"
  local paths
  paths="$(tarball_load_artifacts "$file")" || exit 1

  while IFS= read -r path; do
    [[ -n "$path" ]] || continue
    ARG_ARTIFACTS+=("$path")
  done <<< "$paths"
}

function require_token() {
  if [[ $FLAG_GITHUB = true ]]; then
    if [[ -z "${GITHUB_TOKEN:-}" ]]; then
      error "GITHUB_TOKEN is not set. Cannot create GitHub release."
      exit 1
    fi
    return
  fi

  if [[ -z "${FORGE_ADMIN_TOKEN:-}" ]]; then
    error "FORGE_ADMIN_TOKEN is not set. Cannot create release."
    exit 1
  fi
}

# ==========================================================================
# Title
# ==========================================================================

function build_title() {
  local tag="$1"

  case "$(get_release_type "$tag")" in
    patch) echo "$tag: $(get_commit_subject "$tag")" ;;
    minor) echo "$tag: $(manifest_get minor_name)" ;;
    major) echo "$tag: $(manifest_get major_name)" ;;
    *) echo "$tag" ;;
  esac
}

function get_commit_subject() {
  local tag="$1"
  local prev_tag
  prev_tag="$(get_relevant_baseline_tag "$tag")"
  if [[ -z "$prev_tag" ]]; then
    prev_tag="$(initial_commit)"
  fi
  git log --no-merges --invert-grep --grep='^.\?chore' \
    -1 --format='%s' "${prev_tag}..${tag}" 2>/dev/null || echo ""
}

# ==========================================================================
# Body
# ==========================================================================

function build_body() {
  local tag="$1"
  local prev_tag commits prs grouped doc

  prev_tag="$(get_relevant_baseline_tag "$tag")"
  if [[ -z "$prev_tag" ]]; then
    prev_tag="$(initial_commit)"
  fi

  commits="$(list_commits "$prev_tag" "$tag")"
  if [[ "$commits" == "[]" ]]; then
    echo "No changes."
    return
  fi

  prs="$(fetch_pr_metadata "$commits" "$FORGE_ADMIN_TOKEN")"
  grouped="$(group_prs "$prs")"
  doc="$(build_release_doc "$grouped" "$(format_date)")"
  markdown_render "$doc"
}

function format_date() {
  local -A _date
  get_date _date

  local y="${_date[year]}"
  local mon="${_date[month]}"
  local d="${_date[day]}"
  local suf="${_date[day_suffix]}"
  local h="${_date[hours_12]}"
  local min="${_date[minutes]}"
  local ampm="${_date[ampm]}"
  local tz="${_date[timezone]}"

  echo "$mon $d$suf, $y $h:$min $ampm $tz"
}

function build_github_body() {
  local tag="$1"
  local org repo forgejo_base

  org="$(manifest_get org)"
  repo="$(manifest_get repo)"
  forgejo_base="${FORGEJO_API_BASE:-https://creature-workshop.kernelle-soft.com}"

  echo "See full release notes at ${forgejo_base}/${org}/${repo}/releases/tag/${tag}"
}

# ==========================================================================
# Publishing
# ==========================================================================

function publish_github() {
  local tag="$1" title="$2" body="$3" prerelease="$4"

  local release_args release_result release_id
  release_args="$(jq_set "$(new_github_release_options)" \
    '.tag_name' "$tag" \
    '.name'     "$title" \
    '.body'     "$body" \
    '.prerelease' "$prerelease" \
    '.token'    "$GITHUB_TOKEN"
  )"

  release_result="$(github_create_release "$release_args")"
  release_id="$(jq_get "$release_result" '.id')"

  if [[ -z "$release_id" || "$release_id" == "null" ]]; then
    error "Failed to create GitHub release. Response:"
    error "$(jq_dump "$release_result")"
    exit 1
  fi

  log "Created GitHub release $tag (id: $release_id)"
  upload_github_artifacts "$release_id"
}

function upload_github_artifacts() {
  local release_id="$1"

  for artifact in ${ARG_ARTIFACTS[@]+"${ARG_ARTIFACTS[@]}"}; do
    local filename="${artifact##*/}"
    local upload_args
    upload_args="$(jq_set "$(new_github_upload_options)" \
      '.release_id' "$release_id" \
      '.filename'   "$filename" \
      '.filepath'   "$artifact" \
      '.token'      "$GITHUB_TOKEN"
    )"

    log "Uploading $filename to GitHub..."
    github_upload_artifact "$upload_args"
  done
}

function publish() {
  local tag="$1" title="$2" body="$3" prerelease="$4"

  local release_args release_result release_id
  release_args="$(jq_set "$(new_release_options)" \
    '.tag_name' "$tag" \
    '.name'     "$title" \
    '.body'     "$body" \
    '.prerelease' "$prerelease" \
    '.token'    "$FORGE_ADMIN_TOKEN"
  )"

  release_result="$(create_release "$release_args")"
  release_id="$(jq_get "$release_result" '.id')"

  if [[ -z "$release_id" || "$release_id" == "null" ]]; then
    error "Failed to create release. Response:"
    error "$(jq_dump "$release_result")"
    exit 1
  fi

  log "Created release $tag (id: $release_id)"
  upload_artifacts "$release_id"
}

function upload_artifacts() {
  local release_id="$1"

  for artifact in ${ARG_ARTIFACTS[@]+"${ARG_ARTIFACTS[@]}"}; do
    local filename="${artifact##*/}"
    local upload_args
    upload_args="$(jq_set "$(new_upload_options)" \
      '.release_id' "$release_id" \
      '.filename'   "$filename" \
      '.filepath'   "$artifact" \
      '.token'      "$FORGE_ADMIN_TOKEN"
    )"

    log "Uploading $filename..."
    upload_artifact "$upload_args"
  done
}

function generate_checksums() {
  local checksum_artifacts=()
  for artifact in ${ARG_ARTIFACTS[@]+"${ARG_ARTIFACTS[@]}"}; do
    [[ "${artifact##*/}" == "SHA256SUMS" ]] && continue
    checksum_artifacts+=("$artifact")
  done

  if [[ ${#checksum_artifacts[@]} -gt 0 ]]; then
    sha256sum "${checksum_artifacts[@]}" | sed 's|  .*/|  |' > SHA256SUMS
    ARG_ARTIFACTS+=("SHA256SUMS")
  fi
}

# ==========================================================================
# Dry Run
# ==========================================================================

function print_dry_run() {
  local tag="$1" title="$2" body="$3" prerelease="$4"

  log_banner "Dry-Run: $tag"
  log "  Title:      $title"
  log "  Prerelease: $prerelease"
  log ""
  log "--- Release Notes ---"
  log "$body"
  log "--- End Notes ---"
  log ""

  if [[ ${#ARG_ARTIFACTS[@]} -gt 0 ]]; then
    log "Artifacts:"
    for a in "${ARG_ARTIFACTS[@]}"; do
      log "  $a"
    done
  fi
}

main "$@"
