#!/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
        echo "patch: ${1}"
        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; }

# include common functions
. ../utils.sh

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
  ADDITIONAL_FILES=()
  BASENAME=$(basename "${FILE}")
  DIRNAME=$(dirname "${FILE}")

  if [[ "${BASENAME}" =~ ^([0-9])([1-9])(-.*)\.patch$ ]]; then
    GROUP_ID="${BASH_REMATCH[1]}"
    INDEX="${BASH_REMATCH[2]}"
    ENDNAME="${BASH_REMATCH[3]}"

    for ((I = 0; I < INDEX; I++)); do
      for CANDIDATE in "${DIRNAME}/${GROUP_ID}${I}-"*.patch; do
        if [[ -f "$CANDIDATE" ]]; then
          ADDITIONAL_FILES+=("$CANDIDATE")
        fi
      done
    done
  fi

  if [[ ${#ADDITIONAL_FILES[@]} -gt 0 ]]; then
    check_file ${ADDITIONAL_FILES[@]} "${FILE}"
  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
    ADDITIONAL_FILES=()
    BASENAME=$(basename "${FILE}")
    DIRNAME=$(dirname "${FILE}")

    if [[ "${BASENAME}" =~ ^([0-9])([1-9])(-.*)\.patch$ ]]; then
      GROUP_ID="${BASH_REMATCH[1]}"
      INDEX="${BASH_REMATCH[2]}"
      ENDNAME="${BASH_REMATCH[3]}"

      for ((I = 0; I < INDEX; I++)); do
        NOT_FOUND=1

        for CANDIDATE in "${DIRNAME}/${GROUP_ID}${I}-"*.patch; do
          if [[ -f "$CANDIDATE" ]]; then
            ADDITIONAL_FILES+=( "${CANDIDATE}" )
            NOT_FOUND=0
          fi
        done

        if (( $NOT_FOUND )); then
          for CANDIDATE in "${DIRNAME}/../${GROUP_ID}${I}-"*.json; do
            if [[ -f "$CANDIDATE" ]]; then
              apply_actions "${CANDIDATE}"
            fi
          done
        fi

        if (( $NOT_FOUND )); then
          for CANDIDATE in "${DIRNAME}/../${GROUP_ID}${I}-"*.patch; do
            if [[ -f "$CANDIDATE" ]]; then
              ADDITIONAL_FILES+=( "${CANDIDATE}" )
            fi
          done
        fi
      done

      if [[ ${#ADDITIONAL_FILES[@]} -gt 0 ]]; then
        check_file ${ADDITIONAL_FILES[@]} "${FILE}"
      else
        check_file "${FILE}"
      fi
    else
      check_file "${FILE}"
    fi
  done

  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
