#!/usr/bin/env bash
# wakelog — post to your changelog from the terminal.
# Docs: https://wakelog.wakelog.workers.dev/docs   Install: curl -fsSL https://wakelog.wakelog.workers.dev/install.sh | sh
set -euo pipefail

WAKELOG_URL="${WAKELOG_URL:-https://wakelog.wakelog.workers.dev}"
CONF="${WAKELOG_CONFIG:-$HOME/.config/wakelog/config}"
# config file may set WAKELOG_URL / WAKELOG_TOKEN / WAKELOG_PROJECT
# environment variables win over config
_env_url="${WAKELOG_URL:-}"; _env_token="${WAKELOG_TOKEN:-}"; _env_proj="${WAKELOG_PROJECT:-}"
[ -f "$CONF" ] && . "$CONF"
[ -n "$_env_url" ] && WAKELOG_URL="$_env_url"
[ -n "$_env_token" ] && WAKELOG_TOKEN="$_env_token"
[ -n "$_env_proj" ] && WAKELOG_PROJECT="$_env_proj"

die() { echo "wakelog: $*" >&2; exit 1; }
need_curl() { command -v curl >/dev/null 2>&1 || die "curl is required"; }

usage() {
  cat <<EOF
wakelog — free changelogs for people who ship  (${WAKELOG_URL})

Usage:
  wakelog init <project-slug> [token]   save project + API token to $CONF
  wakelog post [title] [-t tag] [-b body] [-p project] [--from-git [ref|range]]
                                        post an update (body also read from stdin)
  wakelog import [file|url] [-p project] [-y]
                                        import an existing CHANGELOG.md (default: ./CHANGELOG.md).
                                        A GitHub/GitLab/Codeberg repo URL imports its Releases
  wakelog lint [file|url] [--min-grade A|B|C|D]
                                        grade a changelog against best practices
                                        (default: ./CHANGELOG.md; no token needed;
                                        exits 1 if below --min-grade — CI-friendly)
  wakelog publish <post-id> [-p project]
                                        publish a draft (the id printed by --draft/--at).
                                        Works with a scoped wlp_ CI token — draft at tag
                                        time, publish when the deploy is green
  wakelog list [project]                list recent posts
  wakelog url [project]                 print the public changelog URL
  wakelog help                          this help

Tags: new, fix, improved, announcement, security (or any short word).
Your API token is on your dashboard: ${WAKELOG_URL}/dashboard
Examples:
  wakelog init my-app wl_abc123...
  wakelog post "v1.4: Dark mode" -t new -b "You asked, we shipped."
  git log -1 --pretty=%B | wakelog post "Deployed \$(date +%F)"
  wakelog post --from-git               # commits since last tag, titled after HEAD's tag
  wakelog post "v2.1" --draft           # save hidden; publish later from dashboard/API
  wakelog post "v2.1" --at 2026-08-01T14:00Z   # schedule: auto-publishes at that time (UTC)
  wakelog post "Sprint 12" --from-git v1.3..HEAD
  wakelog lint --min-grade B            # CI: fail the build if the changelog slips
  wakelog publish 42                    # flip draft 42 live (wl_ or scoped wlp_ token)
EOF
}

cmd="${1:-help}"; shift || true

