#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
#
# Extract steps from .buildkite/test-nightly.yml that contain pytest, synthesize
# small bash wrappers (exports + pytest), run them, and tee output to logs named
# after each step's Buildkite "key" when present (otherwise a slug of the label).
# YAML steps whose label contains "Perf Test" run first, then
# python tools/nightly/generate_nightly_perf_excel.py runs (even if some perf jobs failed;
# the script only aggregates JSON already present under tests/dfx/perf/results).
# Excel and generate_nightly_perf_excel.log are written under $REPO_ROOT/logs/;
# per-job tee logs use LOG_DIR (see --log-dir). Then remaining jobs run.
#
# Test kind (--test-type / TEST_TYPE), multiple allowed (OR semantics):
#   Repeat the flag and/or use comma-separated values, e.g.
#     --test-type perf,acc   OR   --test-type perf --test-type function
#   If any selected value is all, YAML steps are unconstrained except that perf/acc/function tokens
#   are ignored; all,stability / all,local / all,stability,local still run those extra bundles.
#   perf     — label contains "Perf Test" (throughput / benchmark jobs)
#   acc      — label contains "Accuracy Test" (incl. GEBench / GEdit-Bench style)
#   function — label has neither "Perf Test" nor "Accuracy Test" (incl. Doc, Multi-Replica, etc.)
#   stability— fixed dfx stability scripts under tests/dfx/stability/scripts/ (see below)
#   local    — pytest -sv -m "<MODEL_TYPE markers> and local_model" from repo root (no YAML step extract)
#   all      — no test-kind filter for YAML steps (any leaf step with pytest in test-nightly.yml)
#
# Model area (--model-type / MODEL_TYPE), multiple allowed (OR semantics):
#   e.g. --model-type omni,tts
#   omni     — label contains "Omni ·"
#   tts      — label contains "TTS ·"
#   diffusion— label contains "Diffusion"
#   all      — no model filter (e.g. Quantization-only steps match only when test-type allows)
#
#   Combining stability and/or local with perf/acc/function/all runs each enabled bundle; each enabled
#   mode must match at least one job or the script exits with status 2.
#
#   local (when included in TEST_TYPE):
#     From repo root: pytest -sv -m "<markers> and local_model" (markers from MODEL_TYPE: omni, tts,
#     diffusion; all → "(omni or tts or diffusion) and local_model"). Not filtered by nightly YAML.
#
#   stability (when included in TEST_TYPE):
#     From repo root: pytest -s -v tests/dfx/stability/scripts/test_stability_*.py
#     model_type: omni → qwen3_omni; tts → qwen3_tts; diffusion → qwen_image + wan22; all → all four
#     LABEL_SUBSTR: if set, script path / job key / filename must contain it
#
# Requirements: bash, python3, PyYAML (pip install pyyaml)
#
# Usage:
#   bash path/to/run_nightly_jobs.sh
#   REPO_ROOT=/path/to/vllm-omni bash path/to/run_nightly_jobs.sh --test-type perf,acc --dry-run
#   YML=/path/to/vllm-omni/.buildkite/test-nightly.yml bash path/to/run_nightly_jobs.sh
#
# Repository / YAML (no dependency on where this script lives):
#   • Set REPO_ROOT (or pass --repo-root) — default YAML is $REPO_ROOT/.buildkite/test-nightly.yml
#   • Or set YML (or --yaml) — repo root is inferred as parent of the .buildkite directory
#   • Or run from inside the clone: git rev-parse --show-toplevel, else walk up from $PWD,
#     then from the script's directory, until .buildkite/test-nightly.yml exists
#
# Optional environment:
#   REPO_ROOT     - vllm-omni root (working directory for pytest); see above
#   YML           - path to test-nightly.yml (default: $REPO_ROOT/.buildkite/test-nightly.yml)
#   LOG_DIR       - logs + generated job scripts (default: $REPO_ROOT/logs/nightly_jobs)
#   TEST_TYPE     - comma-separated and/or repeated flags (default: all); see above
#   MODEL_TYPE    - comma-separated and/or repeated flags (default: all); see above
#   LABEL_SUBSTR  - YAML mode: substring of Buildkite step label; stability: substring of path/key/filename
#   DRY_RUN=1     - print extracted commands only; do not write scripts or run pytest
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

