#!/usr/bin/env bash

set -e

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

# shellcheck source=/dev/null
. "${DIR}/Utils/shell_include.sh"

ensure_ladybird_source_dir

WPT_SOURCE_DIR=${WPT_SOURCE_DIR:-"${LADYBIRD_SOURCE_DIR}/Tests/LibWeb/WPT/wpt"}
WPT_REPOSITORY_URL=${WPT_REPOSITORY_URL:-"https://github.com/web-platform-tests/wpt.git"}

BUILD_PRESET=${BUILD_PRESET:-Release}

BUILD_DIR=$(get_build_dir "$BUILD_PRESET")

TMPDIR=${TMPDIR:-/tmp}

: "${TRY_SHOW_LOGFILES_IN_TMUX:=false}"
: "${SHOW_LOGFILES:=true}"
: "${SHOW_PROGRESS:=true}"
: "${PARALLEL_INSTANCES:=1}"

if "$SHOW_PROGRESS"; then
    SHOW_LOGFILES=true
    TRY_SHOW_LOGFILES_IN_TMUX=false
fi

sudo_and_ask() {
    local prompt
    prompt="$1"; shift
    if [ -z "$prompt" ]; then
        prompt="Running '${*}' as root, please enter password for %p: "
    else
        prompt="$prompt; please enter password for %p: "
    fi

    sudo --prompt="$prompt" "${@}"
}

default_binary_path() {
    if [ "$(uname -s)" = "Darwin" ]; then
        echo "${BUILD_DIR}/bin/Ladybird.app/Contents/MacOS"
    else
        echo "${BUILD_DIR}/bin"
    fi
}

ladybird_git_hash() {
    pushd "${LADYBIRD_SOURCE_DIR}" > /dev/null
        git rev-parse --short HEAD
    popd > /dev/null
}

run_dir_path() {
    i="$1"; shift
    local runpath="${BUILD_DIR}/wpt/run.$i"
    echo "$runpath"
}

ensure_run_dir() {
    i="$1"; shift
    local runpath

    runpath="$(run_dir_path "$i")"
    if [ ! -d "$runpath" ]; then
        mkdir -p "$runpath/upper" "$runpath/work" "$runpath/merged"
        # shellcheck disable=SC2140
        sudo_and_ask "Mounting overlayfs on $runpath" mount -t overlay overlay -o lowerdir="${WPT_SOURCE_DIR}",upperdir="$runpath/upper",workdir="$runpath/work" "$runpath/merged"
    fi
    echo "$runpath/merged"
}

LADYBIRD_BINARY=${LADYBIRD_BINARY:-"$(default_binary_path)/Ladybird"}
WEBDRIVER_BINARY=${WEBDRIVER_BINARY:-"$(default_binary_path)/WebDriver"}
TEST_WEB_BINARY=${TEST_WEB_BINARY:-"${BUILD_DIR}/bin/test-web"}
WPT_PROCESSES=${WPT_PROCESSES:-$(get_number_of_processing_units)}
WPT_CERTIFICATES=(
    "tools/certs/cacert.pem"
)
WPT_ARGS=(
    "--binary=${LADYBIRD_BINARY}"
    "--webdriver-binary=${WEBDRIVER_BINARY}"
    "--install-webdriver"
    "--webdriver-arg=--force-cpu-painting"
    "--webdriver-arg=--default-time-zone=UTC"
    "--webdriver-arg=--expose-experimental-interfaces"
    "--no-pause-after-test"
    "--install-fonts"
    "${EXTRA_WPT_ARGS[@]}"
)
IMPORT_ARGS=()
WPT_LOG_ARGS=()

