#!/usr/bin/env bash
#
# biblatex-apa-zh 測試執行器
#
#   bash test/run-tests.sh              跑全部固件
#   bash test/run-tests.sh chinese-prose  只跑一個
#
# 每個固件是 test/fixtures/<name>.tex，對應的斷言在 test/expected/<name>.txt。
# 全數通過 exit 0，任一條斷言不符 exit 1。
#
# 相依：xelatex、biber、pdftotext（poppler）。CJK 字型見 README。

set -uo pipefail   # 刻意不用 -e：一條斷言失敗要繼續跑完其餘的，最後才彙總

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TESTDIR="$ROOT/test"
FIXDIR="$TESTDIR/fixtures"
EXPDIR="$TESTDIR/expected"

PASS=0
FAIL=0

# 把所有連續空白（含換行）壓成單一空格。
#
# pdftotext 依版面硬斷行，同一個引用可能被切成兩行，例如
#     （Hathaway &
#     McKinley，1940）
# 不正規化的話，這條斷言會因為與套件行為無關的換行位置而失敗，而那種測試
# 在幾次無關的內容編輯之後就會被關掉。斷言字串套用同一個函式。
#
# 只動 ASCII 的空白位元組。UTF-8 的續接位元組都 >= 0x80，不會被誤觸。
normalize() {
  tr '\n\r\t' '   ' | tr -s ' '
}

compile_fixture() {
  local name="$1"
  local log="$TESTDIR/$name.log"

  (
    cd "$TESTDIR" || exit 1
    export TEXINPUTS="$ROOT:"
    xelatex -interaction=nonstopmode -halt-on-error "fixtures/$name.tex" >/dev/null 2>&1 || exit 1
    biber "$name" >/dev/null 2>&1 || exit 1
    xelatex -interaction=nonstopmode -halt-on-error "fixtures/$name.tex" >/dev/null 2>&1 || exit 1
    xelatex -interaction=nonstopmode -halt-on-error "fixtures/$name.tex" >/dev/null 2>&1 || exit 1
  )

  if [[ $? -ne 0 || ! -f "$TESTDIR/$name.pdf" ]]; then
    echo "✗ $name：編譯失敗"
    if [[ -f "$log" ]]; then
      echo "  ── log 末 20 行 ──"
      tail -20 "$log" | sed 's/^/  /'
    fi
    return 1
  fi
  return 0
}

run_fixture() {
  local name="$1"
  local fixture="$FIXDIR/$name.tex"
  local expected="$EXPDIR/$name.txt"

  if [[ ! -f "$fixture" ]]; then
    echo "✗ 找不到固件：$fixture"
    FAIL=$((FAIL + 1))
    return
  fi
  if [[ ! -f "$expected" ]]; then
    echo "✗ 找不到期望檔：$expected"
    FAIL=$((FAIL + 1))
    return
  fi

  echo "── $name ──"

  if ! compile_fixture "$name"; then
    FAIL=$((FAIL + 1))
    return
  fi

  local actual
  actual="$(pdftotext "$TESTDIR/$name.pdf" - 2>/dev/null | normalize)"

  local lineno=0
  while IFS= read -r line || [[ -n "$line" ]]; do
    lineno=$((lineno + 1))

    # 註解與空行
    [[ "$line" =~ ^[[:space:]]*# ]] && continue
    [[ -z "${line//[[:space:]]/}" ]] && continue

    local marker="${line:0:1}"
    local needle="${line:2}"
    needle="$(printf '%s' "$needle" | normalize)"

    case "$marker" in
      +)
        if printf '%s' "$actual" | grep -qF -- "$needle"; then
          echo "  ✓ 出現：$needle"
          PASS=$((PASS + 1))
        else
          echo "  ✗ 應出現但沒有：$needle"
          echo "    （$name 的輸出裡找不到這段）"
          FAIL=$((FAIL + 1))
        fi
        ;;
      -)
        if printf '%s' "$actual" | grep -qF -- "$needle"; then
          echo "  ✗ 不應出現卻出現：$needle"
          FAIL=$((FAIL + 1))
        else
          echo "  ✓ 未出現：$needle"
          PASS=$((PASS + 1))
        fi
        ;;
      *)
        # 無法辨識的行要報錯，不能默默略過——期望檔寫錯而測試照樣全綠，
        # 是這類工具最糟的失效方式。
        echo "  ✗ $expected 第 $lineno 行無法辨識（開頭須為 + / - / #）：$line"
        FAIL=$((FAIL + 1))
        ;;
    esac
  done < "$expected"
}

main() {
  local names=()
  if [[ $# -ge 1 ]]; then
    names=("$1")
  else
    for f in "$FIXDIR"/*.tex; do
      [[ -e "$f" ]] || continue
      local base
      base="$(basename "$f" .tex)"
      # 底線開頭的是共用片段（如 _cjkfont），不是固件，沒有對應的期望檔。
      [[ "$base" == _* ]] && continue
      names+=("$base")
    done
  fi

  if [[ ${#names[@]} -eq 0 ]]; then
    echo "✗ 找不到任何固件（$FIXDIR/*.tex）"
    exit 1
  fi

  for n in "${names[@]}"; do
    run_fixture "$n"
  done

  echo ""
  # 變數一定要用大括號：$PASS 後面接全形逗號時，bash 會把多位元組的位元組
  # 讀進變數名，變成 unbound variable。
  echo "═══ 通過 ${PASS}，失敗 ${FAIL} ═══"
  [[ $FAIL -eq 0 ]] || exit 1
  exit 0
}

main "$@"
