# yamllint disable rule:line-length
# inspired by Poeschl/Hassio-Addons, optimized by ChatGPT
---
name: Builder

on:
  workflow_call:
  push:
    branches:
      - master
    paths:
      - "**/config.*"

env:
  BUILD_ARGS: ""

jobs:
  # 1. Detect which add-on folders changed (by config.json|yaml|yml modification)
  detect-changed-addons:
    if: ${{ github.repository_owner == 'alexbelgium' && !contains(github.event.head_commit.message, 'nobuild') }}
    runs-on: ubuntu-latest
    outputs:
      changedAddons: ${{ steps.find_addons.outputs.changed_addons }}
    steps:
      - name: Checkout repo
        uses: actions/checkout@v6
      - name: Find changed addon directories
        id: find_addons
        run: |
          git fetch origin "${{ github.event.before }}" || true
          changed_config_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" | grep -E '^[^/]+/config\.(json|ya?ml)$' || true)
          echo "Changed config files:"
          echo "$changed_config_files"
          changed_addons=$(echo "$changed_config_files" | awk -F/ '{print $1}' | sort -u | jq -R -s -c 'split("\n")[:-1]')
          echo "Changed addons: $changed_addons"
          echo "changed_addons=$changed_addons" >> "$GITHUB_OUTPUT"

  # 2. Pre-build sanitize: normalize spaces, fix script permissions, single commit per add-on
  prebuild-sanitize:
    if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
    needs: detect-changed-addons
    runs-on: ubuntu-latest
    strategy:
      matrix:
        addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
    steps:
      - uses: actions/checkout@v6
      # ────────────────────────────────────────────────────────────────
      # 1. Replace non-printable Unicode spaces  ␣  and
      #    convert Windows line endings (CRLF) → Unix (LF)
      # ────────────────────────────────────────────────────────────────
      - name: Sanitize text files (Unicode spaces + CRLF → LF)
        run: |
          cd "${{ matrix.addon }}"
          UNICODE_SPACES_REGEX=$'[\u00A0\u2002\u2003\u2007\u2008\u2009\u202F\u205F\u3000\u200B]'

          find . -type f | while read -r file; do
            MIME_TYPE=$(file --mime-type -b "$file")
            [[ "$MIME_TYPE" != text/* ]] && continue

            echo "Sanitizing $file"
            # Edit in place, preserving file permissions and metadata
            perl -i -CSD -pe "
              s/${UNICODE_SPACES_REGEX}/ /g;  # space normalization
              s/\r$//;                        # CRLF → LF
            " "$file"
          done
      # ────────────────────────────────────────────────────────────────
      # 2. Ensure every *.sh script is executable
      # ────────────────────────────────────────────────────────────────
      - name: Make all .sh scripts executable
        run: |
          cd "${{ matrix.addon }}"
          find . -type f -iname "*.sh" -exec chmod u+x {} \;
      # ────────────────────────────────────────────────────────────────
      # 3. Verify nothing with mixed line endings slipped through
      # ────────────────────────────────────────────────────────────────
      - name: Assert no mixed CRLF/LF remain
        uses: ymwymw/check-mixed-line-endings@v2
      # ────────────────────────────────────────────────────────────────
      # 4. Commit any changes we made
      # ────────────────────────────────────────────────────────────────
      - name: Commit if needed
        uses: EndBug/add-and-commit@v9
        with:
          commit: -u
          message: "GitHub bot: sanitize (spaces + LF endings) & chmod"
          default_author: github_actions
          pull: --rebase --autostash
          fetch: --tags --force

  # 3. Lint add-on configs
  lint_config:
    if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
    needs: [detect-changed-addons, prebuild-sanitize]
    runs-on: ubuntu-latest
    continue-on-error: true
    strategy:
      matrix:
        addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
    steps:
      - uses: actions/checkout@v6
      - name: Run Home Assistant Add-on Lint
        uses: frenck/action-addon-linter@v2
        with:
          path: "./${{ matrix.addon }}"

  # 4. Build images for changed addons/arches
  build:
    if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
    needs: [detect-changed-addons, lint_config]
    runs-on: ubuntu-latest
    environment: CR_PAT
    name: Build ${{ matrix.arch }} ${{ matrix.addon }} add-on
    strategy:
      matrix:
        addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
        arch: ["aarch64", "amd64", "armv7"]
    steps:
      - uses: actions/checkout@v6
      - name: Resolve Symlinks (in repo)
        run: |
          find . -type l | while read -r link; do
            target="$(readlink -f "$link")"
            if [ -z "$target" ]; then
              echo "Skipping broken symlink: $link"
              continue
            fi
            rm "$link"
            if [ -d "$target" ]; then
              mkdir -p "$link"
              cp -a "$target/." "$link/"
            else
              cp "$target" "$link"
            fi
          done
      - name: Get information
        id: info
        uses: home-assistant/actions/helpers/info@master
        with:
          path: "./${{ matrix.addon }}"
      - name: Check if Dockerfile exists
        id: dockerfile_check
        run: |
          if [ -f "./${{ matrix.addon }}/Dockerfile" ]; then
            echo "has_dockerfile=true" >> "$GITHUB_OUTPUT"
          else
            echo "No Dockerfile found in ${{ matrix.addon }}, skipping build."
            echo "has_dockerfile=false" >> "$GITHUB_OUTPUT"
          fi
      - name: Check if add-on should be built for arch
        id: check
        env:
          HEAD: "${{ github.head_ref }}"
        run: |
          if [[ "${{ steps.info.outputs.architectures }}" =~ ${{ matrix.arch }} ]]; then
             echo "build_arch=true" >> "$GITHUB_OUTPUT";
             echo "image=$(echo "${{ steps.info.outputs.image }}" | cut -d'/' -f3)" >> "$GITHUB_OUTPUT";
             if [[ -z "$HEAD" ]] && [[ "${{ github.event_name }}" == "push" ]]; then
                 echo "BUILD_ARGS=" >> "$GITHUB_ENV";
             fi
          else
             echo "${{ matrix.arch }} is not a valid arch for ${{ matrix.addon }}, skipping build";
             echo "build_arch=false" >> "$GITHUB_OUTPUT";
          fi
      - name: Login to GitHub Container Registry
        if: env.BUILD_ARGS != '--test'
        uses: docker/login-action@v3.6.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build ${{ matrix.addon }} add-on (attempt 1)
        id: builderstep1
        if: steps.check.outputs.build_arch == 'true' && steps.dockerfile_check.outputs.has_dockerfile == 'true'
        continue-on-error: true
        uses: home-assistant/builder@2025.11.0
        env:
          CAS_API_KEY: ${{ secrets.CAS_API_KEY }}
        with:
          args: |
            ${{ env.BUILD_ARGS }} \
            --${{ matrix.arch }} \
            --target "/data/${{ matrix.addon }}" \
            --image "${{ steps.check.outputs.image }}" \
            --docker-hub "ghcr.io/${{ github.repository_owner }}" \
            --addon

      - name: Wait 3 minutes before retry
        if: steps.check.outputs.build_arch == 'true' && steps.dockerfile_check.outputs.has_dockerfile == 'true' && steps.builderstep1.outcome == 'failure'
        run: sleep 180

      - name: Build ${{ matrix.addon }} add-on (attempt 2)
        if: steps.check.outputs.build_arch == 'true' && steps.dockerfile_check.outputs.has_dockerfile == 'true' && steps.builderstep1.outcome == 'failure'
        uses: home-assistant/builder@2025.11.0
        env:
          CAS_API_KEY: ${{ secrets.CAS_API_KEY }}
        with:
          args: |
            ${{ env.BUILD_ARGS }} \
            --${{ matrix.arch }} \
            --target "/data/${{ matrix.addon }}" \
            --image "${{ steps.check.outputs.image }}" \
            --docker-hub "ghcr.io/${{ github.repository_owner }}" \
            --addon

  # 5. Update changelog if needed (for each changed add-on)
  make-changelog:
    if: ${{ needs.detect-changed-addons.outputs.changedAddons != '' && needs.detect-changed-addons.outputs.changedAddons != '[]' }}
    needs: [detect-changed-addons, build]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1
      - name: Update changelog for minor versions
        run: |
          echo "Starting"
          git pull || true
          cd "${{ matrix.addon }}"
          if [ -f config.yaml ]; then
            version="$(sed -e '/version/!d' -e 's/.*version: //' config.yaml)"
          elif [ -f config.json ]; then
            version="$(sed -e '/version/!d' -e 's/.*[^"]*"\([^"]*\)"/\1/' config.json)"
            version="${version//,}"
          else
            exit 1
          fi
          version="${version//\"/}"
          version="${version//\'/}"
          version="$(echo "$version" | xargs)"
          if [[ "$version" == *"test"* ]]; then exit 0; fi
          touch CHANGELOG.md
          if ! grep -q "$version" CHANGELOG.md; then
            first_line="$(sed -n '/./p' CHANGELOG.md | head -n 1)"
            if [[ "$first_line" != "-"* ]]; then
              sed -i "1i\- Minor bugs fixed" CHANGELOG.md
            fi
            sed -i "1i\## $version ($(date '+%d-%m-%Y'))" CHANGELOG.md
          fi
      - name: Commit if needed
        uses: EndBug/add-and-commit@v9
        with:
          commit: -u
          message: "GitHub bot: changelog"
          default_author: github_actions
          pull: --rebase --autostash
          fetch: --force
          push: --force

  # 6. Revert if workflow fails
  revert-on-failure:
    if: failure()
    needs: [detect-changed-addons, prebuild-sanitize, lint_config, build]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
  
      - name: Revert the triggering commit
        run: |
          git config --global user.name "GitHub Actions"
          git config --global user.email "actions@github.com"
          git fetch origin
          git checkout master
          git pull origin master
          git revert --no-commit ${{ github.sha }}
          git commit -m "Revert '${{ github.event.head_commit.message }}' [nobuild]"
          git push origin master