ARG0=$0
print_help() {
    NAME=$(basename "$ARG0")
    cat <<EOF
  Usage: $NAME COMMAND [OPTIONS..] [TESTS...]
    Supported COMMANDs:
      update:     Update the Web Platform Tests repository.
      run:        $NAME run [OPTIONS...] [TESTS...]
                      Run the Web Platform Tests.
      compare:    $NAME compare [OPTIONS...] LOG_FILE [TESTS...]
                      Run the Web Platform Tests comparing the results to the expectations in LOG_FILE.
      import:     $NAME import [PATHS...]
                      Fetch the given test file(s) from https://wpt.live/ and create an in-tree test and expectation files.
      list-tests: $NAME list-tests [PATHS..]
                      List the tests in the given PATHS.
      clean:      $NAME clean
                      Clean up the extra resources and directories (if any leftover) created by this script (Linux only).
      bisect:     $NAME bisect BAD_COMMIT GOOD_COMMIT [TESTS...]
                      Find the first commit where a given set of tests produce unexpected results.

    Env vars:
      EXTRA_WPT_ARGS:             Extra arguments for the wpt command, placed at the end; array, default empty
      TRY_SHOW_LOGFILES_IN_TMUX:  Whether to show split logs in tmux; true or false, default false
      SHOW_LOGFILES:              Whether to show logs at all; true or false, default true
      SHOW_PROGRESS:              Whether to show the progress of the tests, default true
                                    implies SHOW_LOGFILES=true and TRY_SHOW_LOGFILES_IN_TMUX=false

    Options for this script:
      --show-window
          Disable headless mode
      --debug-process PROC_NAME
          Enable debugging for the PROC_NAME ladybird process
      --parallel-instances N
          Enable running in chunked mode with N parallel instances
              N=0 to auto-enable if possible
              N=1 to disable chunked mode (default)
              N>1 to enable chunked mode with explicit process count
      --log PATH
          Alias for --log-raw PATH
      --log-(raw|unittest|xunit|html|mach|tbpl|grouped|chromium|wptreport|wptscreenshot) PATH
          Enable the given wpt log option with the given PATH


    Examples:
      $NAME update
          Updates the Web Platform Tests repository.
      $NAME run
          Run all of the Web Platform Tests.
      $NAME run --log expectations.log css dom
          Run the Web Platform Tests in the 'css' and 'dom' directories and save the output to expectations.log.
      $NAME run --log-wptreport expectations.json --log-wptscreenshot expectations.db css dom
          Run the Web Platform Tests in the 'css' and 'dom' directories; save the output in wptreport format to expectations.json and save screenshots to expectations.db.
      $NAME run --parallel-instances 0 --log-wptreport expectations.json --log-wptscreenshot expectations.db css dom
          Run the Web Platform Tests in the 'css' and 'dom' directories in chunked mode; save the output in wptreport format to expectations.json and save screenshots to expectations.db.
      $NAME run --debug-process WebContent http://wpt.live/dom/historical.html
          Run the 'dom/historical.html' test, attaching the debugger to the WebContent process when the browser is launched.
      $NAME compare expectations.log
          Run all of the Web Platform Tests comparing the results to the expectations in before.log.
      $NAME compare --log results.log expectations.log css/CSS2
          Run the Web Platform Tests in the 'css/CSS2' directory, comparing the results to the expectations in expectations.log; output the results to results.log.
      $NAME import html/dom/aria-attribute-reflection.html
          Import the test from https://wpt.live/html/dom/aria-attribute-reflection.html into the Ladybird test suite.
      $NAME import --force html/dom/aria-attribute-reflection.html
          Import the test from https://wpt.live/html/dom/aria-attribute-reflection.html into the Ladybird test suite, redownloading any files that already exist.
      $NAME list-tests css/CSS2 dom
          Show a list of all tests in the 'css/CSS2' and 'dom' directories.
EOF
}

usage() {
    >&2 print_help
    exit 1
}

CMD=$1
[ -n "$CMD" ] || usage
shift
if [ "$CMD" = "--help" ] || [ "$CMD" = "help" ]; then
    print_help
    exit 0
fi

set_logging_flags()
{
    [ -n "${1}" ] || usage;
    [ -n "${2}" ] || usage;

    log_type="${1}"
    log_name="${2}"

    WPT_LOG_ARGS+=("${log_type}" "${log_name}")
}

headless=1
ARG=$1
while [[ "$ARG" =~ ^(--show-window|--debug-process|--parallel-instances|(--log(-(raw|unittest|xunit|html|mach|tbpl|grouped|chromium|wptreport|wptscreenshot))?))$ ]]; do
    case "$ARG" in
        --show-window)
            headless=0
            ;;
        --debug-process)
            process_name="${2}"
            shift
            WPT_ARGS+=( "--webdriver-arg=--debug-process=${process_name}" )
            ;;
        --log)
            set_logging_flags "--log-raw" "${2}"
            shift
            ;;
        --parallel-instances)
            PARALLEL_INSTANCES="${2}"
            shift
            ;;
        *)
            set_logging_flags "${ARG}" "${2}"
            shift
            ;;
    esac

    shift
    ARG=$1