LABEL_SUBSTR="${LABEL_SUBSTR:-}"
TEST_TYPE_ENV="${TEST_TYPE:-all}"
MODEL_TYPE_ENV="${MODEL_TYPE:-all}"
TEST_TYPE_CLI_PARTS=()
MODEL_TYPE_CLI_PARTS=()
TEST_TYPE_FROM_CLI=0
MODEL_TYPE_FROM_CLI=0
DRY_RUN="${DRY_RUN:-0}"
# REPO_ROOT / YML / LOG_DIR resolved after CLI (do not assume script path vs repo root)

usage() {
  sed -n '2,66p' "$0" | sed 's/^# \{0,1\}//'
}

# Append comma-separated tokens from $2 into array named $1 (lowercased, trimmed).
_split_append_csv_array() {
  local -n _arr="${1}"
  local _raw="${2}"
  local _ifs="${IFS}"
  local _part
  IFS=','
  for _part in ${_raw}; do
    IFS="${_ifs}"
    _part="$(printf '%s' "${_part}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
    [[ -n "${_part}" ]] || continue
    _arr+=("${_part}")
  done
}

# Build comma-separated TEST_TYPE from CLI parts or env; collapse to a single "all" if present.
_finalize_test_type_csv() {
  if [[ "${TEST_TYPE_FROM_CLI}" -eq 1 ]]; then
    if ((${#TEST_TYPE_CLI_PARTS[@]} == 0)); then
      echo "--test-type requires a non-empty value" >&2
      exit 2
    fi
    local _has_all=0 _has_stability=0 _has_local=0
    local _x
    for _x in "${TEST_TYPE_CLI_PARTS[@]}"; do
      if [[ "${_x}" == all ]]; then
        _has_all=1
      fi
      if [[ "${_x}" == stability ]]; then
        _has_stability=1
      fi
      if [[ "${_x}" == local ]]; then
        _has_local=1
      fi
    done
    if [[ "${_has_all}" -eq 1 ]]; then
      local _out="all"
      if [[ "${_has_stability}" -eq 1 ]]; then
        _out="${_out},stability"
      fi
      if [[ "${_has_local}" -eq 1 ]]; then
        _out="${_out},local"
      fi
      printf '%s' "${_out}"
      return 0
    fi
    local -A _seen=()
    local -a _out=()
    for _x in "${TEST_TYPE_CLI_PARTS[@]}"; do
      [[ ${_seen["${_x}"]+isset} ]] && continue
      _seen["${_x}"]=1
      _out+=("${_x}")
    done
    (IFS=','; printf '%s' "${_out[*]}")
  else
    printf '%s' "${TEST_TYPE_ENV}"
  fi
}

_finalize_model_type_csv() {
  if [[ "${MODEL_TYPE_FROM_CLI}" -eq 1 ]]; then
    if ((${#MODEL_TYPE_CLI_PARTS[@]} == 0)); then
      echo "--model-type requires a non-empty value" >&2
      exit 2
    fi
    local -A _seen=()
    local -a _out=()
    local _x
    for _x in "${MODEL_TYPE_CLI_PARTS[@]}"; do
      if [[ "${_x}" == all ]]; then
        printf '%s' "all"
        return 0
      fi
      [[ ${_seen["${_x}"]+isset} ]] && continue
      _seen["${_x}"]=1
      _out+=("${_x}")
    done
    (IFS=','; printf '%s' "${_out[*]}")
  else
    printf '%s' "${MODEL_TYPE_ENV}"
  fi
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -h | --help)
      usage
      exit 0
      ;;
    --dry-run)
      DRY_RUN=1
      shift
      ;;
    --log-dir)
      LOG_DIR="$2"
      shift 2
      ;;
    --yaml)
      YML="$2"
      shift 2
      ;;
    --repo-root)
      REPO_ROOT="$2"
      shift 2
      ;;
    --label-substr)
      LABEL_SUBSTR="$2"
      shift 2
      ;;
    --model-type)
      MODEL_TYPE_FROM_CLI=1
      _split_append_csv_array MODEL_TYPE_CLI_PARTS "$2"
      shift 2
      ;;
    --test-type)
      TEST_TYPE_FROM_CLI=1
      _split_append_csv_array TEST_TYPE_CLI_PARTS "$2"
      shift 2
      ;;
    *)
      echo "Unknown option: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
