name: CI

on:
  schedule:
    # Check for update every day at 07:11
    - cron:  '11 7 * * *'
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

env:
  WORKING_BRANCH: ci/${{ github.ref_name }}
  PR_BRANCH: pr/ci/${{ github.ref_name }}

concurrency: 
  # Github Actions does not support ${{ env.WORKING_BRANCH }} as group name...
  group: ci/${{ github.ref_name }}

jobs:
  prepare:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          path: "bottle-components-repository"
          ref: ${{ github.ref_name }}
          fetch-depth: 0
      
      - name: Create or reset ${{ env.WORKING_BRANCH }}
        working-directory: "bottle-components-repository"
        shell: bash {0}
        run: |
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"
          pr_already_exists=$(gh pr list --app 'github-actions' --head ${{ env.PR_BRANCH }} --base ${{ github.ref_name }} --state 'open' --json number --jq '.[] | .[]')
          if ! [ -z "$pr_already_exists" ]; then
            declare -a BranchArray=( "${{ env.PR_BRANCH }}" "${{ env.WORKING_BRANCH }}" )
            status=0
            for BRANCH in ${BranchArray[@]}; do
              git checkout $BRANCH
              git rebase ${{ github.ref_name }}
              status=$?
              if [ $status -eq 0 ]; then
                git push -f origin $BRANCH
              else
                break
              fi
            done
            if [ $status -eq 0 ]; then
              exit 0
            fi
          fi
          git reset --hard ${{ github.ref_name }}
          git checkout -B ${{ env.PR_BRANCH }}
          git push -f --set-upstream origin ${{ env.PR_BRANCH }}
          git checkout -B ${{ env.WORKING_BRANCH }}
          mkdir patch_dir
          touch patch_dir/.gitkeep
          git add patch_dir/.gitkeep
          git commit -m "Add .gitkeep file"
          git push -f --set-upstream origin ${{ env.WORKING_BRANCH }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  pull:
    needs: prepare
    strategy:
      fail-fast: false
      matrix:
        components: [
            {order: 1 , repo : "doitsujin/dxvk"                  , project-id: 0       , workflow: "artifacts.yml"          , branch: "master", name-prefix: "dxvk-"         , name-suffix: ""                  , version-prefix: "v"        , Category: "dxvk"       , Sub-category: ""      , yaml-file: "14-dxvk.yml"         },
            {order: 2 , repo : "HansKristian-Work/vkd3d-proton"  , project-id: 0       , workflow: "artifacts.yml"          , branch: "master", name-prefix: "vkd3d-proton-" , name-suffix: ""                  , version-prefix: "v"        , Category: "vkd3d"      , Sub-category: ""      , yaml-file: "13-vkd3d-proton.yml" },
            {order: 3 , repo : "bottlesdevs/wine"                , project-id: 0       , workflow: ""                       , branch: "soda"  , name-prefix: "soda-"         , name-suffix: ""                  , version-prefix: "soda-"    , Category: "runners"    , Sub-category: "wine"  , yaml-file: "3-soda.yml"          },
            {order: 4 , repo : "bottlesdevs/wine"                , project-id: 0       , workflow: ""                       , branch: "caffe" , name-prefix: "caffe-"        , name-suffix: ""                  , version-prefix: "caffe-"   , Category: "runners"    , Sub-category: "wine"  , yaml-file: "4-caffe.yml"         },
            {order: 5 , repo : "GloriousEggroll/wine-ge-custom"  , project-id: 0       , workflow: ""                       , branch: "master", name-prefix: "wine-"         , name-suffix: ""                  , version-prefix: ""         , Category: "runners"    , Sub-category: "wine"  , yaml-file: "5-wine-GE-Proton.yml"},
            {order: 6 , repo : "GloriousEggroll/proton-ge-custom", project-id: 0       , workflow: ""                       , branch: "master", name-prefix: "GE-Proton"     , name-suffix: ""                  , version-prefix: "GE-Proton", Category: "runners"    , Sub-category: "proton", yaml-file: "10-GE-Proton.yml"    },
            {order: 7 , repo : "ishitatsuyuki/LatencyFleX"       , project-id: 0       , workflow: "artifact.yml"           , branch: "master", name-prefix: "latencyflex-"  , name-suffix: ""                  , version-prefix: ""         , Category: "latencyflex", Sub-category: ""      , yaml-file: "17-latencyflex.yml"  },
            {order: 8 , repo : "jp7677/dxvk-nvapi"               , project-id: 0       , workflow: "artifacts.yml"          , branch: "master", name-prefix: "dxvk-nvapi-"   , name-suffix: ""                  , version-prefix: ""         , Category: "nvapi"      , Sub-category: ""      , yaml-file: "16-dxvk-nvapi.yml"   },
            {order: 9 , repo : "Sporif/dxvk-async"               , project-id: 0       , workflow: ""                       , branch: "master", name-prefix: "dxvk-async-"   , name-suffix: ""                  , version-prefix: ""         , Category: "dxvk"       , Sub-category: ""      , yaml-file: "15-dxvk-async.yml"   },
            {order: 10, repo : "Kron4ek/Wine-Builds"             , project-id: 0       , workflow: ""                       , branch: "master", name-prefix: "wine-"         , name-suffix: "-staging-tkg-amd64", version-prefix: ""         , Category: "runners"    , Sub-category: "wine"  , yaml-file: "11-kron4ek.yml"      },
            {order: 11, repo : "Kron4ek/Wine-Builds"             , project-id: 0       , workflow: ""                       , branch: "master", name-prefix: "wine-"         , name-suffix: "-staging-amd64"    , version-prefix: ""         , Category: "runners"    , Sub-category: "wine"  , yaml-file: "11-kron4ek.yml"      },
            {order: 12, repo : "Kron4ek/Wine-Builds"             , project-id: 0       , workflow: ""                       , branch: "master", name-prefix: "wine-"         , name-suffix: "-amd64"            , version-prefix: ""         , Category: "runners"    , Sub-category: "wine"  , yaml-file: "11-kron4ek.yml"      },
            {order: 13, repo : "bottlesdevs/runtime"             , project-id: 0       , workflow: ""                       , branch: "main"  , name-prefix: "runtime-"      , name-suffix: ""                  , version-prefix: ""         , Category: "runtimes"   , Sub-category: ""      , yaml-file: "1-runtime.yml"       },
            {order: 14, repo : "Ph42oN/dxvk-gplasync"            , project-id: 43488626, workflow: ""                       , branch: "main"  , name-prefix: "dxvk-gplasync-", name-suffix: ""                  , version-prefix: ""         , Category: "dxvk"       , Sub-category: ""      , yaml-file: "15-dxvk-async.yml"   },
          ]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          path: "bottle-components-repository"
          ref: ${{ env.PR_BRANCH }}

      - uses: actions/setup-python@v5
        with:
          python-version: '3.x' 
          cache: 'pip'
          cache-dependency-path: 'bottle-components-repository/input_files/requirements-dev.txt'

      # Also needs python3-pip, zstd, gawk and gh packages which are pre-installed
      - run: pip install -r bottle-components-repository/input_files/requirements-dev.txt

      - name: Check for new release
        id: check-release
        shell: bash {0}
        run: |
          if [ ${{ matrix.components.project-id }} -ne 0 ]; then
            gitlab_json=$(curl -s https://gitlab.com/api/v4/projects/${{ matrix.components.project-id }}/releases)
            latest_release=$(echo -E "$gitlab_json" | jq -r ".[] | select(.tag_name | startswith(\"${{ matrix.components.version-prefix }}\")) | .tag_name" | head -1)
          else
            latest_release=$(gh release list -R ${{ matrix.components.repo }} | grep -m1 "${{ matrix.components.version-prefix }}" | awk '{print $(NF-1)}')
          fi
          if [ -z "$latest_release" ]; then
            echo "::error::Cannot find release tag begining by: ${{ matrix.components.version-prefix }}"
            exit 1
          fi
          asset_name=" "
          found=false
          i=-1
          until [ -z "$asset_name" ] || [ "$asset_name" = null ] || [ "$found" = true ]; do
            i=$((i + 1))
            if [ ${{ matrix.components.project-id }} -ne 0 ]; then
              asset_name=$(echo -E "$gitlab_json" | jq -r ".[] | if .tag_name == \"$latest_release\" then . else empty end | .assets | .links | .[$i] | .name")
            else
              asset_name=$(gh release view "$latest_release" --json assets -q ".[] | .[$i] | .name" -R ${{ matrix.components.repo }})
            fi
            if [[ "$asset_name" =~ ${{ matrix.components.name-prefix }}.*[0-9]+${{ matrix.components.name-suffix }}\.tar.* ]]; then
              found=true
            fi
          done
          if [ "$found" = false ]; then
            echo "::error::Cannot find a corresponding asset name."
            exit 1
          else
            if [ ${{ matrix.components.project-id }} -ne 0 ]; then
              timestamp=$(echo -E "$gitlab_json" | jq -r ".[] | if .tag_name == \"$latest_release\" then . else empty end | .created_at")
              asset_json_raw=$(echo -E "$gitlab_json" | jq -r ".[] | if .tag_name == \"$latest_release\" then . else empty end | .assets | .links | .[$i]")
              asset_json_raw=$(echo -E "$asset_json_raw" | jq -r ".[\"url\"] = .direct_asset_url")
              asset_json=$(echo -E "$asset_json_raw" | jq -c ". += {createdAt: \"$timestamp\"}")
            else
              asset_json=$(gh release view "$latest_release" --json assets -q ".[] | .[$i]" -R ${{ matrix.components.repo }})
            fi
            echo "asset=$asset_json" >> $GITHUB_OUTPUT
            echo "version=${latest_release#${{ matrix.components.version-prefix }}}" >> $GITHUB_OUTPUT
          fi
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: actions/checkout@v4
        with:
          repository: bottlesdevs/tools
          path: "tools"

      - name: Define environment variables
        run: |
          lowercase_component_name=$(echo "${{ matrix.components.name-prefix }}${{ steps.check-release.outputs.version }}${{ matrix.components.name-suffix }}" | tr "[:upper:]" "[:lower:]")
          name_prefix="$(echo ${{ matrix.components.name-prefix }} | tr '[:upper:]' '[:lower:]')"
          echo "NAMEPREFIX=$name_prefix" >> $GITHUB_ENV
          echo "NAMESUFFIX=$(echo ${{ matrix.components.name-suffix }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
          echo "NAME=$lowercase_component_name" >> $GITHUB_ENV
          echo "CATEGORY=${{ matrix.components.Category }}" >> $GITHUB_ENV
          echo "SUBCATEGORY=${{ matrix.components.Sub-category }}" >> $GITHUB_ENV
          echo "CHANNEL=stable" >> $GITHUB_ENV
          echo "YAML_FILENAME=${{ matrix.components.yaml-file }}" >> $GITHUB_ENV
          echo "CREATED_AT=${{ fromJSON(steps.check-release.outputs.asset).createdAt }}" >> $GITHUB_ENV
          echo "UPDATED=false" >> $GITHUB_ENV
          # Components specific tweaks
          if [[ "${{ matrix.components.repo }}" == "GloriousEggroll/wine-ge-custom" ]]; then
            url="${{ fromJSON(steps.check-release.outputs.asset).url }}"
            name="${url##*/wine-}"
            name="${name%.tar*}"
            name="${name%-x86_64}"
            if [[ "$name" == "lutris-ge-lol-"* ]]; then
              echo "NAMEPREFIX=lutris-ge-lol-" >> $GITHUB_ENV
              echo "NAME=$name" >> $GITHUB_ENV
            fi
          elif [[ "${{ matrix.components.repo }}" == "Kron4ek/Wine-Builds" ]]; then
            echo "NAMEPREFIX=kron4ek-$name_prefix" >> $GITHUB_ENV
            echo "NAME=kron4ek-$lowercase_component_name" >> $GITHUB_ENV
          fi

      - name: Update stable input_file entry
        working-directory: "bottle-components-repository"
        run: |
          # This script can set env.UPDATED to true
          ./input_files/update_yml_entries.sh

      - name: Create release tar archive
        if: env.UPDATED == 'true'
        working-directory: "bottle-components-repository"
        run: |
          release_archive_url=${{ fromJSON(steps.check-release.outputs.asset).url }}
          release_archive_name=${{ fromJSON(steps.check-release.outputs.asset).name }}
          if [[ $release_archive_name == *".tar.zst" ]]; then
            curl -fsSLO "$release_archive_url"
            tar --use-compress-program=unzstd -xvf $release_archive_name
            tar -zcvf "${{ env.NAME }}.tar.gz" "${{ env.NAME }}"
            echo "UPLOAD_FILE=$(pwd)/${{ env.NAME }}.tar.gz" >> $GITHUB_ENV
          elif [[ $release_archive_name == "dxvk-nvapi"* ]]; then
            curl -fsSLO "$release_archive_url"
            mkdir "${{ env.NAME }}"
            tar -xvf $release_archive_name --directory "${{ env.NAME }}"
            tar -zcvf "${{ env.NAME }}.tar.gz" "${{ env.NAME }}"
            echo "UPLOAD_FILE=$(pwd)/${{ env.NAME }}.tar.gz" >> $GITHUB_ENV
          fi

      - name: Check for new artifact
        id: download-artifact
        if: matrix.components.workflow != '' && env.UPDATED != 'true'
        shell: bash {0}
        run: |
          run_id=$(gh run list -b ${{ matrix.components.branch }} -w ${{ matrix.components.workflow }} -R ${{ matrix.components.repo }} --json databaseId -q '.[] | .databaseId' --status success -L 1)
          status=$?
          if [ $status -ne 0 ]; then
            echo "::error::Could not get run ID of last successful run."
            exit 1
          fi
          found_artifact=true
          gh run download -R ${{ matrix.components.repo }} $run_id -D artifact
          status=$?
          if [ $status -ne 0 ]; then
            echo "::warning::Could not download artifact, probably expired."
            found_artifact=false
          else
            artifact_info=$(gh run view -R ${{ matrix.components.repo }} $run_id --json headSha,createdAt -q '.')
            echo "artifact_info=$artifact_info" >> $GITHUB_OUTPUT
          fi
          echo "found_artifact=$found_artifact" >> $GITHUB_OUTPUT
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Update environment variables
        if: env.UPDATED != 'true' && steps.download-artifact.outputs.found_artifact == 'true'
        run: |
          commit_sha1="${{ fromJSON(steps.download-artifact.outputs.artifact_info).headSha }}"
          echo "NAME=${{ env.NAME }}-1-${commit_sha1::7}" >> $GITHUB_ENV
          echo "CHANNEL=unstable" >> $GITHUB_ENV
          echo "CREATED_AT=${{ fromJSON(steps.download-artifact.outputs.artifact_info).createdAt }}" >> $GITHUB_ENV

      - name: Update unstable input_file entry
        if: env.UPDATED != 'true' && steps.download-artifact.outputs.found_artifact == 'true'
        working-directory: "bottle-components-repository"
        run: |
          # This script can set env.UPDATED to true
          ./input_files/update_yml_entries.sh

      - name: Create artifact tar archive
        if: env.UPDATED == 'true' && steps.download-artifact.outputs.found_artifact == 'true'
        working-directory: "bottle-components-repository"
        run: |
          folder=$(find "${{ github.workspace }}/artifact" -type d -name "${{ env.NAMEPREFIX }}*")
          # Components specific tweaks
          if [[ "${{ env.NAMEPREFIX }}" == "dxvk"* ]]; then
            rm -rf "$folder/dxvk-native"*
          fi
          mkdir "${{ env.NAME }}"
          mv "$folder"/* "${{ env.NAME }}"
          tar -zcvf "${{ env.NAME }}.tar.gz" "${{ env.NAME }}"
          echo "UPLOAD_FILE=$(pwd)/${{ env.NAME }}.tar.gz" >> $GITHUB_ENV

      - name: Upload component archive
        id: upload-component
        if: env.UPLOAD_FILE != ''
        working-directory: "bottle-components-repository"
        shell: bash {0}
        run: |
          gh release create "${{ env.NAME }}" ${{ env.UPLOAD_FILE }} -n ""
          status=$?
          if [ $status -ne 0 ]; then
            gh release upload "${{ env.NAME }}" ${{ env.UPLOAD_FILE }} --clobber
            status=$?
            if [ $status -ne 0 ]; then
              echo "::error::Could not upload component."
            fi
          fi
          asset=$(gh release view "${{ env.NAME }}" --json assets -q ".[] | .[0]")
          echo "asset=$asset" >> $GITHUB_OUTPUT
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name : Generate appropriate YAML file
        if: env.UPDATED == 'true'
        working-directory: "bottle-components-repository"
        run: |
          repo=${{ matrix.components.repo }}
          name=${{ env.NAME }}
          if [ "${{ env.UPLOAD_FILE }}" != "" ]; then
            url="${{ steps.upload-component.outputs.asset && fromJSON(steps.upload-component.outputs.asset).url }}"
            path="${{ env.UPLOAD_FILE }}"
          else
            url="${{ fromJSON(steps.check-release.outputs.asset).url }}"
          fi
          python "${{ github.workspace }}/tools/MaintainersHelpers/component-generator.py" "components" "CI" "$name" "${repo%/*}" "${{ env.CHANNEL }}" "$url" $path
          # Components specific tweaks
          if [[ "$repo" == "GloriousEggroll"* ]]; then
            source="${url##*/}"
            source="${source#wine-}"
            source="${source%.tar*}"
            source="${source%-x86_64}"
            echo "Post:" >> "$name.yml"
            echo -e "- action: rename" >> "$name.yml"
            echo -e "  source: $source" >> "$name.yml"
            echo -e "  dest: $name" >> "$name.yml"
          elif [[ "$repo" == "Kron4ek/Wine-Builds" ]]; then
            echo "Post:" >> "$name.yml"
            echo -e "- action: rename" >> "$name.yml"
            echo -e "  source: ${name#kron4ek-}" >> "$name.yml"
            echo -e "  dest: $name" >> "$name.yml"
          fi
          mv "$name.yml" "${{ env.CATEGORY }}/${{ env.SUBCATEGORY }}"

      - name : Commit changes
        if: env.UPDATED == 'true'
        working-directory: "bottle-components-repository"
        timeout-minutes: 10
        shell: bash {0}
        run: |
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"
          git add "${{ env.CATEGORY }}/${{ env.SUBCATEGORY }}/${{ env.NAME }}.yml" "input_files/${{ env.YAML_FILENAME }}"
          git commit -m "Add ${{ env.NAME }}"
          file_name="$(date -d "${{ github.event.created_at }}" +%s)-${{ matrix.components.order }}-${{ env.NAME }}.patch"
          git diff HEAD~1 HEAD > "$file_name"
          git fetch origin ${{ env.WORKING_BRANCH }}:${{ env.WORKING_BRANCH }}
          git switch ${{ env.WORKING_BRANCH }}
          git branch --set-upstream-to=origin/${{ env.WORKING_BRANCH }} ${{ env.WORKING_BRANCH }}
          mv "$file_name" patch_dir
          git add patch_dir/*.patch
          git pull
          git commit -m "Add ${{ env.NAME }}"
          git push
          success=$?
          until [ $success -eq 0 ]; do
            git pull --rebase;
            git push
            success=$?
          done

  finish:
    needs: [prepare, pull]
    if: always() && needs.prepare.result == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          path: "bottle-components-repository"
          ref: ${{ env.WORKING_BRANCH }}
          fetch-depth: 0

      - name: Generate index.yml
        working-directory: "bottle-components-repository"
        shell: bash {0}
        run: |
          git checkout ${{ github.ref_name }}
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"
          git merge ${{ env.WORKING_BRANCH }} --no-commit --no-ff
          git reset
          readarray -d '' entries < <(printf '%s\0' patch_dir/*.patch | sort -zV)
          for entry in "${entries[@]}"; do
            patch -p1 --fuzz 5 < "$entry"
            git add -- . ':!patch_dir' ':!**.orig'
            entry=${entry#patch_dir/*-[0-9]*-}
            git commit -a -m "Add component ${entry%\.patch}"
          done
          ./generate_index.sh
          git add index.yml
          git commit -m "Generate index.yml" || true

      - name: Create Pull Request
        id: cpr
        working-directory: "bottle-components-repository"
        shell: bash {0}
        run: |
          git checkout -B ${{ env.PR_BRANCH }}
          git push -f --set-upstream origin ${{ env.PR_BRANCH }}

          gh_output=$(gh pr create --title "Update available :tada:" --body "One or more components have been updated!" --head ${{ env.PR_BRANCH }} --base ${{ github.ref_name }})
          echo "PR_CREATED=$([ $? -eq 0 ] && echo true || echo false)" >> $GITHUB_OUTPUT
          pr_number=$($gh_output | tail -n1)
          echo "PR_NUMBER=${pr_number#https://github.com/${{ github.repository }}/pull/}" >> $GITHUB_OUTPUT
        env:
          # PAT is not necessary unless auto-merge is used
          # GH_TOKEN: ${{secrets.PAT}}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Enable Pull Request Automerge
        if: false
        # if: steps.cpr.outputs.PR_CREATED == 'true'
        run: gh pr merge --merge --auto "${{ steps.cpr.outputs.PR_NUMBER }}"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Auto approve
        if: false
        # if: steps.cpr.outputs.PR_CREATED == 'true'
        run: gh pr review --approve "${{ steps.cpr.outputs.PR_NUMBER }}"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}