done

if [ $headless -eq 1 ]; then
    WPT_ARGS+=( "--webdriver-arg=--headless" )
fi

exit_if_running_as_root "Do not run WPT.sh as root"

construct_test_list() {
    TEST_LIST=( "$@" )

    for i in "${!TEST_LIST[@]}"; do
        item="${TEST_LIST[i]}"
        item="${item#"$WPT_SOURCE_DIR"/}"
        item="${item#*Tests/LibWeb/WPT/wpt/}"
        item="${item#http://wpt.live/}"
        item="${item#https://wpt.live/}"
        TEST_LIST[i]="$item"
    done
}

ensure_wpt_repository() {
    mkdir -p "${WPT_SOURCE_DIR}"
    pushd "${WPT_SOURCE_DIR}" > /dev/null
        if [ ! -d .git ]; then
            git clone --depth 1 "${WPT_REPOSITORY_URL}" "${WPT_SOURCE_DIR}"
        fi
    popd > /dev/null
}

build_ladybird_and_webdriver() {
    "${LADYBIRD_SOURCE_DIR}"/Meta/ladybird.py build WebDriver
}

update_wpt() {
    ensure_wpt_repository
    pushd "${WPT_SOURCE_DIR}" > /dev/null
        git pull
    popd > /dev/null
}

cleanup_run_infra() {
    readarray -t pids < <(jobs -p)
    for pid in "${pids[@]}"; do
        if ps -p "$pid" > /dev/null; then
            echo "Killing background process $pid"
            kill -HUP "$pid" 2>/dev/null || true
        fi
    done

    readarray -t NSS < <(ip netns list 2>/dev/null | grep -E '^wptns[0-9]+' | awk '{print $1}')
    if [ "${#NSS}" = 0 ]; then
        return
    fi
    echo "Cleaning up namespaces: ${NSS[*]}"
    for i in "${!NSS[@]}"; do
        ns="${NSS[$i]}"

        echo "Cleaning up namespace: $ns"

        # Delete namespace
        if sudo_and_ask "" ip netns list | grep -qw "$ns"; then
            sudo_and_ask "Removing netns $ns" ip netns delete "$ns" || echo "  failed to delete netns $ns"
        fi

        # Remove hosts override
        sudo_and_ask "Removing support files for netns $ns" rm -rf "/etc/netns/$ns" || echo "  failed to delete /etc/netns/$ns"
    done
}

cleanup_run_dirs() {
    readarray -t dirs < <(ls "${BUILD_DIR}/wpt" 2>/dev/null)
    if [ "${#dirs}" = 0 ]; then
        return
    fi

    echo "Cleaning run dirs: ${dirs[*]}"
    for dir in "${dirs[@]}"; do
        mount_path="${BUILD_DIR}/wpt/$dir/merged"
        for _ in $(seq 1 5); do
            readarray -t pids_in_use < <(sudo_and_ask "" lsof "$mount_path" 2>/dev/null | cut -f2 -d' ')
            [ "${#pids_in_use[@]}" = 0 ] && break
                echo Trying to kill procs: "${pids_in_use[@]}"
                kill -INT "${pids_in_use[@]}" 2>/dev/null || true
        done
        sudo_and_ask "" umount "$mount_path" || true
    done
    rm -fr "${BUILD_DIR}/wpt"
}

cleanup_merge_dirs_and_infra() {
    # Cleanup is only needed on Linux
    if [[ $OSTYPE == 'linux'* ]]; then
        cleanup_run_dirs
        cleanup_run_infra
    fi
}
trap cleanup_merge_dirs_and_infra EXIT INT TERM