done

TEST_TYPE="$(_finalize_test_type_csv)"
MODEL_TYPE="$(_finalize_model_type_csv)"

BUILDKITE_REL=".buildkite/test-nightly.yml"

_find_repo_containing_nightly() {
  local dir="${1:-}"
  [[ -n "$dir" ]] || return 1
  dir="$(cd "$dir" && pwd)" || return 1
  while true; do
    if [[ -f "${dir}/${BUILDKITE_REL}" ]]; then
      printf '%s\n' "$dir"
      return 0
    fi
    [[ "${dir}" == "/" ]] && return 1
    dir="$(dirname "${dir}")"
  done
}

_derive_repo_root_from_yml() {
  local yml="$1"
  local d
  d="$(cd "$(dirname "${yml}")" && pwd)"
  [[ "$(basename "${d}")" == ".buildkite" ]] || return 1
  printf '%s\n' "$(dirname "${d}")"
}

# Resolve REPO_ROOT, YML (no relative path between script and repo)
if [[ -n "${YML:-}" && -n "${REPO_ROOT:-}" ]]; then
  REPO_ROOT="$(cd "${REPO_ROOT}" && pwd)"
  YML="$(cd "$(dirname "${YML}")" && pwd)/$(basename "${YML}")"
elif [[ -n "${YML:-}" ]]; then
  YML="$(cd "$(dirname "${YML}")" && pwd)/$(basename "${YML}")"
  if ! REPO_ROOT="$(_derive_repo_root_from_yml "${YML}")"; then
    echo "Could not derive REPO_ROOT from YML=${YML} (expected file at <repo>/.buildkite/test-nightly.yml)." >&2
    echo "Set REPO_ROOT explicitly (or pass --repo-root) for pytest working directory." >&2
    exit 2
  fi
elif [[ -n "${REPO_ROOT:-}" ]]; then
  REPO_ROOT="$(cd "${REPO_ROOT}" && pwd)"
  YML="${REPO_ROOT}/${BUILDKITE_REL}"
else
  REPO_ROOT=""
  if command -v git >/dev/null 2>&1; then
    REPO_ROOT="$(git -C "${PWD}" rev-parse --show-toplevel 2>/dev/null || true)"
  fi
  if [[ -z "${REPO_ROOT}" ]]; then
    REPO_ROOT="$(_find_repo_containing_nightly "${PWD}" || true)"
  fi
  if [[ -z "${REPO_ROOT}" ]]; then
    REPO_ROOT="$(_find_repo_containing_nightly "${SCRIPT_DIR}" || true)"
  fi
  if [[ -z "${REPO_ROOT}" ]]; then
    echo "Could not locate ${BUILDKITE_REL}. Set REPO_ROOT or YML, run from inside the vllm-omni clone," >&2
    echo "or place this script (or run from a cwd) under the repository tree." >&2
    exit 2
  fi
  YML="${REPO_ROOT}/${BUILDKITE_REL}"
fi

LOG_DIR="${LOG_DIR:-${REPO_ROOT}/logs/nightly_jobs}"

# Require nightly YAML only when at least one non-stability test kind needs it (validated in Python too).
_needs_nightly_yml=0
IFS=',' read -ra _TTOK <<< "${TEST_TYPE}"
for _t in "${_TTOK[@]}"; do
  _t="$(printf '%s' "${_t}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
  case "${_t}" in
    perf | acc | function | all)
      _needs_nightly_yml=1
      ;;
  esac
done
if [[ "${_needs_nightly_yml}" -eq 1 ]] && [[ ! -f "${YML}" ]]; then
  echo "YAML not found: ${YML}" >&2
  exit 2
fi

if ! command -v python3 >/dev/null 2>&1; then
  echo "python3 is required." >&2
  exit 1
fi

mkdir -p "${LOG_DIR}/jobs"
LOG_DIR="$(cd "${LOG_DIR}" && pwd)"
export REPO_ROOT LOG_DIR YML LABEL_SUBSTR TEST_TYPE MODEL_TYPE DRY_RUN

