on:
  push:
    branches:
      - "main"
      - "standalone"
      - "feature/update**"
      - "feature/server_esm**"
    paths-ignore:
      - "docs/**"
      - "bin/**"
    tags:
      - "v*"
  workflow_dispatch:

env:
  GHCR_REGISTRY: ghcr.io
  DOCKERHUB_REGISTRY: docker.io
  IMAGE_NAME: ${{ github.repository}}
  TEST_TAG: ${{ github.repository}}:test

permissions:
  contents: read
  packages: write

jobs:
  test_docker:
    name: Check Docker build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - dockerfile: Dockerfile.alpine
          - dockerfile: Dockerfile
    steps:
      - name: Checkout the repository
        uses: actions/checkout@v6

      - name: Set IMAGE_NAME to lowercase
        run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
      - name: Set TEST_TAG to lowercase
        run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - uses: pnpm/action-setup@v6
      - name: Set up node & dependencies
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: "pnpm"

      - name: Install npm dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright Browsers
        run: pnpm exec playwright install --with-deps

      - name: Run the TypeScript build
        run: pnpm run server:build

      - name: Build and export to Docker
        uses: docker/build-push-action@v7
        with:
          context: apps/server
          file: apps/server/${{ matrix.dockerfile }}
          load: true
          tags: ${{ env.TEST_TAG }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Validate container run output
        run: |
          CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./apps/server/spec/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }})
          echo "Container ID: $CONTAINER_ID"

      - name: Wait for the healthchecks to pass
        uses: stringbean/docker-healthcheck-action@v3
        with:
          container: trilium_local
          wait-time: 50
          require-status: running
          require-healthy: true

      - name: Run Playwright tests
        run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm --filter=server e2e

      - name: Upload Playwright trace
        if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: Playwright trace (${{ matrix.dockerfile }})
          path: test-output/playwright/output

      - uses: actions/upload-artifact@v7
        if: ${{ !cancelled() }}
        with:
          name: Playwright report (${{ matrix.dockerfile }})
          path: playwright-report/
          retention-days: 30

      # Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
      - name: Print entire log
        if: always()
        run: |
          journalctl -u docker CONTAINER_NAME=trilium_local --no-pager

  build:
    name: Build Docker images
    strategy:
      fail-fast: false
      matrix:
        include:
          - dockerfile: Dockerfile.alpine
            platform: linux/amd64
            image: ubuntu-latest
          - dockerfile: Dockerfile
            platform: linux/arm64
            image: ubuntu-24.04-arm
          - dockerfile: Dockerfile.legacy
            platform: linux/arm/v7
            image: ubuntu-24.04-arm
          - dockerfile: Dockerfile.legacy
            platform: linux/arm/v8
            image: ubuntu-24.04-arm
    runs-on: ${{ matrix.image }}
    needs:
      - test_docker
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    steps:
      - name: Prepare
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
      - name: Set IMAGE_NAME to lowercase
        run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
      - name: Set TEST_TAG to lowercase
        run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV

      - name: Checkout repository
        uses: actions/checkout@v6
      - uses: pnpm/action-setup@v6
      - name: Set up node & dependencies
        uses: actions/setup-node@v6
        with:
          node-version: 24
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Update build info
        run: pnpm run chore:update-build-info

      - name: Update nightly version
        if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
        run: pnpm run chore:ci-update-nightly-version

      - name: Run the TypeScript build
        run: pnpm run server:build

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=tag
            type=sha
          flavor: |
            latest=false

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@v7
        with:
          context: apps/server
          file: apps/server/${{ matrix.dockerfile }}
          platforms: ${{ matrix.platform }}
          labels: ${{ steps.meta.outputs.labels }}
          outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true

      - name: Export digest
        run: |
          mkdir -p /tmp/digests
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@v7
        with:
          name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    name: Merge manifest lists
    runs-on: ubuntu-latest
    needs:
      - build
    steps:
      - name: Download digests
        uses: actions/download-artifact@v8
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true
      - name: Set IMAGE_NAME to lowercase
        run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
      - name: Set TEST_TAG to lowercase
        run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV

      - name: Set up crane
        uses: imjasonh/setup-crane@v0.5

      - name: Login to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          registry: ${{ env.DOCKERHUB_REGISTRY }}
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=ref,event=tag
            type=sha
          flavor: |
            latest=false

      - name: Verify digests exist on GHCR
        working-directory: /tmp/digests
        run: |
          echo "Verifying all digests are available on GHCR..."
          for DIGEST_FILE in *; do
            DIGEST="sha256:${DIGEST_FILE}"
            echo -n "  ${DIGEST}: "
            crane manifest "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" > /dev/null
            echo "OK"
          done

      - name: Create and push multi-arch manifest
        working-directory: /tmp/digests
        run: |
          GHCR_IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}"
          DOCKERHUB_IMAGE="${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}"

          # Build -m flags for crane index append from digest files
          MANIFEST_ARGS=""
          for d in *; do
            MANIFEST_ARGS="${MANIFEST_ARGS} -m ${GHCR_IMAGE}@sha256:${d}"
          done

          # Create multi-arch manifest for each tag from metadata, plus copy to DockerHub
          while IFS= read -r TAG; do
            echo "Creating manifest: ${TAG}"
            crane index append ${MANIFEST_ARGS} -t "${TAG}"

            SUFFIX="${TAG#*:}"
            echo "Copying to DockerHub: ${DOCKERHUB_IMAGE}:${SUFFIX}"
            crane copy "${TAG}" "${DOCKERHUB_IMAGE}:${SUFFIX}"
          done <<< "${{ steps.meta.outputs.tags }}"

          # For stable releases (tags without hyphens), also create stable + latest
          REF_NAME="${GITHUB_REF#refs/tags/}"
          if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then
            echo "Creating stable tags..."
            crane index append ${MANIFEST_ARGS} -t "${GHCR_IMAGE}:stable"
            crane copy "${GHCR_IMAGE}:stable" "${DOCKERHUB_IMAGE}:stable"

            echo "Creating latest tags..."
            crane copy "${GHCR_IMAGE}:stable" "${GHCR_IMAGE}:latest"
            crane copy "${GHCR_IMAGE}:latest" "${DOCKERHUB_IMAGE}:latest"
          fi

      - name: Inspect manifests
        run: |
          REF_NAME="${GITHUB_REF#refs/heads/}"
          REF_NAME="${REF_NAME#refs/tags/}"
          echo "=== GHCR ==="
          crane manifest "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME}"
          echo ""
          echo "=== DockerHub ==="
          crane manifest "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME}"
