name: CI-wasm

# Builds the Magic WebAssembly target (both the non-TCL and TCL variants)
# on every push and pull request as a CI check. **Publishing** only happens
# when a release tag of the form <x.y.z>... (optionally v-prefixed) is
# pushed — the same tag that triggers the AppImage release workflows:
#
#   # bump magic/VERSION, commit, push to default branch
#   git tag 8.3.638
#   git push origin 8.3.638
#
# The tag name (minus any leading "v") provides the base; the workflow appends
# the commit date and short SHA: 8.3.799 → 8.3.799020261231+git01234cde.
# Forks publish under their own namespace via the @<owner>/ scope.

on:
  push:
  pull_request:
  workflow_dispatch:
    inputs:
      emsdk_version:
        description: 'emsdk version to build with (default: latest; pin a version number to bisect)'
        type: string
        default: 'latest'
      tcl_ref:
        description: 'TCL ref to build against (default: auto-resolve latest stable tag). Use a tag like core-9-0-3, a branch, or a commit SHA to bisect a regression.'
        type: string
        default: ''
      tcl_repo_url:
        description: 'TCL repository URL (default: https://github.com/tcltk/tcl.git)'
        type: string
        default: ''
      dry_run:
        description: 'Dry run: pack only, do not publish even on tag pushes'
        type: boolean
        default: true

permissions:
  contents: read
  packages: write