make_instances() {
    if [ "${PARALLEL_INSTANCES}" = 1 ]; then
        echo 1
        return
    fi

    if ! command -v ip &>/dev/null; then
        echo "the 'ip' command is required to run WPT in chunked mode" >&2
        echo 1
        return
    fi

    if ! sudo_and_ask "Making test netns 'testns'" ip netns add testns; then
        echo "ip netns failed, chunked mode not available" >&2
        echo 1
        return
    fi
    sudo_and_ask "Cleaning up test netns 'testns'" ip netns delete testns

    explicit_count="${PARALLEL_INSTANCES}"

    local total_cores count ns
    total_cores=$(nproc)
    count=$(( total_cores / 2 ))
    (( count < 1 )) && count=1

    if (( explicit_count > 0 )); then
        count="$explicit_count"
    fi

    for i in $(seq 0 $((count - 1))); do
        ns="wptns$i"

        # Create namespace
        sudo_and_ask "" ip netns add "$ns"
        sudo_and_ask "" ip netns exec "$ns" ip link set lo up

        # Setup DNS and hosts (we've messed with it before getting here)
        sudo_and_ask "" mkdir -p "/etc/netns/$ns"
        sudo_and_ask "" cp /etc/hosts "/etc/netns/$ns/hosts"
    done

    echo "$count"
}

instance_run() {
    local idx="$1"; shift
    local rundir="$1"; shift
    local ns="wptns$idx"
    if sudo_and_ask "" ip netns list | grep -qw "$ns"; then
        (
            cd "$rundir"
            sudo_and_ask "" ip netns exec "$ns" sudo -u "$USER" -- env "PATH=$PATH" "$@"
        )
    else
        echo "  netns $ns not found, running in the host namespace"
        (
            cd "${WPT_SOURCE_DIR}"
            "$@"
        )
    fi
}

