#!/usr/bin/env bash

:<<'DOC'
API for generating release notes from git history and PR metadata.
Reads .forgejo/releases.json to determine grouping and formatting.
Falls back to shellops's default config if the consumer doesn't provide one.
DOC

__releases_api__CONFIG_FILE="$PROJ/.forgejo/releases.json"
__releases_api__DEFAULT_CONFIG="$SHELLOPS_DIR/schema/releases.default.json"

function load_release_config() {
  if [[ -f "$__releases_api__CONFIG_FILE" ]]; then
    cat "$__releases_api__CONFIG_FILE"
  else
    warn "No releases.json found at $__releases_api__CONFIG_FILE — using default"
    cat "$__releases_api__DEFAULT_CONFIG"
  fi
}

# ==========================================================================
# PR Metadata
# ==========================================================================

:<<'DOC'
Fetches PR metadata from Codeberg for each commit that references a PR number.

Usage: fetch_pr_metadata "\$commits_json" "\$token"

  commits_json: JSON array of {hash, message, pr_number} from list_commits
  token: Codeberg API token

  Returns a JSON array of {pr_number, title, author, labels} objects.
DOC
function fetch_pr_metadata() {
  local commits="$1" token="$2"
  local pr_numbers
  pr_numbers="$(jq_pluck "$commits" '.pr_number')"
  pr_numbers="$(jq_filter "$pr_numbers" '. != null')"
  jq_map fetch_pull_request "$pr_numbers" "$token"
}

function fetch_pull_request() {
  local pr_number="$1" token="$2"

  if ! jq_defined "$pr_number"; then
    echo "null"
    return
  fi

  local pr_data
  pr_data="$(get_pull_request "$(jq_set "{}" \
    '.pr_number' "$pr_number" \
    '.token' "$token"
  )")"

  local title author labels
  title="$(jq_get "$pr_data" '.title')"
  author="$(jq_get "$pr_data" '.user.login')"
  labels="$(jq_pluck "$(jq_get "$pr_data" '.labels')" '.name')"

  jq_set "{}" \
    '.pr_number' "$pr_number" \
    '.title' "$title" \
    '.author' "$author" \
    '.labels' "$labels"
}

# ==========================================================================
# Grouping
# ==========================================================================

:<<'DOC'
Groups PR entries by the priority-ordered groups defined in releases.json.

Usage: group_prs "\$prs_json"

  First matching group wins (top-to-bottom priority).
  Labels that placed the PR into a group are stripped from inline display.
  PRs with skip labels are excluded entirely.

  Returns a JSON object keyed by group header, each value an array of entries:
    {
      "Impact": [{"title":"...","pr_number":"1","author":"...","labels":[...],"inline_labels":[...]}],
      "Development": [],
      ...
    }
DOC
function group_prs() {
  local prs="$1"
  local config groups skip_labels default_group grouped

  config="$(load_release_config)"
  groups="$(jq_get "$config" '.groups')"
  skip_labels="$(jq_get "$config" '.skip')"
  default_group="$(jq_get "$config" '.default_group')"

  grouped="$(create_empty_groups "$groups" "$default_group")"
  jq_reduce __releases_api__classify_pr \
    "$prs" \
    "$grouped" \
    "$groups" "$skip_labels" "$default_group"
}

function __releases_api__classify_pr() {
  local grouped="$1" pr="$2"
  local groups="$3" skip_labels="$4" default_group="$5"
  local pr_labels
  local match matched_header matched_labels
  local inline_labels

  pr_labels="$(jq_get "$pr" '.labels')"

  if should_skip_pr "$pr_labels" "$skip_labels"; then
    echo "$grouped"
    return
  fi

  match="$(find_matching_group "$pr_labels" "$groups")"
  matched_header="$(jq_get "$match" '.header')"
  matched_labels="$(jq_get "$match" '.labels')"

  if ! jq_defined "$matched_header"; then
    matched_header="$default_group"
    matched_labels="[]"
  fi

  inline_labels="$(
    get_inline_labels "$pr_labels" "$matched_labels" "$skip_labels"
  )"

  pr="$(jq_set "$pr" '.inline_labels' "$inline_labels")"

  jq_push "$grouped" "$matched_header" "$pr"
}