jobs:
  build-wasm:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 2

      - name: Set up Node.js
        uses: actions/setup-node@v7
        with:
          node-version: '24'
          registry-url: 'https://npm.pkg.github.com'

      - name: Install emsdk
        env:
          # Defaults to latest so CI tracks emsdk HEAD and catches breakage early.
          # Override via workflow_dispatch to pin a specific version when needed
          # (e.g. to bisect a regression or verify a post-build.sh patch still applies).
          EMSDK_VERSION: ${{ github.event.inputs.emsdk_version || 'latest' }}
        run: |
          git clone https://github.com/emscripten-core/emsdk.git
          cd emsdk
          ./emsdk install "$EMSDK_VERSION"
          ./emsdk activate "$EMSDK_VERSION"

      # Dump native + emscripten preprocessor defines. Useful for diagnosing
      # WASM-build differences after an emsdk bump.
      - name: Emscripten Diagnostic
        run: |
          source ./emsdk/emsdk_env.sh
          echo "===== gcc -dM -E - =====";  echo | gcc  -dM -E - | sort
          echo "===== g++ -dM -E - =====";  echo | g++  -dM -E - | sort
          echo "===== emcc -dM -E - ====="; echo | emcc -dM -E - | sort
          echo "===== em++ -dM -E - ====="; echo | em++ -dM -E - | sort

      # Determine which TCL ref to build against.
      # Priority: workflow_dispatch input > auto-resolved latest stable tag.
      # TCL stable releases follow the core-<major>-<even_minor>-<patch>
      # naming convention; core-9-0-x is the current stable series.
      # Falls back to main only if no release tags are found at all.
      - name: Resolve TCL ref
        id: resolve-tcl
        env:
          TCL_REPO_URL: ${{ github.event.inputs.tcl_repo_url || 'https://github.com/tcltk/tcl.git' }}
          TCL_REF_INPUT: ${{ github.event.inputs.tcl_ref || '' }}
        run: |
          if [ -n "$TCL_REF_INPUT" ]; then
            TCL_REF="$TCL_REF_INPUT"
            echo "Using workflow_dispatch TCL_REF: $TCL_REF"
          else
            TCL_REF=$(git ls-remote --tags --sort=-version:refname "$TCL_REPO_URL" \
                        'refs/tags/core-9-0-*' \
                      | grep -v '\^{}' \
                      | grep -P "core-.*?-\d+$" \
                      | sort -k +2 -r -V \
                      | head -1 \
                      | awk '{print $2}' \
                      | sed 's|refs/tags/||')
            if [ -z "$TCL_REF" ]; then
              TCL_REF=main
              echo "Warning: no stable core-9-0-x tag found, falling back to main"
            else
              echo "Auto-resolved latest stable TCL tag: $TCL_REF"
            fi
          fi
          echo "tcl_ref=$TCL_REF"           >> "$GITHUB_OUTPUT"
          echo "tcl_repo_url=$TCL_REPO_URL" >> "$GITHUB_OUTPUT"

      # Clone tcltk/tcl into a sibling directory at the resolved ref.
      # Done as an explicit step so the exact commit is visible in the job
      # log. The source tree is read-only — the WASM build runs inside magic.
      - name: Clone tcltk/tcl
        env:
          TCL_REPO_URL: ${{ steps.resolve-tcl.outputs.tcl_repo_url }}
          TCL_REF:      ${{ steps.resolve-tcl.outputs.tcl_ref }}
        run: |
          # autocrlf=false: ubuntu-latest is already LF, but make it explicit.
          git -c core.autocrlf=false clone "$TCL_REPO_URL" ../tcl
          cd ../tcl
          git checkout --detach "$TCL_REF"
          echo "=== TCL commit ==="
          git log -n1 --format="commit %H%nauthor  %an <%ae>%ndate    %ci%nref     %D%n%n    %s"

      - name: Build WASM — both variants (tcl + notcl)
        env:
          TCL_REF:      ${{ steps.resolve-tcl.outputs.tcl_ref }}
          TCL_REPO_URL: ${{ steps.resolve-tcl.outputs.tcl_repo_url }}
        run: |
          source ./emsdk/emsdk_env.sh
          bash npm/build.sh --variant=both

      - name: Run example tests (non-TCL variant)
        run: cd npm && npm test

      - name: Run full test suite (TCL variant)
        run: cd npm && npm run test:tcl

      # Dump generated text outputs (.ext, .spice, .cif, …) into the CI log
      # so a regression in extraction / netlisting / cifoutput is visible
      # without having to download artifacts. The .gds output is binary —
      # skip it and just record its size.
      - name: Display example outputs
        run: |
          shopt -s nullglob
          for dir in npm/examples/output npm/examples/output-tcl; do
            [ -d "$dir" ] || continue
            echo "======== $dir ========"
            for f in "$dir"/*; do
              name=$(basename "$f")
              case "$f" in
                *.gds) echo "===== $name (binary, $(wc -c < "$f") bytes — skipped) =====" ;;
                *)     echo "===== $name ====="; cat "$f" ;;
              esac
            done
          done

      # The release gate. We publish a new npm version only when a tag of the
      # shape <x.y.z>... (optionally v-prefixed) is pushed, matching the
      # AppImage release workflows so one tag releases everything.
      #
      # Version scheme (per dmiles' recommendation):
      #   {MAJOR}.{MINOR}.{PATCH}0{YYYYMMDD}+git{SHORT_SHA}
      # e.g. 8.3.799 pushed on 2026-12-31 → 8.3.799020261231+git01234cde
      #
      # The leading zero between PATCH and date keeps the number readable and
      # ensures correct numeric ordering: 799020261231 < 800020261231.
      # Build metadata (+git...) is ignored by npm for comparison but retained
      # for traceability. Security patches for the 799 series can be inserted
      # as later dates (799020270101, 799020270201, …) and are matched by the
      # range <=8.3.799900000000 or <8.3.8000000000000.
      - name: Determine release version (tag-driven only)
        id: release
        run: |
          date=$(git show -s --format=%cs | tr -d '-')
          hash=$(git show -s --format=%h)
          if [ "${{ github.event_name }}" = "push" ] && \
             echo "${{ github.ref }}" | grep -Eq '^refs/tags/v?[0-9]+\.[0-9]+\.[0-9]+'; then
            tag="${GITHUB_REF#refs/tags/}"
            base="${tag#v}"
            echo "publish=true"                              >> "$GITHUB_OUTPUT"
            echo "version=${base}0${date}+git${hash}"       >> "$GITHUB_OUTPUT"
            echo "Tag release: $tag → npm version ${base}0${date}+git${hash}"
          else
            base=$(cat VERSION)
            echo "publish=false"                             >> "$GITHUB_OUTPUT"
            echo "version=${base}0${date}+git${hash}"       >> "$GITHUB_OUTPUT"
            echo "Non-tag build: will not publish."
          fi

      - name: Set package version and scope
        env:
          VERSION: ${{ steps.release.outputs.version }}
        run: |
          # Scope the package to the repo owner so it lands in the right
          # GitHub Packages namespace regardless of who hosts the repo.
          SCOPED_NAME="@${{ github.repository_owner }}/magic-vlsi-wasm"
          cd npm
          npm pkg set name="$SCOPED_NAME"
          npm pkg set publishConfig.registry="https://npm.pkg.github.com"
          npm version "$VERSION" --no-git-tag-version --allow-same-version

      - name: Pack
        run: |
          mkdir -p npm_pack_dest
          npm/pack.sh --pack-destination "${GITHUB_WORKSPACE}/npm_pack_dest"
          # Guessing a little at the filename we get out
          filename=$(find "${GITHUB_WORKSPACE}/npm_pack_dest" -mindepth 1 -maxdepth 1 -type f -name "*.tgz" -printf "%f\n" | head -1)

          if [ -n "$filename" ]
          then
            echo "NPM_MODULE_FILENAME=$filename" >> $GITHUB_ENV

            mkdir -p npm_pack
            tar -zxvf "${GITHUB_WORKSPACE}/npm_pack_dest/$filename" -C npm_pack
          fi
          if [ -n "$GITHUB_REPOSITORY_OWNER" ]
          then
            echo "NPM_MODULE_PREFIX=${GITHUB_REPOSITORY_OWNER}-" >> $GITHUB_ENV
          fi

      - name: Upload tarball as artifact
        uses: actions/upload-artifact@v7
        if: env.NPM_MODULE_FILENAME != ''
        with:
          path: npm_pack_dest/${{ env.NPM_MODULE_FILENAME }}
          archive: false

      - name: Upload npm module contents as standard artifact zip
        uses: actions/upload-artifact@v7
        if: env.NPM_MODULE_FILENAME != ''
        with:
          name: ${{ env.NPM_MODULE_PREFIX }}magic-vlsi-wasm-npm-${{ steps.release.outputs.version }}
          path: npm_pack

      - name: Publish to GitHub Packages
        if: steps.release.outputs.publish == 'true' && github.event.inputs.dry_run != 'true'
        run: cd npm && npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Write a Markdown summary visible next to the artifacts on the Actions
      # page. Captures the exact versions used so a future regression can be
      # bisected without scrolling through raw logs.
      - name: Build summary
        if: always()
        env:
          TCL_REF:      ${{ steps.resolve-tcl.outputs.tcl_ref }}
          TCL_REPO_URL: ${{ steps.resolve-tcl.outputs.tcl_repo_url }}
          VERSION:      ${{ steps.release.outputs.version }}
        run: |
          source ./emsdk/emsdk_env.sh 2>/dev/null || true
          EMCC_VER=$(emcc --version 2>/dev/null | head -1 || echo "unavailable")
          GCC_VER=$(gcc  --version 2>/dev/null | head -1 || echo "unavailable")
          NODE_VER=$(node --version 2>/dev/null || echo "unavailable")
          MAGIC_VER=$(cat VERSION 2>/dev/null || echo "unavailable")
          NPM_MODULE_FILENAME=${NPM_MODULE_FILENAME:-unavailable}
          if [ -d ../tcl/.git ]; then
            TCL_SHA=$(cd ../tcl && git rev-parse HEAD)
            TCL_DATE=$(cd ../tcl && git log -1 --format="%ci")
            TCL_SUBJECT=$(cd ../tcl && git log -1 --format="%s")
          else
            TCL_SHA="(not cloned)"; TCL_DATE=""; TCL_SUBJECT=""
          fi
          printf '## Build info: %s\n\n'    "$VERSION"                >> "$GITHUB_STEP_SUMMARY"
          printf '| Component | Details |\n'                          >> "$GITHUB_STEP_SUMMARY"
          printf '|-----------|----------|\n'                         >> "$GITHUB_STEP_SUMMARY"
          printf '| Output | `%s` |\n'      "$NPM_MODULE_FILENAME"   >> "$GITHUB_STEP_SUMMARY"
          printf '| Magic | `%s` |\n'       "$MAGIC_VER"             >> "$GITHUB_STEP_SUMMARY"
          printf '| Emscripten | %s |\n'    "$EMCC_VER"              >> "$GITHUB_STEP_SUMMARY"
          printf '| GCC | %s |\n'           "$GCC_VER"               >> "$GITHUB_STEP_SUMMARY"
          printf '| Node.js | %s |\n'       "$NODE_VER"              >> "$GITHUB_STEP_SUMMARY"
          printf '| TCL repo | %s |\n'      "$TCL_REPO_URL"          >> "$GITHUB_STEP_SUMMARY"
          printf '| TCL ref | `%s` |\n'     "$TCL_REF"               >> "$GITHUB_STEP_SUMMARY"
          printf '| TCL commit | `%s` |\n'  "$TCL_SHA"               >> "$GITHUB_STEP_SUMMARY"
          printf '| TCL date | %s |\n'      "$TCL_DATE"              >> "$GITHUB_STEP_SUMMARY"
          printf '| TCL subject | %s |\n'   "$TCL_SUBJECT"           >> "$GITHUB_STEP_SUMMARY"
