---
name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

env:
  FORCE_COLOR: "1" # Make tools pretty.
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  PIP_NO_PYTHON_VERSION_WARNING: "1"

permissions: {}

jobs:
  build-package:
    name: Build & verify package
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          fetch-depth: 0
          persist-credentials: false

      - uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0
        id: baipp

    outputs:
      # Used to define the matrix for tests below. The value is based on
      # packaging metadata (trove classifiers).
      python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }}

  tests:
    name: Tests & Mypy API on ${{ matrix.python-version }}
    runs-on: ubuntu-latest
    needs: build-package

    strategy:
      fail-fast: false
      matrix:
        # Created by the build-and-inspect-python-package action above.
        python-version: ${{ fromJson(needs.build-package.outputs.python-versions) }}

    env:
      PYTHON: ${{ matrix.python-version }}

    steps:
      - name: Download pre-built packages
        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
        with:
          name: Packages
          path: dist
      - run: |
          tar xf dist/*.tar.gz --strip-components=1
          rm -rf src
      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
        with:
          python-version: ${{ matrix.python-version }}
          allow-prereleases: true
      - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0

      - name: Run tests
        run: >
          uvx --with tox-uv tox run
          --installpkg dist/*.whl
          -f py${PYTHON//./}-tests

      - name: Upload coverage data
        uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
        with:
          name: coverage-data-${{ matrix.python-version }}
          path: .coverage.*
          include-hidden-files: true
          if-no-files-found: ignore

      - name: Check public API with Mypy
        run: >
          uvx --with tox-uv tox run
          --installpkg dist/*.whl
          -e py${PYTHON//./}-mypy

  coverage:
    name: Ensure 100% test coverage
    runs-on: ubuntu-latest
    needs: tests
    if: always()

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false
      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
        with:
          python-version-file: .python-version-default
      - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0

      - name: Download coverage data
        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
        with:
          pattern: coverage-data-*
          merge-multiple: true

      - name: Combine coverage and fail if it's <100%.
        run: |
          uv tool install coverage

          coverage combine
          coverage html --skip-covered --skip-empty

          # Report and write to summary.
          coverage report --format=markdown >> $GITHUB_STEP_SUMMARY

          # Report again and fail if under 100%.
          coverage report --fail-under=100

      - name: Upload HTML report if check failed.
        uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
        with:
          name: html-report
          path: htmlcov
        if: ${{ failure() }}

  typing:
    name: Check types using supported type checkers
    runs-on: ubuntu-latest
    needs: build-package

    steps:
      - name: Download pre-built packages
        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
        with:
          name: Packages
          path: dist
      - run: tar xf dist/*.tar.gz --strip-components=1
      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
        with:
          python-version-file: .python-version-default
      - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0

      - run: >
          uvx --with tox-uv
          tox run -f typing

  docs:
    name: Run doctests
    needs: build-package
    runs-on: ubuntu-latest
    steps:
      - name: Download pre-built packages
        uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
        with:
          name: Packages
          path: dist
      - run: tar xf dist/*.tar.gz --strip-components=1
      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
        with:
          # Keep in sync with tox.ini/docs & .readthedocs.yaml
          python-version: "3.13"
      - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0

      - run: >
          uvx --with tox-uv
          tox run -e docs-doctests

  install-dev:
    name: Verify dev env
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false
      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
        with:
          python-version-file: .python-version-default
      - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0

      - run: uv venv
      - run: uv pip install -e . --group dev

      - run: .venv/bin/python -Ic 'import structlog; print(structlog.__version__)'
        if: runner.os != 'Windows'
      - run: .\.venv\Scripts\python.exe -Ic 'import structlog; print(structlog.__version__)'
        if: runner.os == 'Windows'

      # Exercise the terminal initialization that we don't test otherwise.
      - run: uv pip install colorama
        if: runner.os == 'Windows'
      - run: .\.venv\Scripts\python.exe -Ic 'import structlog; log = structlog.get_logger(); log.info("colors"); structlog.dev.ConsoleRenderer.get_active().colors = False; log.info("bw")'
        if: runner.os == 'Windows'

  required-checks-pass:
    name: Ensure everything required is passing for branch protection
    if: always()

    needs:
      - coverage
      - install-dev
      - typing
      - docs

    runs-on: ubuntu-latest

    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
        with:
          jobs: ${{ toJSON(needs) }}

  colors:
    name: Visual check for color settings using env variables
    needs: build-package
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false
      - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
        with:
          python-version-file: .python-version-default
      - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0

      - run: >
          uvx --with=tox-uv
          tox run
          -f color