case "$cmd" in
  init)
    proj="${1:-}"; [ -n "$proj" ] || die "usage: wakelog init <project-slug> [token]"
    token="${2:-}"
    if [ -z "$token" ]; then
      printf "API token (from %s/dashboard): " "$WAKELOG_URL" >&2
      read -r token
    fi
    [ -n "$token" ] || die "no token given"
    mkdir -p "$(dirname "$CONF")"
    umask 077
    {
      echo "WAKELOG_URL=\"$WAKELOG_URL\""
      echo "WAKELOG_TOKEN=\"$token\""
      echo "WAKELOG_PROJECT=\"$proj\""
    } > "$CONF"
    echo "Saved to $CONF"
    echo "Try: wakelog post \"Hello from the CLI\" -t announcement"
    ;;

  post)
    need_curl
    title=""; tag=""; body=""; proj="${WAKELOG_PROJECT:-}"
    fromgit=""; gitref=""; draft=""; at=""
    if [ $# -gt 0 ] && [ "${1#-}" = "$1" ]; then title="$1"; shift; fi
    while [ $# -gt 0 ]; do
      case "$1" in
        -t|--tag) tag="${2:-}"; shift 2 ;;
        -b|--body) body="${2:-}"; shift 2 ;;
        -d|--draft) draft=1; shift ;;
        --at) at="${2:-}"; shift 2 ;;
        -p|--project) proj="${2:-}"; shift 2 ;;
        --from-git)
          fromgit=1; shift
          if [ $# -gt 0 ] && [ "${1#-}" = "$1" ]; then gitref="$1"; shift; fi ;;
        *) die "unknown option: $1" ;;
      esac
    done
    if [ -n "$fromgit" ]; then
      command -v git >/dev/null 2>&1 || die "--from-git requires git"
      git rev-parse --git-dir >/dev/null 2>&1 || die "--from-git: not inside a git repository"
      range=""
      if [ -n "$gitref" ]; then
        case "$gitref" in *..*) range="$gitref" ;; *) range="$gitref..HEAD" ;; esac
      else
        lasttag="$(git describe --tags --abbrev=0 --exclude '*[!v0-9.]*' 2>/dev/null || git describe --tags --abbrev=0 2>/dev/null || true)"
        # if HEAD itself is tagged, list commits since the tag before it
        if [ -n "$lasttag" ] && [ "$(git rev-parse "$lasttag^{commit}" 2>/dev/null)" = "$(git rev-parse HEAD)" ]; then
          prevtag="$(git describe --tags --abbrev=0 "$lasttag^" 2>/dev/null || true)"
          range="${prevtag:+$prevtag..}HEAD"
        elif [ -n "$lasttag" ]; then
          range="$lasttag..HEAD"
        fi
      fi
      if [ -n "$range" ]; then
        gitlog="$(git log --no-merges --pretty='- %s' "$range" 2>/dev/null)" || die "--from-git: bad ref/range '$range'"
      else
        gitlog="$(git log --no-merges --pretty='- %s' -n 20)"
      fi
      [ -n "$gitlog" ] || die "--from-git: no commits in range ${range:-HEAD}"
      body="${body:+$body

}$gitlog"
      if [ -z "$title" ]; then
        headtag="$(git tag --points-at HEAD 2>/dev/null | head -1)"
        if [ -n "$headtag" ]; then title="$headtag"
        elif [ -n "$range" ]; then title="Changes since ${range%..*}"
        else title="Changes — $(date +%F)"; fi
      fi
    fi
    [ -n "$title" ] || die "usage: wakelog post <title> [-t tag] [-b body] [-d|--draft] [--at 2026-08-01T14:00Z] [-p project] [--from-git [ref|range]]"
    [ -n "$proj" ] || die "no project — run 'wakelog init <slug>' or pass -p"
    [ -n "${WAKELOG_TOKEN:-}" ] || die "no API token — run 'wakelog init' or set WAKELOG_TOKEN"
    if [ -z "$body" ] && [ ! -t 0 ]; then body="$(cat)"; fi
    resp="$(curl -fsS -X POST "$WAKELOG_URL/api/v1/$proj/posts" \
      -H "Authorization: Bearer $WAKELOG_TOKEN" \
      --data-urlencode "title=$title" \
      --data-urlencode "tag=$tag" \
      ${draft:+--data-urlencode "draft=1"} \
      ${at:+--data-urlencode "publish_at=$at"} \
      --data-urlencode "body=$body")" || die "post failed"
    url="$(printf '%s' "$resp" | sed -n 's/.*"url":"\([^"]*\)".*/\1/p')"
    if [ -n "$at" ]; then id="$(printf '%s' "$resp" | sed -n 's/.*"id":\([0-9]*\).*/\1/p')"; echo "Scheduled ✓  (id $id — goes live at $at UTC; unschedule with publish_at= via PATCH)"
    elif [ -n "$draft" ]; then id="$(printf '%s' "$resp" | sed -n 's/.*"id":\([0-9]*\).*/\1/p')"; echo "Draft saved ✓  (id $id — publish from the dashboard or: wakelog publish $id)"
    else echo "Shipped ✓  ${url:-$resp}"; fi
    ;;

  import)
    need_curl
    file=""; proj="${WAKELOG_PROJECT:-}"; yes=""
    while [ $# -gt 0 ]; do
      case "$1" in
        -p|--project) proj="${2:-}"; shift 2 ;;
        -y|--yes) yes=1; shift ;;
        -*) die "unknown option: $1" ;;
        *) file="$1"; shift ;;
      esac
    done
    file="${file:-CHANGELOG.md}"
    case "$file" in
      https://*) src="url=$file" ;;
      http://*) die "only https URLs are supported" ;;
      *) [ -f "$file" ] || die "no such file: $file (usage: wakelog import [file|url])"
         src="changelog@$file" ;;
    esac
    [ -n "$proj" ] || die "no project — run 'wakelog init <slug>' or pass -p"
    [ -n "${WAKELOG_TOKEN:-}" ] || die "no API token — run 'wakelog init' or set WAKELOG_TOKEN"
    resp="$(curl -fsS -X POST "$WAKELOG_URL/api/v1/$proj/import" \
      -H "Authorization: Bearer $WAKELOG_TOKEN" \
      --data-urlencode "dry_run=1" \
      --data-urlencode "$src")" || die "import preview failed"
    found="$(printf '%s' "$resp" | sed -n 's/.*"found":\([0-9]*\).*/\1/p')"
    [ -n "$found" ] || die "unexpected response: $resp"
    echo "Found $found entries in $file:"
    printf '%s\n' "$resp" | tr '{' '\n' | sed -n 's/.*"title":"\([^"]*\)","date":\("\([^"]*\)"\|null\).*/  \3  \1/p' | head -20
    [ "$found" -gt 20 ] && echo "  … and $((found - 20)) more"
    if [ -z "$yes" ]; then
      printf "Import all %s into /p/%s? [y/N] " "$found" "$proj" >&2
      read -r ans
      case "$ans" in y|Y|yes) ;; *) echo "Aborted."; exit 1 ;; esac
    fi
    resp="$(curl -fsS -X POST "$WAKELOG_URL/api/v1/$proj/import" \
      -H "Authorization: Bearer $WAKELOG_TOKEN" \
      --data-urlencode "$src")" || die "import failed"
    n="$(printf '%s' "$resp" | sed -n 's/.*"imported":\([0-9]*\).*/\1/p')"
    s="$(printf '%s' "$resp" | sed -n 's/.*"skipped":\([0-9]*\).*/\1/p')"
    echo "Imported ${n:-?} entries (${s:-0} already existed) ✓  $WAKELOG_URL/p/$proj"
    ;;

  lint)
    need_curl
    target=""; min=""
    while [ $# -gt 0 ]; do
      case "$1" in
        --min-grade) min="$(printf '%s' "${2:-}" | tr '[:lower:]' '[:upper:]')"; shift 2 ;;
        -*) die "unknown option: $1" ;;
        *) target="$1"; shift ;;
      esac
    done
    if [ -n "$min" ]; then
      case "$min" in A|B|C|D) ;; *) die "--min-grade takes A, B, C or D" ;; esac
    fi
    target="${target:-CHANGELOG.md}"
    case "$target" in
      https://*) src="url=$target" ;;
      http://*) die "only https URLs are supported" ;;
      *) [ -f "$target" ] || die "no such file: $target (usage: wakelog lint [file|url] [--min-grade B])"
         src="changelog@$target" ;;
    esac
    resp="$(curl -sS -X POST "$WAKELOG_URL/api/v1/lint" \
      --data-urlencode "format=text" \
      --data-urlencode "$src" \
      -w '\n%{http_code}')" || die "lint request failed"
    code="${resp##*$'\n'}"
    body="${resp%$'\n'*}"
    body="${body%$'\n'}"
    [ "$code" = "200" ] || die "${body#error: }"
    printf '%s\n' "$body"
    grade="$(printf '%s\n' "$body" | sed -n '1s/.*grade: \([A-F]\).*/\1/p')"
    [ -n "$grade" ] || die "could not read grade from response"
    if [ -n "$min" ]; then
      rank() { case "$1" in A) echo 4 ;; B) echo 3 ;; C) echo 2 ;; D) echo 1 ;; *) echo 0 ;; esac; }
      if [ "$(rank "$grade")" -lt "$(rank "$min")" ]; then
        echo "wakelog: grade $grade is below required $min" >&2
        exit 1
      fi
    fi
    ;;

  publish)
    need_curl
    id=""; proj="${WAKELOG_PROJECT:-}"
    while [ $# -gt 0 ]; do
      case "$1" in
        -p|--project) proj="${2:-}"; shift 2 ;;
        -*) die "unknown option: $1" ;;
        *) id="$1"; shift ;;
      esac
    done
    case "$id" in ''|*[!0-9]*) die "usage: wakelog publish <post-id> [-p project]   (the id printed by 'wakelog post --draft')" ;; esac
    [ -n "$proj" ] || die "no project — run 'wakelog init <slug>' or pass -p"
    [ -n "${WAKELOG_TOKEN:-}" ] || die "no API token — run 'wakelog init' or set WAKELOG_TOKEN"
    resp="$(curl -sS -X PATCH "$WAKELOG_URL/api/v1/$proj/posts/$id" \
      -H "Authorization: Bearer $WAKELOG_TOKEN" \
      --data-urlencode "draft=false" \
      -w '\n%{http_code}')" || die "publish request failed"
    code="${resp##*$'\n'}"
    body="${resp%$'\n'*}"
    if [ "$code" != "200" ]; then
      # \" inside JSON strings would truncate the match — turn them into plain quotes first
      err="$(printf '%s' "$body" | sed 's/\\"/'"'"'/g' | sed -n 's/.*"error":"\([^"]*\)".*/\1/p')"
      die "publish failed: ${err:-$body}"
    fi
    echo "Published ✓  $WAKELOG_URL/p/$proj/$id"
    ;;

  list)
    need_curl
    proj="${1:-${WAKELOG_PROJECT:-}}"
    [ -n "$proj" ] || die "no project — run 'wakelog init <slug>' or pass one: wakelog list <slug>"
    curl -fsS "$WAKELOG_URL/api/v1/$proj/posts?format=text" || die "list failed (project exists?)"
    ;;

  url)
    proj="${1:-${WAKELOG_PROJECT:-}}"
    [ -n "$proj" ] || die "no project configured"
    echo "$WAKELOG_URL/p/$proj"
    ;;

  help|-h|--help) usage ;;
  *) usage; exit 1 ;;
esac