# Drop stale wrapper scripts, old tee logs, and perf manifest so a filtered run does not
# mix with the previous outputs.
if [[ "${DRY_RUN}" != "1" ]]; then
  shopt -s nullglob
  _stale=( "${LOG_DIR}/jobs"/*.sh )
  _stale_logs=( "${LOG_DIR}"/*.log )
  shopt -u nullglob
  if ((${#_stale[@]})); then
    rm -f "${_stale[@]}"
  fi
  if ((${#_stale_logs[@]})); then
    rm -f "${_stale_logs[@]}"
  fi
  rm -f "${LOG_DIR}/jobs/.perf_job_keys"
  rm -f "${REPO_ROOT}/logs/nightly_perf_manual.xlsx"
  rm -f "${LOG_DIR}/nightly_perf_manual.xlsx"
fi

# shellcheck disable=SC2016,SC1078,SC1079
python3 - <<'PY'
from __future__ import annotations

import os
import re
import stat
import sys
from pathlib import Path

try:
    import yaml
except ImportError:
    print("Missing PyYAML. Install with: pip install pyyaml", file=sys.stderr)
    sys.exit(1)

REPO_ROOT = Path(os.environ["REPO_ROOT"]).resolve()
LOG_DIR = Path(os.environ["LOG_DIR"]).resolve()
YML = Path(os.environ["YML"]).resolve()
LABEL_SUBSTR = (os.environ.get("LABEL_SUBSTR") or "").strip()
DRY_RUN = os.environ.get("DRY_RUN", "0") == "1"

ALLOWED_TEST_TYPES = frozenset({"all", "perf", "acc", "function", "stability", "local"})
ALLOWED_MODEL_TYPES = frozenset({"all", "omni", "tts", "diffusion"})


def parse_model_types(raw: str) -> list[str]:
    parts = [p.strip().lower() for p in (raw or "").split(",") if p.strip()]
    if not parts:
        parts = ["all"]
    bad = [p for p in parts if p not in ALLOWED_MODEL_TYPES]
    if bad:
        print(
            f"Invalid MODEL_TYPE / --model-type value(s): {bad!r} "
            f"(allowed: {', '.join(sorted(ALLOWED_MODEL_TYPES))})",
            file=sys.stderr,
        )
        sys.exit(2)
    if "all" in parts:
        return ["all"]
    out: list[str] = []
    seen: set[str] = set()
    for p in parts:
        if p not in seen:
            seen.add(p)
            out.append(p)
    return out


def parse_test_types(raw: str) -> list[str]:
    """If 'all' appears, YAML dimension is unconstrained; extras like stability/local are kept."""
    parts = [p.strip().lower() for p in (raw or "").split(",") if p.strip()]
    if not parts:
        parts = ["all"]
    bad = [p for p in parts if p not in ALLOWED_TEST_TYPES]
    if bad:
        print(
            f"Invalid TEST_TYPE / --test-type value(s): {bad!r} "
            f"(allowed: {', '.join(sorted(ALLOWED_TEST_TYPES))})",
            file=sys.stderr,
        )
        sys.exit(2)
    if "all" in parts:
        out: list[str] = ["all"]
        if "stability" in parts:
            out.append("stability")
        if "local" in parts:
            out.append("local")
        return out
    out: list[str] = []
    seen: set[str] = set()
    for p in parts:
        if p not in seen:
            seen.add(p)
            out.append(p)
    return out


PYTEST_CMD_RE = re.compile(
    r"(?:timeout\s+\S+\s+)?(?:python3? -m\s+)?pytest\s+[^\n&|;]*"
)


def iter_leaf_steps(steps, group: str | None = None):
    """Flatten Buildkite steps; yield (leaf_step_dict, group_title)."""
    for raw in steps or []:
        if not isinstance(raw, dict):
            continue
        nested = raw.get("steps")
        if isinstance(nested, list) and nested:
            g = raw.get("group")
            next_group: str | None
            if isinstance(g, str):
                next_group = g
            elif g is not None:
                next_group = str(g)
            else:
                next_group = group
            yield from iter_leaf_steps(nested, next_group)
            continue
        if raw.get("commands"):
            yield raw, group


def raw_command_text(step: dict) -> str:
    raw = step.get("commands") or []
    if isinstance(raw, str):
        raw = [raw]
    text = "\n".join((c.strip() if isinstance(c, str) else "") for c in raw if c)
    return text.replace("$$", "$")


def export_lines(text: str) -> list[str]:
    out: list[str] = []
    for line in text.splitlines():
        s = line.strip()
        if not s or s.startswith("#"):
            continue
        if s.startswith("export "):
            out.append(s)
    return out


def pytest_lines(text: str) -> list[str]:
    out: list[str] = []
    for m in PYTEST_CMD_RE.finditer(text):
        line = m.group(0).strip()
        line_start = text.rfind("\n", 0, m.start()) + 1
        before = text[line_start : m.start()]
        if before.lstrip().startswith("#"):
            continue
        if line:
            out.append(line)
    return out


def label_matches_test_type(label: str, test_types: list[str]) -> bool:
    """perf / acc / function / all — aligned with nightly step labels (OR across test_types)."""
    if "all" in test_types:
        return True
    has_perf = "Perf Test" in label
    has_acc = "Accuracy Test" in label
    is_function = not has_perf and not has_acc
    for t in test_types:
        if t in ("stability", "local"):
            continue
        if t == "perf" and has_perf:
            return True
        if t == "acc" and has_acc:
            return True
        if t == "function" and is_function:
            return True
    return False


def label_matches_model_type(label: str, model_types: list[str]) -> bool:
    """Omni · / TTS · / Diffusion — OR across model_types."""
    if "all" in model_types:
        return True
    if "omni" in model_types and "Omni ·" in label:
        return True
    if "tts" in model_types and "TTS ·" in label:
        return True
    if "diffusion" in model_types and "Diffusion" in label:
        return True
    return False


def job_key_from_step(step: dict, label: str) -> str:
    k = step.get("key")
    if isinstance(k, str) and k.strip():
        return k.strip()
    slug = re.sub(r"[^\w\-.]+", "_", label or "job", flags=re.UNICODE)
    slug = re.sub(r"_+", "_", slug).strip("_") or "job"
    return slug


# Fixed stability scripts (when stability is selected); MODEL_TYPES narrows which run.
STABILITY_CASES: list[tuple[str, str, tuple[str, ...]]] = [
    ("stability_qwen3_omni", "tests/dfx/stability/scripts/test_stability_qwen3_omni.py", ("omni",)),
    ("stability_qwen3_tts", "tests/dfx/stability/scripts/test_stability_qwen3_tts.py", ("tts",)),
    ("stability_qwen_image", "tests/dfx/stability/scripts/test_stability_qwen_image.py", ("diffusion",)),
    ("stability_wan22", "tests/dfx/stability/scripts/test_stability_wan22.py", ("diffusion",)),
]


def _stability_model_matches(model_types: list[str], families: tuple[str, ...]) -> bool:
    if "all" in model_types:
        return True
    return bool(set(model_types) & set(families))


def _write_job_script(key: str, script_lines: list[str], jobs_dir: Path) -> None:
    body = "\n".join(script_lines) + "\n"
    if DRY_RUN:
        print(f"=== {key} ===")
        print(body)
        return
    job_path = jobs_dir / f"{key}.sh"
    job_path.write_text(body, encoding="utf-8")
    try:
        mode = job_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
        job_path.chmod(mode)
    except OSError:
        pass
    print(f"generated {job_path}", file=sys.stderr)


def local_pytest_marker_expr(model_types: list[str]) -> str:
    """Build -m expression: (omni|tts|diffusion) markers combined with local_model."""
    if "all" in model_types:
        families: tuple[str, ...] = ("omni", "tts", "diffusion")
    else:
        families = tuple(model_types)
    if len(families) == 1:
        return f"{families[0]} and local_model"
    return f"({' or '.join(families)}) and local_model"


def run_local_mode(jobs_dir: Path, model_types: list[str]) -> int:
    """Single job: pytest -sv -m '<model markers> and local_model' (testpaths from pytest config)."""
    marker_expr = local_pytest_marker_expr(model_types)
    key = "local_pytest"
    pytest_line = f'pytest -sv -m "{marker_expr}"'
    script_lines = [
        "#!/usr/bin/env bash",
        f"# Local marker tests — MODEL_TYPE={','.join(model_types)}",
        "set -euo pipefail",
        f'cd "{REPO_ROOT}"',
        pytest_line,
    ]
    _write_job_script(key, script_lines, jobs_dir)
    return 1


def run_stability_mode(jobs_dir: Path, model_types: list[str]) -> int:
    matched = 0
    for key, rel_posix, families in STABILITY_CASES:
        if not _stability_model_matches(model_types, families):
            continue
        if LABEL_SUBSTR:
            fn = Path(rel_posix).name
            if (
                LABEL_SUBSTR not in rel_posix
                and LABEL_SUBSTR not in key
                and LABEL_SUBSTR not in fn
            ):
                continue
        rel_path = REPO_ROOT / rel_posix
        if not rel_path.is_file():
            print(f"# skip (missing file): {rel_path}", file=sys.stderr)
            continue
        pytest_line = f"pytest -s -v {rel_posix}"
        matched += 1
        script_lines = [
            "#!/usr/bin/env bash",
            f"# Stability: {key} — {rel_posix}",
            "set -euo pipefail",
            f'cd "{REPO_ROOT}"',
            pytest_line,
        ]
        _write_job_script(key, script_lines, jobs_dir)
    return matched


def main() -> None:
    test_types = parse_test_types(os.environ.get("TEST_TYPE", "all"))
    model_types = parse_model_types(os.environ.get("MODEL_TYPE", "all"))

    want_stability = "stability" in test_types
    want_local = "local" in test_types
    yaml_test_types = [t for t in test_types if t not in ("stability", "local")]
    needs_yaml = bool(yaml_test_types)

    jobs_dir = LOG_DIR / "jobs"
    if not DRY_RUN:
        jobs_dir.mkdir(parents=True, exist_ok=True)

    matched_stability = 0
    matched_yaml = 0
    perf_job_keys: list[str] = []

    if want_stability:
        matched_stability = run_stability_mode(jobs_dir, model_types)

    if want_local:
        run_local_mode(jobs_dir, model_types)

    if needs_yaml:
        if not YML.is_file():
            print(f"YAML not found: {YML}", file=sys.stderr)
            sys.exit(1)

        data = yaml.safe_load(YML.read_text(encoding="utf-8"))
        top_steps = (data or {}).get("steps") or []

        for step, _grp in iter_leaf_steps(top_steps):
            label = step.get("label") or ""
            if LABEL_SUBSTR and LABEL_SUBSTR not in label:
                continue
            if not label_matches_test_type(label, yaml_test_types):
                continue
            if not label_matches_model_type(label, model_types):
                continue
            text = raw_command_text(step)
            exports = export_lines(text)
            pys = pytest_lines(text)
            if not pys:
                print(f"# skip (no pytest line): {label!r}", file=sys.stderr)
                continue

            key = job_key_from_step(step, label)
            matched_yaml += 1

            script_lines = [
                "#!/usr/bin/env bash",
                f'# From Buildkite label: {label.replace(chr(10), " ")}',
                "set -euo pipefail",
                f'cd "{REPO_ROOT}"',
            ]
            script_lines.extend(exports)
            script_lines.extend(pys)
            _write_job_script(key, script_lines, jobs_dir)
            if "Perf Test" in label and key not in perf_job_keys:
                perf_job_keys.append(key)

    if not DRY_RUN:
        manifest_path = LOG_DIR / "jobs" / ".perf_job_keys"
        if perf_job_keys:
            manifest_path.write_text("\n".join(perf_job_keys) + "\n", encoding="utf-8")
        elif manifest_path.is_file():
            manifest_path.unlink()

    errs: list[str] = []
    if want_stability and matched_stability == 0:
        errs.append(
            f"No stability jobs matched TEST_TYPE={test_types!r} MODEL_TYPE={model_types!r} "
            f"LABEL_SUBSTR={LABEL_SUBSTR!r} under {REPO_ROOT}"
        )
    if needs_yaml and matched_yaml == 0:
        errs.append(
            f"No YAML steps matched TEST_TYPE={test_types!r} MODEL_TYPE={model_types!r} "
            f"LABEL_SUBSTR={LABEL_SUBSTR!r} in {YML}"
        )
    if errs:
        for e in errs:
            print(e, file=sys.stderr)
        sys.exit(2)


main()
PY

if [[ "${DRY_RUN}" == "1" ]]; then
  exit 0
fi

# Run jobs: YAML perf steps first (if manifest exists), then generate Excel (always when
# manifest non-empty, even if some perf jobs failed; Excel uses whatever JSON exists on disk),
# then remaining jobs. Paths align with nightly Buildkite (tests/dfx/perf/results).
run_generated_jobs_with_tee() {
  set -o pipefail
  local any_fail=0
  local perf_list="${LOG_DIR}/jobs/.perf_job_keys"
  local -a perf_keys=()
  if [[ -f "${perf_list}" ]] && [[ -s "${perf_list}" ]]; then
    mapfile -t perf_keys < "${perf_list}"
  fi

  _run_one_job() {
    local _job="$1"
    local base out job_status
    base="$(basename "${_job}" .sh)"
    out="${LOG_DIR}/${base}.log"
    echo "==> ${_job}  (tee ${out})" >&2
    (cd "${REPO_ROOT}" && bash "${_job}") 2>&1 | tee "${out}"
    job_status="${PIPESTATUS[0]}"
    return "${job_status}"
  }

  local k
  for k in "${perf_keys[@]}"; do
    [[ -n "${k}" ]] || continue
    if [[ ! -f "${LOG_DIR}/jobs/${k}.sh" ]]; then
      echo "warning: perf job script missing, skip: ${LOG_DIR}/jobs/${k}.sh" >&2
      any_fail=1
      continue
    fi
    _run_one_job "${LOG_DIR}/jobs/${k}.sh" || any_fail=1
  done

  if ((${#perf_keys[@]})); then
    local excel_out excel_log excel_status excel_repo_logs
    excel_repo_logs="${REPO_ROOT}/logs"
    mkdir -p "${excel_repo_logs}"
    excel_out="${excel_repo_logs}/nightly_perf_$(date -u +%Y%m%d-%H%M%S).xlsx"
    excel_log="${excel_repo_logs}/generate_nightly_perf_excel.log"
    if [[ "${any_fail}" -ne 0 ]]; then
      echo "Note: one or more perf jobs failed; still running generate_nightly_perf_excel.py " \
        "(report reflects JSON already under tests/dfx/perf/results)." >&2
    fi
    echo "==> python3 tools/nightly/generate_nightly_perf_excel.py -> ${excel_out}  (tee ${excel_log})" >&2
    (
      cd "${REPO_ROOT}" && python3 tools/nightly/generate_nightly_perf_excel.py \
        --input-dir "${REPO_ROOT}/tests/dfx/perf/results" \
        --diffusion-input-dir "${REPO_ROOT}/tests/dfx/perf/results" \
        --output-file "${excel_out}"
    ) 2>&1 | tee "${excel_log}"
    excel_status="${PIPESTATUS[0]}"
    if [[ "${excel_status}" -ne 0 ]]; then
      any_fail=1
      echo "generate_nightly_perf_excel.py failed (exit ${excel_status}). See ${excel_log}" >&2
    fi
  fi

  local -A _perf_done=()
  for k in "${perf_keys[@]}"; do
    [[ -n "${k}" ]] && _perf_done["${k}"]=1
  done

  local _job base
  shopt -s nullglob
  for _job in "${LOG_DIR}/jobs"/*.sh; do
    base="$(basename "${_job}" .sh)"
    [[ ${_perf_done["${base}"]+isset} ]] && continue
    _run_one_job "${_job}" || any_fail=1
  done
  shopt -u nullglob

  if [[ "${any_fail}" -ne 0 ]]; then
    echo "One or more selected jobs failed. See logs under ${LOG_DIR}." >&2
    return 1
  fi
  echo "All selected jobs finished OK. Logs: ${LOG_DIR}/*.log" >&2
  return 0
}

run_generated_jobs_with_tee || exit 1
