#!/usr/bin/env bash

export VSCODE_QUALITY="stable"

while getopts ":i" opt; do
  case "$opt" in
    i)
      export VSCODE_QUALITY="insider"
      ;;
    *)
      ;;
  esac
done

generate_rejects() {
  local PATCH_FILE="$1"

  if ! command -v python3 >/dev/null 2>&1; then
    echo "python3 not found; cannot create reject files for ${PATCH_FILE}"
    return 1
  fi

  PATCH_FOR_REJECT="${PATCH_FILE}" python3 <<'PY'
import os
import pathlib
import re
import subprocess


def chunk_needs_reject(raw_chunk: str) -> bool:
    """Return True when a patch chunk cannot be applied nor reversed."""

    chunk = raw_chunk if raw_chunk.endswith("\n") else raw_chunk + "\n"

    def _run_git(extra_args):
        return subprocess.run(
            ["git", "apply", "--check", "--ignore-whitespace", *extra_args],
            input=chunk,
            text=True,
            capture_output=True,
        )

    forward = _run_git([])
    if forward.returncode == 0:
        return False

    reverse = _run_git(["--reverse"])
    if reverse.returncode == 0:
        return False

    return True

patch_path = os.environ["PATCH_FOR_REJECT"]
with open(patch_path, "r", encoding="utf-8", errors="ignore") as src:
    content = src.read()

chunks = re.split(r'(?m)^diff --git ', content)
for chunk in chunks:
    chunk = chunk.strip()
    if not chunk:
        continue
    chunk = "diff --git " + chunk
    match = re.search(r'^diff --git a/(.*?) b/(.*?)$', chunk, re.MULTILINE)
    if not match:
        continue
    a_path, b_path = match.groups()
    candidate = b_path if b_path != "/dev/null" else a_path
    if candidate in ("/dev/null", ""):
        continue
    if candidate.startswith("../") or candidate.startswith("..\\") or candidate.startswith("/"):
        continue
    dest = pathlib.Path(candidate + ".rej")
    if not chunk_needs_reject(chunk):
      continue
    dest.parent.mkdir(parents=True, exist_ok=True)
    with dest.open("w", encoding="utf-8") as fh:
      fh.write(chunk)
      if not chunk.endswith("\n"):
        fh.write("\n")
    print(f"generated reject: {dest}")
PY
}

check_file() {
  while [ $# -gt 1 ]; do
    git apply --reject "${1}"

    shift
  done

  if [[ -f "${1}.bak" ]]; then
    mv -f $1{.bak,}
  fi

  if [[ -f "${1}" ]]; then
    git apply --reject "../patches/helper/settings.patch"
    git add .
    git commit --no-verify -q -m "VSCODIUM HELPER"

    echo applying patch: "${1}"

    if ! git apply --ignore-whitespace "${1}"; then
      echo failed to apply patch "${1}"

      git apply --reject --verbose "${1}"

      if [[ -z "$( find . -name '*.rej' -print )" ]]; then
        echo "no .rej generated by git; creating fallback rejects for ${1}"

        if ! generate_rejects "${1}"; then
          echo "failed to generate reject files for ${1}"
          exit 1
        fi

        if [[ -z "$( find . -name '*.rej' -print )" ]]; then
          echo "still no .rej after attempting to create them for ${1}"
          exit 1
        fi
      fi

      while [[ -n "$( find . -name '*.rej' -print )" ]]; do
        find . -name '*.rej' -print
        read -rp "Press any key when the conflict have been resolved..." -n1 -s
        echo
      done

      git restore .vscode/settings.json
      git add .
      git diff --staged -U1 > "${1}"
    fi

    git add .
    git reset -q --hard HEAD~
  fi
}

cd vscode || { echo "'vscode' dir not found"; exit 1; }

git add .
git reset -q --hard HEAD

while [[ -n "$( git log -1 | grep "VSCODIUM HELPER" )" ]]; do
  git reset -q --hard HEAD~
done

for FILE in ../patches/*.patch; do
  if [[ "${FILE}" == *"/fix-policies.patch" ]]; then
    check_file "../patches/fix-keymap.patch" "../patches/fix-policies.patch"
  else
    check_file "${FILE}"
  fi
done

if [[ "${VSCODE_QUALITY}" == "insider" ]]; then
  for FILE in ../patches/insider/*.patch; do
    check_file "${FILE}"
  done
fi

for ARCH in alpine linux osx windows; do
  for FILE in "../patches/${ARCH}/"*.patch; do
    if [[ "${ARCH}" == "linux" && "${FILE}" == *"/arch-"* ]] || [[ "${ARCH}" == "linux" && "${FILE}" == *"/fix-dependencies.patch" ]] || [[ "${ARCH}" == "windows" && "${FILE}" == *"/cli"* ]]; then
      echo "skip ${FILE}"
    else
      check_file "${FILE}"
    fi
  done

  if [[ "${ARCH}" == "linux" ]]; then
    check_file "../patches/optional-tree-sitter.patch" "../patches/linux/fix-dependencies.patch"

    check_file "../patches/cli.patch" "../patches/linux/arch-0-support.patch"
    check_file "../patches/cli.patch" "../patches/linux/arch-0-support.patch" "../patches/linux/arch-1-ppc64le.patch"
    check_file "../patches/cli.patch" "../patches/linux/arch-0-support.patch" "../patches/linux/arch-1-ppc64le.patch" "../patches/linux/arch-2-riscv64.patch"
    check_file "../patches/cli.patch" "../patches/linux/arch-0-support.patch" "../patches/linux/arch-1-ppc64le.patch" "../patches/linux/arch-2-riscv64.patch" "../patches/linux/arch-3-loong64.patch"
    check_file "../patches/cli.patch" "../patches/linux/arch-0-support.patch" "../patches/linux/arch-1-ppc64le.patch" "../patches/linux/arch-2-riscv64.patch" "../patches/linux/arch-3-loong64.patch" "../patches/linux/arch-4-s390x.patch"
  elif [[ "${ARCH}" == "windows" ]]; then
    check_file "../patches/cli.patch" "../patches/windows/cli.patch"
  fi

  for TARGET in client reh; do
    for FILE in "../patches/${ARCH}/${TARGET}/"*.patch; do
      check_file "${FILE}"
    done

    for FILE in "../patches/${ARCH}/${TARGET}/"*/*.patch; do
      check_file "${FILE}"
    done
  done
done
