name: Build WinPython Distribution for 3.14
# matrix allows to avoid duplicated code in github_workflows_build-dot, github_workflows_build-whl, github_workflows_build-slim
# 2025-08-23a: merged code proposed per Gemini 2.5 flash, it corrected also PYTHONIOENCODING="utf-8" error
# 2025-08-23b: moved manually to Python-3.14.0rc2
# 2025-08-25a: will use manual uploaded free-threading, as astral doesn't provide
# 2025-08-25b: move up all parameters  
on:
  workflow_dispatch:

jobs:
  build-winpython:
    runs-on: windows-latest
    
    strategy:
      fail-fast: true # Allows other matrix jobs to complete even if one fails
      matrix:
        # Define different WinPython build configurations
        flavor_config:
          # WinPython Dot configuration
          - FLAVOR: "dot"
            REQUIREMENTS: "winpython\\portable\\cycle_2025_04\\requir.64-3_14_0_1dotb3.txt"
            REQUIREMENTS_WHL: ""
            ZIP: "1"
            SEVEN_Z: "0"
            EXE: "1"
            PANDOC: "0"
            ARTIFACT_NAME: "publish_314dot"
            python_sourcef: "https://github.com/astral-sh/python-build-standalone/releases/download/20250818/cpython-3.14.0rc2+20250818-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
            python_sha256f: "361a49531a5d4bdc72bbccc81f7b5881b5eb751e3b47683b233cacae5aeeaeed"
            WINPYARCH: "64"
            WINPYVERSION: "314"
            WINPYVER2: "3.14.0.1"
            my_release_level: "b4"
            build_location: "WPy64-31401b4"


          - FLAVOR: "free"
            REQUIREMENTS: "winpython\\portable\\cycle_2025_04\\requir.64-3_14_0_1dotb3.txt"
            REQUIREMENTS_WHL: ""
            ZIP: "1"
            SEVEN_Z: "0"
            EXE: "1"
            PANDOC: "0"
            ARTIFACT_NAME: "publish_314free"
            python_sourcef: "https://github.com/winpython/winpy.github.io/releases/download/20250825/cpython-3.14.0rc2+20250825-x86_64-pc-windows-msvc-freethreaded-install_only_stripped.tar.gz"
            python_sha256f: "dda491993214ae78948297848aa9d75d1d10762c33ff16487793ca5205516e4a"
            WINPYARCH: "64"
            WINPYVERSION: "314"
            WINPYVER2: "3.14.0.1"
            my_release_level: "b4"
            build_location: "WPy64-31401b4"

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set static variables and Python version
        shell: bash
        run: |
          # Set matrix-specific variables
          echo "WINPYARCH=${{ matrix.flavor_config.WINPYARCH }}" >> $GITHUB_ENV
          echo "WINPYVERSION=${{ matrix.flavor_config.WINPYVERSION }}" >> $GITHUB_ENV

          echo "build_location=${{ matrix.flavor_config.build_location }}" >> $GITHUB_ENV
          echo "dotwheelhouse=dotpython\\wheelhouse\\included.wheels" >> $GITHUB_ENV
          echo "destwheelhouse=${{ matrix.flavor_config.build_location }}\\wheelhouse\\included.wheels" >> $GITHUB_ENV

          echo "WINPYVER2=${{ matrix.flavor_config.WINPYVER2 }}" >> $GITHUB_ENV
          echo "my_release_level=${{ matrix.flavor_config.my_release_level }}" >> $GITHUB_ENV
          echo "WINPYVER=${{ matrix.flavor_config.WINPYVER2 }}${{ matrix.flavor_config.FLAVOR }}${{ matrix.flavor_config.my_release_level }}" >> $GITHUB_ENV

          # Set matrix-specific variables
          echo "WINPYFLAVOR=${{ matrix.flavor_config.FLAVOR }}" >> $GITHUB_ENV
          echo "WINPYrequirements=${{ matrix.flavor_config.REQUIREMENTS }}" >> $GITHUB_ENV
          echo "WINPYrequirementswhl=${{ matrix.flavor_config.REQUIREMENTS_WHL }}" >> $GITHUB_ENV
          echo "WINPYZIP=${{ matrix.flavor_config.ZIP }}" >> $GITHUB_ENV
          echo "WINPY7Z=${{ matrix.flavor_config.SEVEN_Z }}" >> $GITHUB_ENV
          echo "WINPYEXE=${{ matrix.flavor_config.EXE }}" >> $GITHUB_ENV

          echo "python_sourcef=${{ matrix.flavor_config.python_sourcef }}" >> $GITHUB_ENV
          echo "python_sha256f=${{ matrix.flavor_config.python_sha256f }}" >> $GITHUB_ENV

          echo "pandoc_source=https://github.com/jgm/pandoc/releases/download/3.1.9/pandoc-3.1.9-windows-x86_64.zip" >> $GITHUB_ENV
          echo "pandoc_sha256=11eb6dbe5286c9e5edb0cca4412e7d99ec6578ec04158b0b7fe11f7fd96688e5"  >> $GITHUB_ENV


      - name: See variables
        shell: pwsh
        run: |
          Write-Output "WINPYVERSION is $env:WINPYVERSION" 
          Write-Output "WINPYFLAVOR is $env:WINPYFLAVOR"  
          Write-Output "python_sourcef is $env:python_sourcef"
          Write-Output "python_sha256f is $env:python_sha256f"
          Write-Output "python_source is $env:python_source"
          Write-Output "python_sha256 is $env:python_sha256"
          Write-Output "WINPYrequirements is $env:WINPYrequirements"
          Write-Output "WINPYrequirementswhl is $env:WINPYrequirementswhl"
          Write-Output "WINPYZIP is $env:WINPYZIP"
          Write-Output "WINPY7Z is $env:WINPY7Z"
          Write-Output "WINPYEXE is $env:WINPYEXE"
          Write-Output "build_location is $env:build_location"
          Write-Output "dotwheelhouse is $env:dotwheelhouse"
          Write-Output "destwheelhouse is $env:destwheelhouse"
          Write-Output "WINPYVER is $env:WINPYVER"
          Write-Output "WINPYVER2 is $env:WINPYVER2"

      - name: Download python-3 standalone
        shell: pwsh
        run: |
          Write-Output "python_sourcef used is $env:python_sourcef"
          curl.exe -L -o "python-3-embed.tar.gz" $env:python_sourcef
          # Calculate SHA256 hash
          $filePath = "python-3-embed.tar.gz"
          $expectedHash = $env:python_sha256f

          $hashObject = Get-FileHash -Path $filePath -Algorithm SHA256
          $actualHash = $hashObject.Hash.ToLower()

          if ($actualHash -eq $expectedHash.ToLower()) {
              Write-Output "Hash matches."
          } else {
              Write-Output "Hash does NOT match."
              Write-Output "Actual: $actualHash"
              Write-Output "Expected: $expectedHash"
              exit 1
          }

      - name: Extract python-3-embed.tar.gz to dotpython
        shell: bash
        run: |
          mkdir dotpython
          tar -xf python-3-embed.tar.gz -C dotpython
    
      - name: Copy launchers_final files to dotpython
        shell: bash
        run: |
          cp -r winpython/portable/launchers_final/* dotpython/
          mkdir $env:dotwheelhouse

      - name: List dotpython contents (for debugging)
        shell: pwsh
        run: |
          Get-ChildItem dotpython

      - name: Prepare WinPython target structure
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Path $env:build_location
          Get-ChildItem -Path dotpython -Force | Move-Item -Destination $env:build_location -Force

      - name: Download and integrate pandoc binary
        shell: pwsh
        if: ${{ matrix.flavor_config.PANDOC == '1' }}
        run: |
          # Define the URL for the Pandoc binary and the target path
          $pandocUrl = "https://github.com/jgm/pandoc/releases/download/3.1.9/pandoc-3.1.9-windows-x86_64.zip"
          $pandocZipPath = "pandoc.zip"
          $tempDir = "pandoc_temp"
          $targetDir = "$env:build_location\t"
          
          # Download the zip file
          Write-Host "Downloading Pandoc from $env:pandoc_source"
          curl.exe -L -o $pandocZipPath $env:pandoc_source
          
          # Create a temporary directory for extraction
          Write-Host "Creating temporary directory for extraction"
          mkdir -p $tempDir
          
          # Unzip the contents to the temporary directory
          Write-Host "Extracting archive to temporary directory"
          Expand-Archive -Path $pandocZipPath -DestinationPath $tempDir
          
          # Create the final target directory
          Write-Host "Creating final target directory: $targetDir"
          if (-not (Test-Path -Path $targetDir)) {
              mkdir -p $targetDir
          }
          
          # Find and copy only the pandoc.exe file
          Write-Host "Copying pandoc.exe to $targetDir"
          Copy-Item -Path (Join-Path $tempDir "pandoc-3.1.9\pandoc.exe") -Destination $targetDir -Force         

          # Optional: Verify the contents
          Write-Host "Listing contents of $targetDir"
          Get-ChildItem -Path $targetDir

          # Calculate SHA256 hash
          $filePath = $pandocZipPath
          $expectedHash = $env:pandoc_sha256

          $hashObject = Get-FileHash -Path $filePath -Algorithm SHA256
          $actualHash = $hashObject.Hash.ToLower()

          if ($actualHash -eq $expectedHash.ToLower()) {
              Write-Output "Hash matches."
          } else {
              Write-Output "Hash does NOT match."
              Write-Output "Actual: $actualHash"
              Write-Output "Expected: $expectedHash"
              exit 1
          }

          # Optional: Clean up temporary files
          Write-Host "Cleaning up temporary files..."
          Remove-Item -Path $tempDir -Recurse -Force
          Remove-Item -Path $pandocZipPath -Force
          

      - name: Upgrade pip and patch launchers
        shell: pwsh
        run: |
          & "$env:build_location\python\python.exe" -m pip install --upgrade pip
          & "$env:build_location\python\python.exe" -m pip install --upgrade packaging==25.0
          & "$env:build_location\python\python.exe" -c "from wppm import wppm;dist=wppm.Distribution();dist.patch_standard_packages('pip', to_movable=True)"

      - name: Write env.ini file
        shell: pwsh
        run: |
          $destDir = "$env:build_location\scripts"
          echo "WINPYthon_exe=$env:WINPYthon_exe" > env.ini
          echo "WINPYthon_subdirectory_name=$env:WINPYthon_subdirectory_name" >> env.ini
          echo "WINPYVER=$env:WINPYVER" >> env.ini
          echo "WINPYVER2=$env:WINPYVER2" >> env.ini
          echo "WINPYFLAVOR=$env:WINPYFLAVOR" >> env.ini
          echo "WINPYARCH=$env:WINPYARCH" >> env.ini
          Copy-Item -Path "env.ini" -Destination "$destDir\env.ini"

      - name: Download main requirements to $env:dotwheelhouse
        shell: pwsh
        run: |

          & "$env:build_location\python\python.exe" -m pip download --dest $env:dotwheelhouse --no-deps --require-hashes -r $env:WINPYrequirements

      - name: Download additional wheelhouse requirements
        shell: pwsh
        run: |
          if ($env:WINPYrequirementswhl -eq "") {
              Write-Output "No additional wheelhouse requirements."
          } else {
            & "$env:build_location\python\python.exe" -m pip download --dest $env:destwheelhouse --no-deps --require-hashes -r $env:WINPYrequirementswhl
          }

      - name: Install requirements
        shell: pwsh
        run: |
          & "$env:build_location\python\python.exe" -m pip install --no-deps --no-index --trusted-host=None --find-links=$env:dotwheelhouse --require-hashes -r $env:WINPYrequirements

      - name: Generate Markdown content and pylock file
        shell: pwsh
        run: |
          mkdir publish_output

          # Ensure unicode for wppm output
          $env:PYTHONIOENCODING="utf-8"

          $destfile_md = "publish_output\WinPython$env:WINPYFLAVOR-$($env:WINPYARCH)bit-$env:WINPYVER2.md" 
          & "$env:build_location\python\python.exe" -m wppm -md | Out-File -FilePath $destfile_md -Encoding utf8 

          gc $destfile_md

          & "$env:build_location\python\python.exe" -m pip freeze | Out-File -FilePath  dotpython\freeze.txt
          $destfile_pylock = "publish_output\pylock.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_').toml"
          & "$env:build_location\python\python.exe" -m pip lock --no-deps --find-links=$env:dotwheelhouse -r dotpython\freeze.txt -o $destfile_pylock

          $outreq = "publish_output\requir.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_').txt"
          & "$env:build_location\python\python.exe" -X utf8 -c "from wppm import wheelhouse as wh; wh.pylock_to_req(r'$destfile_pylock', r'$outreq')"

          if ($env:WINPYrequirementswhl -eq "") {
              Write-Output "No additional wheelhouse requirements."
          } else {
          $destfile_pylockwheel = "publish_output\pylock.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_')_wheels.toml"
          & "$env:build_location\python\python.exe" -m pip lock --no-deps --require-hashes -r $env:WINPYrequirementswhl -o $destfile_pylockwheel

          $outreqwheel = "publish_output\requir.$env:WINPYARCH-$($env:WINPYVER -replace '\.', '_')_wheels.txt"
          & "$env:build_location\python\python.exe" -X utf8 -c "from wppm import wheelhouse as wh; wh.pylock_to_req(r'$destfile_pylockwheel', r'$outreqwheel')"
          Copy-Item -Path $outreqwheel -Destination (Join-Path  $env:build_location "wheelhouse") -Force         
          Copy-Item -Path $destfile_pylockwheel -Destination (Join-Path  $env:build_location "wheelhouse") -Force         

          }

      - name: Zip the result
        shell: pwsh
        if: ${{ matrix.flavor_config.ZIP == '1' }}
        run: |
          $destfile = "publish_output\WinPython$env:WINPYARCH-$env:WINPYVER.zip" 
          Compress-Archive -Path "$env:build_location" -DestinationPath $destfile

      - name: 7z archive the result
        shell: pwsh
        if: ${{ matrix.flavor_config.SEVEN_Z == '1' }}
        run: |
          $destfile7z = "publish_output\WinPython$env:WINPYARCH-$env:WINPYVER.7z"
          $sourceDir = "$env:build_location"
          7z a $destfile7z $sourceDir

      - name: 7z executable archive the result
        shell: pwsh
        if: ${{ matrix.flavor_config.EXE == '1' }}
        run: |
          $destfile7z = "publish_output\WinPython$env:WINPYARCH-$env:WINPYVER.exe"
          $sourceDir = "$env:build_location"
          7z a -sfx $destfile7z $sourceDir

      - name: generate hashes wppm style
        shell: pwsh
        run: |
          $DESTFILE="./publish_output/hashes.md" 
          Get-ChildItem -Path ".\publish_output\*.*"
          # Get the list of files matching the pattern and pass them as arguments
          $filesToHash = Get-ChildItem -Path ".\publish_output\*64*.*"
          & "$env:build_location\python\python.exe" -c "import sys;from wppm import hash; hash.print_hashes(sys.argv[1:])" @($filesToHash.FullName) | Out-File -FilePath $DESTFILE
          gc $DESTFILE

      - name: Upload WinPython folder as artifact
        uses: actions/upload-artifact@v4
        with:
            name: ${{ matrix.flavor_config.ARTIFACT_NAME }}
            path: publish_output
            retention-days: 66  # keeps artifact for 66 days
