#!/bin/sh

{

set -ex

version=$1
repository_owner=fish-shell
remote=origin
if [ -n "$2" ]; then
    set -u
    repository_owner=$2
    remote=$3
    set +u
    [ $# -eq 3 ]
fi

[ -n "$version" ]

for tool in \
    cmake \
    bundle \
    diff \
    gh \
    gpg \
    jq \
    ninja \
    ruby \
    tar \
    timeout \
    uv \
    xz \
; do
    if ! command -v "$tool" >/dev/null; then
        echo >&2 "$0: missing command: $1"
        exit 1
    fi
done

committer=$(git var GIT_AUTHOR_IDENT)
committer=${committer% *} # strip timezone
committer=${committer% *} # strip timestamp
gpg --local-user="$committer" --sign </dev/null >/dev/null

repo_root="$(dirname "$0")/.."
fish_site=$repo_root/../fish-site
fish_site_repo=git@github.com:$repository_owner/fish-site

for path in . "$fish_site"
do
    if ! git -C "$path" diff HEAD --quiet ||
        git ls-files --others --exclude-standard | grep .; then
        echo >&2 "$0: index and worktree must be clean"
        exit 1
    fi
done

(
    cd "$fish_site"
    [ "$(git rev-parse HEAD)" = \
        "$(git ls-remote "$fish_site_repo" refs/heads/master |
            awk '{print $1}')" ]
)

if git tag | grep -qxF "$version"; then
    echo >&2 "$0: tag $version already exists"
    exit 1
fi

integration_branch=$(
    git for-each-ref --points-at=HEAD 'refs/heads/Integration_*' \
        --format='%(refname:strip=2)'
)
[ -n "$integration_branch" ] ||
    git merge-base --is-ancestor "$remote"/master HEAD

sed -n 1p CHANGELOG.rst | grep -q '^fish .*(released .*)$'
sed -n 2p CHANGELOG.rst | grep -q '^===*$'

changelog_title="fish $version (released $(date +'%B %d, %Y'))"
sed -i \
    -e "1c$changelog_title" \
    -e "2c$(printf %s "$changelog_title" | sed s/./=/g)" \
    CHANGELOG.rst

CreateCommit() {
    git commit -m "$1

Created by ./build_tools/release.sh $version"
}

sed -i "s/^version = \".*\"/version = \"$1\"/g" Cargo.toml
cargo fetch --offline # bumps the version in Cargo.lock
if [ "$1" = "$version" ]; then
    # debchange is a Debian script to manage the Debian changelog, but
    # it's too annoying to install everywhere. Just do it by hand.
    cat - contrib/debian/changelog > contrib/debian/changelog.new <<EOF
fish (${version}-1) stable; urgency=medium

  * Release of new version $version.

  See https://github.com/fish-shell/fish-shell/releases/tag/$version for details.

 -- $committer  $(date -R)

EOF
    mv contrib/debian/changelog.new contrib/debian/changelog
    git add contrib/debian/changelog
fi
git add CHANGELOG.rst Cargo.toml Cargo.lock
CreateCommit "Release $version"

# Tags must be full objects, not lightweight tags, for
# git_version-gen.sh to work.
git -c "user.signingKey=$committer" \
    tag --sign --message="Release $version" "$version"

git push "$remote" "$version"

TIMEOUT=
gh() {
    command ${TIMEOUT:+timeout $TIMEOUT} \
        gh --repo "$repository_owner/fish-shell" "$@"
}

gh workflow run release.yml --ref="$version" \
    --raw-field="version=$version"

run_id=
while [ -z "$run_id" ] && sleep 5
do
    run_id=$(gh run list \
                --json=databaseId --jq=.[].databaseId \
                --workflow=release.yml --limit=1 \
                --commit="$(git rev-parse "$version^{commit}")")
done

# Update fishshell.com
tag_oid=$(git rev-parse "$version")
tmpdir=$(mktemp -d)
fish_tar_xz=fish-$version.tar.xz
(
    local_tarball=$tmpdir/local-tarball
    mkdir "$local_tarball"
    FISH_ARTEFACT_PATH=$local_tarball ./build_tools/make_tarball.sh
    cd "$local_tarball"
    tar xf "$fish_tar_xz"
)
# TODO This works on draft releases only if "gh" is configured to
# have write access to the fish-shell repository. Unless we are fine
# publishing the release at this point, we should at least fail if
# "gh" doesn't have write access.
while ! \
    gh release download "$version" --dir="$tmpdir" \
        --pattern="$fish_tar_xz"
do
    TIMEOUT=30 gh run watch "$run_id" ||:
    sleep 5
done
actual_tag_oid=$(git ls-remote "$remote" |
    awk '$2 == "refs/tags/'"$version"'" { print $1 }')
[ "$tag_oid" = "$actual_tag_oid" ]

(
    cd "$tmpdir"
    tar xf "$fish_tar_xz"
    diff -ur "fish-$version" "local-tarball/fish-$version"
    gpg --local-user="$committer" --sign --detach --armor \
        "$fish_tar_xz"
    gh release upload "$version" "$fish_tar_xz.asc"
)

(
    cd "$tmpdir/local-tarball/fish-$version"
    uv --no-managed-python venv
    # shellcheck disable=1091
    . .venv/bin/activate
    cmake -GNinja -DCMAKE_BUILD_TYPE=Debug .
    ninja doc
)
CopyDocs() {
    rm -rf "$fish_site/site/docs/$1"
    cp -r "$tmpdir/local-tarball/fish-$version/cargo/fish-docs/html" "$fish_site/site/docs/$1"
    git -C "$fish_site" add "site/docs/$1"
}
minor_version=${version%.*}
CopyDocs "$minor_version"
latest_release=$(
    releases=$(git tag | grep '^[0-9]*\.[0-9]*\.[0-9]*.*' |
        sed '
            # De-prioritize release candidates (1.2.3-rc0)
            s/-/~/g
        ' | LC_ALL=C sort --version-sort
    )
    printf %s\\n "$releases" | tail -1
)
if [ "$version" = "$latest_release" ]; then
    CopyDocs current
fi
rm -rf "$tmpdir"
(
    cd "$fish_site"
    make
    git add -u
    git add docs
    if git ls-files --others --exclude-standard | grep .; then
        exit 1
    fi
    git commit --message="$(printf %s "\
        | Release $version (docs)
        |
        | Created by ../fish-shell/build_tools/release.sh
    " | sed 's,^\s*| \?,,')"
)

gh_api_repo() {
    path=$1
    shift
    command gh api \
        -H "Accept: application/vnd.github+json" \
        -H "X-GitHub-Api-Version: 2022-11-28" \
        "/repos/$repository_owner/fish-shell/$path" \
        "$@"
}

# Approve macos-codesign
# TODO what if current user can't approve?
gh_pending_deployments() {
    gh_api_repo "actions/runs/$run_id/pending_deployments" "$@"
}
while {
    environment_id=$(gh_pending_deployments | jq .[].environment.id)
    [ -z "$environment_id" ]
}
do
    sleep 5
done
echo '
        {
            "environment_ids": ['"$environment_id"'],
            "state": "approved",
            "comment": "Approved via ./build_tools/release.sh"
        }
    ' |
gh_pending_deployments --method POST --input=-

# Await completion.
gh run watch "$run_id"

while {
    ! draft=$(gh release view "$version" --json=isDraft --jq=.isDraft) \
    || [ "$draft" = true ]
}
do
    sleep 20
done

(
    cd "$fish_site"
    make new-release
    git add -u
    git add docs
    if git ls-files --others --exclude-standard | grep .; then
        exit 1
    fi
    git commit --message="$(printf %s "\
        | Release $version (release list update)
        |
        | Created by ../fish-shell/build_tools/release.sh
    " | sed 's,^\s*| \?,,')"
    # This takes care to support remote names that are different from
    # fish-shell remote name. Also, support detached HEAD state.
    git push "$fish_site_repo" HEAD:master
    git fetch "$fish_site_repo" \
        "$(git rev-parse HEAD):refs/remotes/origin/master"
)

if [ -n "$integration_branch" ]; then {
    git push "$remote" "$version^{commit}:refs/heads/$integration_branch"
} else {
    changelog=$(cat - CHANGELOG.rst <<EOF
fish ?.?.? (released ???)
=========================

EOF
    )
    printf %s\\n "$changelog" >CHANGELOG.rst
    git add CHANGELOG.rst
    CreateCommit "start new cycle"
    git push "$remote" HEAD:master
} fi

milestone_version="$(
    if echo "$version" | grep -q '\.0$'; then
        echo "$minor_version"
    else
        echo "$version"
    fi
)"
milestone_number() {
    gh_api_repo milestones?state=open |
        jq --arg name "fish $1" '
            .[] | select(.title == $name) | .number
        '
}
gh_api_repo milestones/"$(milestone_number "$milestone_version")" \
    --method PATCH --raw-field state=closed

next_minor_version=$(echo "$minor_version" |
    awk -F. '{ printf "%s.%s", $1, $2+1 }')
if [ -z "$(milestone_number "$next_minor_version")" ]; then
    gh_api_repo milestones --method POST \
        --raw-field title="fish $next_minor_version"
fi

exit

}