function create_empty_groups() {
  local groups="$1" default_group="$2"
  local buckets
  buckets="$(jq_reduce __releases_api__add_empty_bucket "$groups" "{}")"
  jq_set "$buckets" "$default_group" "[]"
}

function __releases_api__add_empty_bucket() {
  local acc="$1" group="$2"
  local header
  header="$(jq_get "$group" '.header')"
  jq_set "$acc" "$header" "[]"
}

function find_matching_group() {
  local pr_labels="$1" groups="$2"
  jq_find __releases_api__has_matching_labels "$groups" "$pr_labels"
}

function __releases_api__has_matching_labels() {
  local group="$1" pr_labels="$2"
  local group_labels length

  group_labels="$(jq_get "$group" '.labels')"
  length="$(jq_len "$(jq_intersect "$pr_labels" "$group_labels")")"
  ((length > 0))
}

:<<'DOC'
Returns the PR's labels minus those used for grouping or skipping.
These are displayed inline in the release note bullet point.

Usage: get_inline_labels "\$pr_labels" "\$matched_labels" "\$skip_labels"

  Given pr_labels=["breaking","godot","skip-changelog"],
        matched_labels=["breaking"], skip_labels=["skip-changelog"]
  Returns: ["godot"]
DOC
function get_inline_labels() {
  local pr_labels="$1" matched_labels="$2" skip_labels="$3"
  local exclude
  exclude="$(jq_concat "$matched_labels" "$skip_labels")"
  jq_difference "$pr_labels" "$exclude"
}

function should_skip_pr() {
  local pr_labels="$1" skip_labels="$2"
  local length
  length="$(jq_len "$(jq_intersect "$pr_labels" "$skip_labels")")"
  ((length > 0))
}

# ==========================================================================
# Rendering
# ==========================================================================

:<<'DOC'
Builds a markdown document (JSON array of lines) from grouped PR entries.

Usage: build_release_doc "\$grouped_json" "\$date_string"

  grouped_json: output of group_prs
  date_string: formatted release date

  Returns a JSON array suitable for markdown_render.
DOC
function build_release_doc() {
  local grouped="$1" date_string="$2"
  local config groups doc

  config="$(load_release_config)"
  groups="$(jq_get "$config" '.groups')"

  doc="[]"
  doc="$(markdown_line "$doc" "Released on $date_string")"
  doc="$(markdown_nl "$doc")"

  jq_reduce __releases_api__render_group "$groups" "$doc" "$grouped"
}

function __releases_api__render_group() {
  local doc="$1" group="$2" grouped="$3"
  local header entries length

  header="$(jq_get "$group" '.header')"
  entries="$(jq_get "$grouped" "$header")"

  length="$(jq_len "$entries")"
  if ((length == 0)); then
    echo "$doc"
    return
  fi

  doc="$(markdown_h2 "$doc" "$header")"
  doc="$(jq_reduce __releases_api__render_bullet "$entries" "$doc")"
  markdown_nl "$doc"
}

function __releases_api__render_bullet() {
  local doc="$1" entry="$2"
  markdown_bullet "$doc" "$(format_bullet "$entry")"
}

function format_bullet() {
  local entry="$1"
  local title pr_number author inline_labels prefix
  # shellcheck disable=SC2016
  local label_template='"`\(.)`"'

  title="$(jq_get "$entry" '.title')"
  pr_number="$(jq_get "$entry" '.pr_number')"
  author="$(jq_get "$entry" '.author')"
  inline_labels="$(jq_get "$entry" '.inline_labels')"

  prefix="$(jq_join "$inline_labels" " " "$label_template")"
  echo "${prefix:+$prefix }${title} (#${pr_number}) @${author}"
}