show_files() {
    if ! "$SHOW_LOGFILES"; then
        return
    fi
    local files=("$@")
    if ! command -v tmux &>/dev/null || ! "$TRY_SHOW_LOGFILES_IN_TMUX"; then
        if "$TRY_SHOW_LOGFILES_IN_TMUX"; then
            echo "tmux is not available, falling back to tail"
        fi
        if "$SHOW_PROGRESS"; then
            bash "${DIR}/watch_wpt_progress.sh" "${files[@]}" &
        else
            tail -f "${files[@]}" &
        fi
        PID=$!
        for pid in $(jobs -p | grep -v $PID); do
            # shellcheck disable=SC2009
            ps | grep -q "$pid" && wait "$pid"
        done
        kill -HUP $PID
    else
        tmux new-session -d

        tmux send-keys "less +F ${files[0]}" C-m

        for ((i = 1; i < ${#files[@]}; i++)); do
            if (( i % 2 == 1 )); then
                tmux split-window -h "less +F ${files[i]}"
            else
                tmux split-window -v "less +F ${files[i]}"
            fi
            tmux select-layout tiled > /dev/null
        done

        tmux attach
    fi
}

copy_results_to() {
    local target="$1"; shift
    local runcount="$1"; shift
    mkdir -p "$target"
    for i in $(seq 0 $((runcount - 1))); do
        for f in "$(run_dir_path "$i")/upper"/*; do
            cp -r "$f" "$target/$(basename "$f").run_$i"
        done
    done
}

show_summary() {
    local logs=("$@")
    local total_tests=0
    local expected=0
    local skipped=0
    local errored=0
    local subtest_issues=0
    local max_time=0

    for out_file in "${logs[@]}"; do
        # wpt puts random garbage in the output, strip those (as they're all nonprint)
        mapfile -t lines < <(grep -A4 -aE 'Ran [0-9]+ tests finished in' "$out_file" \
                             | iconv -f utf-8 -t ascii//TRANSLIT 2>/dev/null \
                             | sed 's/[^[:print:]]//g')

        for line in "${lines[@]}"; do
            if [[ $line =~ Ran[[:space:]]([0-9]+)[[:space:]]tests[[:space:]]finished[[:space:]]in[[:space:]]([0-9.]+) ]]; then
                (( total_tests += BASH_REMATCH[1] ))
                time=${BASH_REMATCH[2]}
                [[ $(echo "$time > $max_time" | bc -l) == 1 ]] && max_time=$time
            elif [[ $line =~ ([0-9]+)[[:space:]]ran[[:space:]]as[[:space:]]expected ]]; then
                (( expected += BASH_REMATCH[1] ))
            elif [[ $line =~ ([0-9]+)[[:space:]]tests[[:space:]]skipped ]]; then
                (( skipped += BASH_REMATCH[1] ))
            elif [[ $line =~ ([0-9]+)[[:space:]]tests[[:space:]](crashed|timed[[:space:]]out|had[[:space:]]errors)[[:space:]]unexpectedly ]]; then
                (( errored += BASH_REMATCH[1] ))
            elif [[ $line =~ ([0-9]+)[[:space:]]tests[[:space:]]had[[:space:]]unexpected[[:space:]]subtest[[:space:]]results ]]; then
                (( subtest_issues += BASH_REMATCH[1] ))
            fi
        done
    done

    echo "Total tests run: $total_tests"
    echo "Ran as expected: $expected"
    echo "Skipped: $skipped"
    echo "Errored unexpectedly: $errored"
    echo "Unexpected subtest results: $subtest_issues"
    echo "Longest run time: ${max_time}s"
}

# run_wpt_chunked <#processes> <wpt args...>
run_wpt_chunked() {
    local procs concurrency
    procs="$1"; shift

    # Ensure open files limit is at least 1024, so the WPT runner does not run out of descriptors
    if [ "$(ulimit -n)" -lt $((1024 * procs)) ]; then
        ulimit -S -n $((1024 * procs))
    fi

    if [ "$procs" -le 1 ]; then
        command=(./wpt run -f --browser-version="1.0-$(ladybird_git_hash)" --processes="${WPT_PROCESSES}" "$@")
        echo "${command[@]}"
        "${command[@]}"
        return
    fi

    concurrency=$(( $(nproc) * 2 / procs ))

    echo "Preparing the venv setup..."
    base_venv="${BUILD_DIR}/wpt-prep/_venv"
    ./wpt --venv "$base_venv" run "${WPT_ARGS[@]}" ladybird THIS_TEST_CANNOT_POSSIBLY_EXIST || true

    echo "Launching $procs chunked instances (concurrency=$concurrency each)"
    local logs=()
    local run_start_time
    run_start_time=$(date +"%Y%m%d%H%M%S")

    for i in $(seq 0 $((procs - 1))); do
        local rundir runpath logpath
        rundir="$(ensure_run_dir "$i")"
        runpath="$(run_dir_path "$i")"
        logpath="$runpath/upper/run.logs"
        echo "rundir at $rundir, logs in $logpath"
        touch "$logpath"
        logs+=("$logpath")

        cp -r "$base_venv" "${runpath}/_venv"

        command=(./wpt --venv "${runpath}/_venv" \
            run \
            --this-chunk="$((i + 1))" \
            --total-chunks="$procs" \
            --chunk-type=hash \
            -f \
            --browser-version="1.0-$(ladybird_git_hash)"
            --processes="$concurrency" \
            "$@")
        echo "[INSTANCE $i / ns wptns$i] ${command[*]}"
        instance_run "$i" "$rundir" script -q "$logpath" -c "$(printf "%q " "${command[@]}")" &>/dev/null &
    done

    show_files "${logs[@]}"
    wait

    copy_results_to "${BUILD_DIR}/wpt-run-${run_start_time}" "$procs"
    show_summary "${logs[@]}"
}

absolutize_log_args() {
    for ((i=0; i<${#WPT_LOG_ARGS[@]}; i += 2)); do
        WPT_LOG_ARGS[i + 1]="$(absolutize_path "${WPT_LOG_ARGS[i + 1]}")"
    done
}

update_hosts_file_if_needed() {
    pushd "${WPT_SOURCE_DIR}" > /dev/null
        if [ "$(comm -13 <(sort -u /etc/hosts) <(./wpt make-hosts-file | sort -u) | wc -l)" -gt 0 ]; then
            ./wpt make-hosts-file | sudo_and_ask "Appending wpt hosts to /etc/hosts" tee -a /etc/hosts
        fi
    popd > /dev/null
}

execute_wpt() {
    local procs

    procs=$(make_instances)
    if [[ "$procs" -le 1 ]]; then
        absolutize_log_args
    fi

    pushd "${WPT_SOURCE_DIR}" > /dev/null
        for certificate_path in "${WPT_CERTIFICATES[@]}"; do
            if [ ! -f "${certificate_path}" ]; then
                echo "Certificate not found: \"${certificate_path}\""
                exit 1
            fi
            WPT_ARGS+=( "--webdriver-arg=--certificate=${certificate_path}" )
        done
        construct_test_list "${@}"
        run_wpt_chunked "$procs" "${WPT_ARGS[@]}" "${WPT_LOG_ARGS[@]}" ladybird "${TEST_LIST[@]}"
    popd > /dev/null
}

run_wpt() {
    ensure_wpt_repository
    build_ladybird_and_webdriver
    update_hosts_file_if_needed
    execute_wpt "${@}"
}

serve_wpt()
{
    ensure_wpt_repository
    update_hosts_file_if_needed

    pushd "${WPT_SOURCE_DIR}" > /dev/null
        ./wpt serve
    popd > /dev/null
}

cleanup_bisect()
{
    local temp_file_directory="$1"; shift
    if [ -d "${temp_file_directory}" ]; then
        echo "Removing temp file directory: ${temp_file_directory}"
        rm -rf "${temp_file_directory}"
    fi

    git bisect reset
}

bisect_wpt()
{
    if ! git diff-index --quiet HEAD --; then
        echo "You have uncommitted changes, please commit or stash them before bisecting."
        exit 1
    fi

    local bad="$1"; shift
    local good="$1"; shift
    # Commits from before ladybird.py was added don't currently work with this script
    OLDEST_COMMIT_ALLOWED="061a7f766ce"

    if ! git rev-parse --verify "${bad}" >/dev/null 2>&1; then
        echo "Bad commit '${bad}' not found."
        exit 1
    fi

    if ! git rev-parse --verify "${good}" >/dev/null 2>&1; then
        echo "Good commit '${good}' not found."
        exit 1
    fi

    if ! git merge-base --is-ancestor ${OLDEST_COMMIT_ALLOWED} "${good}"; then
        echo "Commits older than ${OLDEST_COMMIT_ALLOWED} aren't allowed (because ladybird.py is required)."
        exit 1
    fi

    if [ "${good}" = "${bad}" ]; then
        echo "The good commit and the bad commit must be different."
        exit 1
    fi

    if ! git merge-base --is-ancestor "${good}" "${bad}"  ; then
        echo "The good commit must be older than the bad commit."
        exit 1
    fi

    ensure_wpt_repository
    construct_test_list "${@}"

    pushd "${LADYBIRD_SOURCE_DIR}" > /dev/null
      local temp_file_directory_base
      temp_file_directory_base="$(mktemp -p "${TMPDIR}" -d "wpt-bisect-helper-XXXXXX")"
      mkdir "${temp_file_directory_base}/Meta"
      local baseline_log_file
      baseline_log_file="$(mktemp -p "${temp_file_directory_base}" -u "XXXXXX.log")"
      local current_branch_or_commit
      current_branch_or_commit="$(git branch --show-current 2> /dev/null)"
      if [ -z "${current_branch_or_commit}" ]; then
          current_branch_or_commit="$(git rev-parse HEAD)"
      fi

      # We create the baseline log file against the bad commit bcause building it may be significantly faster if the
      # good commit is significantly older than the bad commit.
      git checkout "${bad}" 2> /dev/null
      trap 'git checkout "${current_branch_or_commit}" 2> /dev/null' EXIT INT TERM
      echo "Generating baseline log file at \"${baseline_log_file}\""
      $0 run --log "${baseline_log_file}" "${@}" || true
      trap - EXIT INT TERM
      git checkout "${current_branch_or_commit}" 2> /dev/null

      # We need to copy scripts that will run during bisection to ensure that we will always have the latest version.
      required_build_files=(
          "shell_include.sh"
          "wpt-bisect-helper.sh"
      )
      for file in "${required_build_files[@]}"; do
          cp "${LADYBIRD_SOURCE_DIR}/Meta/${file}" "${temp_file_directory_base}/Meta/${file}"
      done
      cp "$0" "${temp_file_directory_base}/Meta/WPT.sh"

      git bisect start "${bad}" "${good}"
      trap cleanup_bisect INT TERM
      git bisect run "${temp_file_directory_base}/Meta/wpt-bisect-helper.sh" "${baseline_log_file}" "${@}" || true
      trap - INT TERM
      cleanup_bisect "${temp_file_directory_base}"
    popd > /dev/null
}

list_tests_wpt()
{
    ensure_wpt_repository

    construct_test_list "${@}"

    pushd "${WPT_SOURCE_DIR}" > /dev/null
        ./wpt run --list-tests ladybird "${TEST_LIST[@]}"
    popd > /dev/null
}

import_wpt()
{
    ensure_wpt_repository

    pushd "${WPT_SOURCE_DIR}" > /dev/null
       if ! git fetch origin > /dev/null; then
            echo "Failed to fetch the WPT repository, please check your network connection."
            exit 1
        fi
        local local_hash
        local_hash=$(git rev-parse HEAD)
        local remote_hash
        remote_hash=$(git rev-parse origin/master)

        if [ "$local_hash" != "$remote_hash" ]; then
            echo "WPT repository is not up to date, please run '$0 update' first."
            exit 1
        fi
    popd > /dev/null

    for i in "${!INPUT_PATHS[@]}"; do
        item="${INPUT_PATHS[i]}"
        item="${item#http://wpt.live/}"
        item="${item#https://wpt.live/}"
        INPUT_PATHS[i]="$item"
    done

    RAW_TESTS=()
    while IFS= read -r test_file; do
        RAW_TESTS+=("${test_file%%\?*}")
    done < <(
        "${ARG0}" list-tests "${INPUT_PATHS[@]}"
    )
    if [ "${#RAW_TESTS[@]}" -eq 0 ]; then
        echo "No tests found for the given paths"
        exit 1
    fi

    TESTS=()
    while IFS= read -r test_file; do
        TESTS+=("$test_file")
    done < <(printf "%s\n" "${RAW_TESTS[@]}" | sort -u)

    pushd "${LADYBIRD_SOURCE_DIR}" > /dev/null
        ./Meta/ladybird.py build test-web
        trap 'exit 1' EXIT INT TERM
        for path in "${TESTS[@]}"; do
            echo "Importing test from ${path}"
            if ! ./Meta/import-wpt-test.py "${IMPORT_ARGS[@]}" https://wpt.live/"${path}"; then
                continue
            fi
            "${TEST_WEB_BINARY}" --rebaseline -f "$path" || true
        done
        trap - EXIT INT TERM
    popd > /dev/null
}

compare_wpt() {
    ensure_wpt_repository
    METADATA_DIR=$(mktemp -d)
    pushd "${WPT_SOURCE_DIR}" > /dev/null
        ./wpt update-expectations --product ladybird --full --metadata="${METADATA_DIR}" "${INPUT_LOG_NAME}"
    popd > /dev/null
    WPT_ARGS+=( "--metadata=${METADATA_DIR}" )
    build_ladybird_and_webdriver
    update_hosts_file_if_needed
    execute_wpt "${@}"
    rm -rf "${METADATA_DIR}"
}

if [[ "$CMD" =~ ^(update|clean|run|serve|bisect|compare|import|list-tests)$ ]]; then
    case "$CMD" in
        update)
            update_wpt
            ;;
        run)
            run_wpt "${@}"
            ;;
        clean)
            if [[ $OSTYPE == 'linux'* ]]; then
                cleanup_run_infra
                cleanup_run_dirs true
            else
                echo "Cleanup is only needed on Linux"
            fi
            ;;
        serve)
            serve_wpt
            ;;
        bisect)
          if [ $# -lt 3 ]; then
              echo "Usage: $0 bisect <bad> <good> [test paths...]"
              usage
          fi
          bisect_wpt "${@}"
          ;;
        import)
            while [[ "$1" =~ ^--(force|wpt-base-url)$ ]]; do
                if [ "$1" = "--wpt-base-url" ]; then
                    if [ -z "$2" ]; then
                        echo "Missing argument for --wpt-base-url"
                        usage
                    fi
                    IMPORT_ARGS+=( "--wpt-base-url=$2" )
                    shift
                else
                    IMPORT_ARGS+=( "$1" )
                fi
                shift
            done
            if [ $# -eq 0 ]; then
                usage
            fi
            INPUT_PATHS=( "$@" )
            import_wpt
            ;;

        compare)
            INPUT_LOG_NAME="$(realpath "$1")"
            if [ ! -f "$INPUT_LOG_NAME" ]; then
                echo "Log file not found: \"${INPUT_LOG_NAME}\""
                usage;
            fi
            shift
            compare_wpt "${@}"
            ;;
        list-tests)
            list_tests_wpt "${@}"
            ;;
    esac
else
    >&2 echo "Unknown command: $CMD"
    usage
fi
