#!/usr/bin/env bash
set -uo pipefail

# ============================================================
# XH-1 Research Harness v3
#
# Pure Bash
# OpenAI-compatible providers
# OpenRouter + Ollama supported
#
# Pipeline:
#
#   Research → Review
#                │
#          ┌─────┴─────┐
#          PASS        FAIL
#           │            │
#         accept       Revision
#                        │
#                        ▼
#                      Review
#
# API failures are NOT research failures.
#
# ============================================================

ROOT="${XH1_RESEARCH_ROOT:-research}"
CONFIG="${XH1_RESEARCH_CONFIG:-xh1-research.conf}"
STATE="${ROOT}/.xh1"

mkdir -p \
    "$STATE/logs" \
    "$STATE/responses" \
    "$STATE/runs"

RUN_ID=""

# ------------------------------------------------------------
# Defaults
# ------------------------------------------------------------

API_BASE_URL="${API_BASE_URL:-https://openrouter.ai/api/v1}"
API_KEY_ENV="${API_KEY_ENV:-OPENROUTER_API_KEY}"

RESEARCH_PROVIDER="${RESEARCH_PROVIDER:-openrouter}"
RESEARCH_MODEL="${RESEARCH_MODEL:-}"

REVIEW_PROVIDER="${REVIEW_PROVIDER:-ollama}"
REVIEW_MODEL="${REVIEW_MODEL:-gemma4:12b}"

REVISION_PROVIDER="${REVISION_PROVIDER:-openrouter}"
REVISION_MODEL="${REVISION_MODEL:-}"

MAX_RESEARCH_ROUNDS="${MAX_RESEARCH_ROUNDS:-3}"
MAX_API_RETRIES="${MAX_API_RETRIES:-4}"

DELAY_SECONDS="${DELAY_SECONDS:-5}"
MAX_ITERATIONS="${MAX_ITERATIONS:-0}"

REVIEW_ENABLED="${REVIEW_ENABLED:-true}"
AUTO_COMMIT="${AUTO_COMMIT:-false}"

OLLAMA_CONTEXT="${OLLAMA_CONTEXT:-16384}"

# ------------------------------------------------------------
# Provider defaults
# ------------------------------------------------------------

OPENROUTER_URL="${OPENROUTER_URL:-https://openrouter.ai/api/v1}"
QWEN_URL="${QWEN_URL:-https://dashscope-intl.aliyuncs.com/compatible-mode/v1}"
OLLAMA_URL="${OLLAMA_URL:-http://127.0.0.1:11434/v1}"

OPENROUTER_KEY_ENV="${OPENROUTER_KEY_ENV:-OPENROUTER_API_KEY}"

# ------------------------------------------------------------
# Logging
# ------------------------------------------------------------

timestamp() {
    date -u '+%Y%m%dT%H%M%SZ'
}

now() {
    date -u '+%Y-%m-%dT%H:%M:%SZ'
}

log() {
    local message="$*"

    echo "[$(now)] $message" |
        tee -a "$STATE/logs/harness.log"
}

warn() {
    log "WARNING: $*"
}

die() {
    echo "ERROR: $*" >&2
    exit 1
}

# ------------------------------------------------------------
# Dependencies
# ------------------------------------------------------------

require() {
    command -v "$1" >/dev/null 2>&1 ||
        die "Required command not found: $1"
}

require_dependencies() {
    require bash
    require curl
    require jq
    require find
    require grep
    require sed
    require awk
    require date
}

# ------------------------------------------------------------
# Configuration
# ------------------------------------------------------------

load_config() {

    if [[ -f "$CONFIG" ]]; then
        # shellcheck disable=SC1090
        source "$CONFIG"
    fi
}

init_config() {

    if [[ -f "$CONFIG" ]]; then
        echo "Configuration already exists:"
        echo "  $CONFIG"
        return 0
    fi

    cat > "$CONFIG" <<'CONFIG'
# ============================================================
# XH-1 Research Harness Configuration
# ============================================================

# ------------------------------------------------------------
# Researcher
# ------------------------------------------------------------

RESEARCH_PROVIDER="openrouter"
RESEARCH_MODEL="minimax/minimax-m3:free"

# ------------------------------------------------------------
# Reviewer
#
# Recommended for 16GB Apple Silicon:
#
#   Ollama + Gemma 4 12B
# ------------------------------------------------------------

REVIEW_PROVIDER="ollama"
REVIEW_MODEL="gemma4:12b"

# ------------------------------------------------------------
# Revision
# ------------------------------------------------------------

REVISION_PROVIDER="openrouter"
REVISION_MODEL="minimax/minimax-m3:free"

# ------------------------------------------------------------
# OpenRouter
# ------------------------------------------------------------

OPENROUTER_URL="https://openrouter.ai/api/v1"

OPENROUTER_KEY_ENV="OPENROUTER_API_KEY"

# ------------------------------------------------------------
# Ollama
# ------------------------------------------------------------

OLLAMA_URL="http://127.0.0.1:11434/v1"

# Context size for local Ollama models.
#
# 8192  = safer memory usage
# 16384 = recommended starting point
# 32768 = larger context, more memory
#
OLLAMA_CONTEXT=16384

# ------------------------------------------------------------
# Research behavior
# ------------------------------------------------------------

# Maximum Research -> Review -> Revision cycles.
MAX_RESEARCH_ROUNDS=3

# Maximum API attempts for a request.
MAX_API_RETRIES=4

# Delay between successful topic processing.
DELAY_SECONDS=5

# Maximum topics processed by run-all.
#
# 0 = unlimited
MAX_ITERATIONS=0

# Enable independent reviewer.
REVIEW_ENABLED=true

# Automatically commit accepted documents.
AUTO_COMMIT=false
CONFIG

    echo "Created $CONFIG"
}

# ------------------------------------------------------------
# Provider handling
# ------------------------------------------------------------

provider_url() {

    local provider="$1"

    case "$provider" in

        openrouter)
            printf '%s' "$OPENROUTER_URL"
            ;;

        ollama)
            printf '%s' "$OLLAMA_URL"
            ;;

        qwen)
            printf '%s' "$QWEN_URL"
            ;;

        *)
            local variable="PROVIDER_${provider}_URL"
            local value="${!variable:-}"

            [[ -n "$value" ]] ||
                die "No URL configured for provider: $provider"

            printf '%s' "$value"
            ;;

    esac
}

provider_key() {

    local provider="$1"

    case "$provider" in

        ollama)
            printf ''
            ;;

        openrouter)
            local env="${OPENROUTER_KEY_ENV:-OPENROUTER_API_KEY}"
            printf '%s' "${!env:-}"
            ;;

        qwen)
            local env="${QWEN_KEY_ENV:-QWEN_API_KEY}"
            printf '%s' "${!env:-}"
            ;;

        *)
            local env_var="PROVIDER_${provider}_KEY_ENV"
            local env="${!env_var:-}"

            if [[ -n "$env" ]]; then
                printf '%s' "${!env:-}"
            fi
            ;;

    esac
}

provider_extra_json() {

    local provider="$1"

    if [[ "$provider" == "ollama" ]]; then

        jq -n \
            --argjson ctx "$OLLAMA_CONTEXT" \
            '{
                num_ctx: $ctx
            }'

    else

        printf '{}'

    fi
}

# ------------------------------------------------------------
# API request
# ------------------------------------------------------------

api_request() {

    local provider="$1"
    local model="$2"
    local system_prompt="$3"
    local user_prompt="$4"
    local output_file="$5"

    local base_url
    local endpoint
    local key

    base_url="$(provider_url "$provider")"
    endpoint="${base_url%/}/chat/completions"

    key="$(provider_key "$provider")"

    local attempt
    local response_file
    local http_code
    local content
    local bytes

    for ((attempt=1; attempt<=MAX_API_RETRIES; attempt++)); do

        log "API: provider=$provider model=$model attempt=$attempt"

        response_file="$STATE/responses/${RUN_ID:-manual}-$(timestamp)-${RANDOM}.json"

        local extra
        extra="$(provider_extra_json "$provider")"

        local payload

        payload="$(
            jq -n \
                --arg model "$model" \
                --arg system "$system_prompt" \
                --arg user "$user_prompt" \
                --argjson extra "$extra" \
                '{
                    model: $model,
                    messages: [
                        {
                            role: "system",
                            content: $system
                        },
                        {
                            role: "user",
                            content: $user
                        }
                    ],
                    temperature: 0.2
                } + $extra'
        )"

        local curl_args=(
            --silent
            --show-error
            --connect-timeout 30
            --max-time 1800
            -H "Content-Type: application/json"
        )

        if [[ -n "$key" ]]; then
            curl_args+=(
                -H "Authorization: Bearer $key"
            )
        fi

        if curl "${curl_args[@]}" \
            -o "$response_file" \
            -w '%{http_code}' \
            --data "$payload" \
            "$endpoint" \
            > "${response_file}.code"
        then
            http_code="$(cat "${response_file}.code")"
        else
            http_code="$(cat "${response_file}.code" 2>/dev/null || echo "000")"
        fi

        rm -f "${response_file}.code"

        if [[ "$http_code" != "200" ]]; then

            log "API HTTP status: $http_code"

            case "$http_code" in

                429)
                    warn "Rate limited by $provider."

                    local wait
                    wait=$((attempt * attempt * 5))

                    log "Waiting ${wait}s before retry."

                    sleep "$wait"
                    ;;

                000)
                    warn "Network/API connection failure."

                    sleep $((attempt * 3))
                    ;;

                4*)
                    warn "Client/API error from $provider."

                    if (( attempt < MAX_API_RETRIES )); then
                        sleep $((attempt * 3))
                    fi
                    ;;

                5*)
                    warn "Provider server error."

                    sleep $((attempt * 5))
                    ;;

                *)
                    warn "Unexpected HTTP status."

                    sleep $((attempt * 3))
                    ;;
            esac

            continue
        fi

        # ----------------------------------------------------
        # Extract response
        # ----------------------------------------------------

        if ! content="$(
            jq -er \
                '.choices[0].message.content // empty' \
                "$response_file"
        )"; then

            warn "Provider returned malformed response."

            sleep $((attempt * 2))
            continue
        fi

        bytes="${#content}"

        # Empty responses are API failures.
        if (( bytes < 10 )); then

            warn "Provider returned an empty/near-empty response (${bytes} bytes)."

            sleep $((attempt * 2))
            continue
        fi

        printf '%s\n' "$content" > "$output_file"

        log "Response received: ${bytes} bytes"

        return 0
    done

    warn "API request exhausted all retries."

    return 1
}

# ------------------------------------------------------------
# Research discovery
# ------------------------------------------------------------

is_soon() {

    grep -qE '^SOON[[:space:]]*$' "$1"
}

find_next_task() {

    while IFS= read -r -d '' file; do

        [[ "$file" == "$STATE"/* ]] && continue

        if is_soon "$file"; then

            if [[ -z "$RUN_ID" ]] ||
               {
                   ! run_has_completed "$file" &&
                   ! run_has_failed "$file"
               }
            then
                printf '%s\n' "$file"
                return 0
            fi

        fi

    done < <(
        find "$ROOT" \
            -type f \
            -name '*.md' \
            -not -path "$STATE/*" \
            -print0
    )

    return 1
}

# ------------------------------------------------------------
# Context
# ------------------------------------------------------------

build_context() {

    local file="$1"

    local relative="${file#"$ROOT"/}"
    local area="${relative%%/*}"

    cat <<EOF
XH-1 RESEARCH PROJECT
=====================

Project:
XH-1

Architecture:
Custom 128-core RISC-V processor.

Repository:
XH-1 Research

Current document:
$file

Research area:
$area

The XH-1 processor is a research CPU project.
It is a custom 128-core RISC-V architecture.

This document is one part of the project's research
documentation.

Do not invent implementation facts.

Clearly distinguish:

- established facts
- RISC-V specification requirements
- published research
- industry practice
- reasonable engineering proposals
- assumptions
- unresolved questions

Prefer technically precise terminology.

If evidence is unavailable, explicitly say so.

CURRENT DOCUMENT
================

EOF

    cat "$file"
}

# ------------------------------------------------------------
# Research prompt
# ------------------------------------------------------------

research_prompt() {

    local file="$1"

    cat <<EOF
You are the primary research engineer for the XH-1 CPU project.

XH-1 is a custom 128-core RISC-V processor.

Research the topic represented by the supplied document.

Your task is to produce a technically rigorous research document
that can eventually become part of the XH-1 architecture documentation.

TOPIC
=====

$file

REQUIREMENTS
============

1. Explain the topic thoroughly.

2. Relate the topic specifically to a 128-core RISC-V processor.

3. Identify relevant RISC-V architectural requirements.

4. Discuss realistic implementation approaches.

5. Compare meaningful alternatives.

6. Discuss advantages and disadvantages.

7. Identify scalability problems caused by 128 cores.

8. Identify interactions with:
   - pipeline
   - cache hierarchy
   - memory system
   - interconnect
   - coherence
   - interrupts
   - operating system
   - verification
   - performance

9. Do not present assumptions as facts.

10. Cite standards, papers, manuals, or reputable technical
    sources where possible.

11. Do not fabricate citations.

12. Identify unresolved design questions.

13. Make recommendations only when sufficient evidence exists.

14. If the XH-1 implementation is not yet decided, explicitly
    mark it as a proposal rather than an existing feature.

15. Prefer quantitative analysis where reasonable.

OUTPUT FORMAT
=============

Return ONLY the proposed Markdown document.

Do not include:
- conversational commentary
- "here is the document"
- analysis outside the document
- review commentary

The result should be suitable for directly replacing the
SOON placeholder if it passes independent review.

CURRENT DOCUMENT
================

$(build_context "$file")
EOF
}

# ------------------------------------------------------------
# Review prompt
# ------------------------------------------------------------

review_prompt() {

    local file="$1"
    local candidate="$2"

    cat <<EOF
You are the INDEPENDENT TECHNICAL REVIEWER for the XH-1 CPU project.

XH-1 is a custom 128-core RISC-V processor.

Your task is to critically review the proposed research document.

You are NOT the author.
You must NOT rewrite the document.

Your job is to identify:

- factual errors
- RISC-V specification errors
- incorrect terminology
- unsupported claims
- missing evidence
- bad citations
- internal contradictions
- missing design alternatives
- unrealistic assumptions
- scalability problems
- 128-core implications
- memory-system interactions
- cache/coherence interactions
- interconnect implications
- verification problems
- power/area implications
- software implications
- recommendations that do not follow from the evidence

IMPORTANT

Do not reject a document merely because XH-1 has not made
a final architectural decision.

A research document may contain:

- proposals
- assumptions
- alternatives
- open questions
- preliminary recommendations

Those are acceptable when clearly identified.

Do reject statements that present speculation as established fact.

Do not invent facts.

Do not invent citations.

Do not claim that RISC-V requires something unless it actually does.

Pay particular attention to whether claims are:

FACT
SPECIFICATION
RESEARCH
ASSUMPTION
PROPOSAL
OPEN QUESTION

DOCUMENT
========

$file

$candidate

============================================================
REVIEW CRITERIA
============================================================

1. Technical correctness

2. RISC-V compliance

3. Microarchitectural correctness

4. Memory-model correctness

5. Cache/coherence correctness

6. Interconnect/scalability

7. 128-core implications

8. Performance

9. Area

10. Power/energy

11. Verification

12. Software/OS implications

13. Evidence and citations

14. Internal consistency

15. Quality of recommendations

============================================================
VERDICT RULE
============================================================

PASS means:

The document is technically credible for its research stage.

Minor improvements may exist.

FAIL means:

There are substantive technical errors, unsupported claims,
contradictions, missing critical alternatives, or other issues
that should be corrected before acceptance.

============================================================
CRITICAL OUTPUT REQUIREMENT
============================================================

Your response MUST contain a machine-readable JSON object
as the FINAL thing in your response.

Do not omit it.

The JSON MUST have exactly this general structure:

{
  "verdict": "PASS",
  "confidence": "HIGH",
  "issues": [],
  "required_fixes": []
}

For a failing document:

{
  "verdict": "FAIL",
  "confidence": "HIGH",
  "issues": [
    "Description of issue"
  ],
  "required_fixes": [
    "Description of required fix"
  ]
}

The value of "verdict" MUST be exactly:

PASS

or:

FAIL

The value of "confidence" MUST be:

HIGH

MEDIUM

or:

LOW

The JSON object MUST be valid JSON.

Do not put Markdown fences around the JSON.

You may provide a normal human-readable review before the JSON.

The FINAL JSON object is the authoritative verdict.

============================================================

Begin your review now.

EOF
}

# ------------------------------------------------------------
# Revision prompt
# ------------------------------------------------------------

revision_prompt() {

    local file="$1"
    local candidate="$2"
    local review="$3"

    cat <<EOF
You are the revision engineer for the XH-1 CPU research project.

XH-1 is a custom 128-core RISC-V processor.

Revise the research document using the independent review.

Do NOT blindly accept every reviewer statement.

If the reviewer is technically incorrect, correct the issue
using sound engineering reasoning.

Do not invent facts.

Do not fabricate citations.

Clearly mark assumptions and proposals.

Preserve useful research from the existing document.

The revised result must be a complete standalone Markdown
document.

OUTPUT ONLY THE REVISED MARKDOWN DOCUMENT.

TOPIC
=====

$file

CURRENT DOCUMENT
================

$candidate

INDEPENDENT REVIEW
==================

$review
EOF
}

# ------------------------------------------------------------
# Review verdict
# ------------------------------------------------------------
parse_verdict() {
    local review="$1"

    python3 - "$review" <<'PY'
import json
import re
import sys

path = sys.argv[1]

with open(path, "r", encoding="utf-8") as f:
    text = f.read()

# ------------------------------------------------------------
# 1. Find JSON objects containing a "verdict" field.
# ------------------------------------------------------------

matches = re.findall(
    r'\{[\s\S]*?"verdict"\s*:\s*"(?:PASS|FAIL)"[\s\S]*?\}',
    text,
    re.IGNORECASE
)

for candidate in reversed(matches):
    try:
        obj = json.loads(candidate)
        verdict = str(obj.get("verdict", "")).upper()

        if verdict in ("PASS", "FAIL"):
            print(verdict)
            sys.exit(0)

    except json.JSONDecodeError:
        pass

# ------------------------------------------------------------
# 2. Legacy format.
# ------------------------------------------------------------

matches = re.findall(
    r'VERDICT\s*:\s*(PASS|FAIL)',
    text,
    re.IGNORECASE
)

if matches:
    print(matches[-1].upper())
    sys.exit(0)

print("INVALID")
PY
}

repair_review_verdict() {

    local original="$1"
    local repaired="$2"

    local prompt

    prompt=$(cat <<EOF
You are a review-output formatter.

The following is a technical review of an XH-1 RISC-V CPU research
document.

The review itself has already been completed.

Your ONLY task is to determine whether the reviewer intended
PASS or FAIL and return valid JSON.

Return ONLY this JSON object:

{
  "verdict": "PASS",
  "confidence": "HIGH",
  "issues": [],
  "required_fixes": []
}

OR:

{
  "verdict": "FAIL",
  "confidence": "HIGH",
  "issues": ["..."],
  "required_fixes": ["..."]
}

Do not add Markdown.

Do not add explanation.

Do not use code fences.

Original review:

$original
EOF
)

    api_request \
        "$REVIEW_PROVIDER" \
        "$REVIEW_MODEL" \
        "You convert technical review output into strict JSON." \
        "$prompt" \
        "$repaired"
}

# ------------------------------------------------------------
# Atomic document replacement
# ------------------------------------------------------------

accept_document() {

    local file="$1"
    local candidate="$2"

    local temp="${file}.xh1.tmp"

    cp "$candidate" "$temp"

    mv "$temp" "$file"

    log "Accepted research document: $file"

    if [[ "$AUTO_COMMIT" == "true" ]]; then

        if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then

            git add "$file"

            git commit \
                -m "research: complete ${file#"$ROOT"/}" \
                || warn "Git commit failed."

        else
            warn "AUTO_COMMIT enabled but repository is not Git."
        fi
    fi
}

# ------------------------------------------------------------
# Run state
# ------------------------------------------------------------

start_run() {

    RUN_ID="$(timestamp)"

    mkdir -p "$STATE/runs/$RUN_ID"

    : > "$STATE/runs/$RUN_ID/completed"
    : > "$STATE/runs/$RUN_ID/failed"
    : > "$STATE/runs/$RUN_ID/attempts"

    log "Started run: $RUN_ID"
}

run_has_completed() {

    local file="$1"

    grep -Fxq "$file" \
        "$STATE/runs/$RUN_ID/completed" \
        2>/dev/null
}

run_has_failed() {

    local file="$1"

    grep -Fxq "$file" \
        "$STATE/runs/$RUN_ID/failed" \
        2>/dev/null
}

mark_completed() {

    local file="$1"

    printf '%s\n' "$file" \
        >> "$STATE/runs/$RUN_ID/completed"
}

mark_failed() {

    local file="$1"

    printf '%s\n' "$file" \
        >> "$STATE/runs/$RUN_ID/failed"
}

record_attempt() {

    local file="$1"
    local round="$2"
    local phase="$3"
    local result="$4"

    printf '%s\t%s\t%s\t%s\t%s\n' \
        "$(now)" \
        "$file" \
        "$round" \
        "$phase" \
        "$result" \
        >> "$STATE/runs/$RUN_ID/attempts"
}

# ------------------------------------------------------------
# Single research topic
# ------------------------------------------------------------

run_single() {

    local requested="${1:-}"

    if [[ -n "$requested" ]]; then
        file="$requested"
    else
        file="$(find_next_task || true)"
    fi

    [[ -n "${file:-}" ]] ||
        die "No SOON research topics found."

    [[ -f "$file" ]] ||
        die "Research document not found: $file"

    if [[ -z "$RUN_ID" ]]; then
        start_run
    fi

    log "=================================================="
    log "Researching: $file"
    log "=================================================="

    local round
    local candidate
    local review
    local revision
    local verdict

    candidate="$STATE/runs/$RUN_ID/candidate.md"
    review="$STATE/runs/$RUN_ID/review.md"
    revision="$STATE/runs/$RUN_ID/revision.md"

    for ((round=1; round<=MAX_RESEARCH_ROUNDS; round++)); do

        log "Research round $round/$MAX_RESEARCH_ROUNDS"

        # ----------------------------------------------------
        # Initial research
        # ----------------------------------------------------

        if (( round == 1 )); then

            log "Running researcher."

            if ! api_request \
                "$RESEARCH_PROVIDER" \
                "$RESEARCH_MODEL" \
                "You are a senior CPU architecture research engineer." \
                "$(research_prompt "$file")" \
                "$candidate"
            then

                warn "Research API failed."
                warn "Topic will NOT be marked as failed."

                return 2
            fi

            record_attempt "$file" "$round" "research" "success"

        else

            log "Running revision agent."

            if ! api_request \
                "$REVISION_PROVIDER" \
                "$REVISION_MODEL" \
                "You are a senior CPU architecture revision engineer." \
                "$(revision_prompt "$file" "$candidate" "$review")" \
                "$revision"
            then

                warn "Revision API failed."
                warn "Research round was NOT counted as a valid revision."

                return 2
            fi

            mv "$revision" "$candidate"

            record_attempt "$file" "$round" "revision" "success"
        fi

        # ----------------------------------------------------
        # Reviewer
        # ----------------------------------------------------

        if [[ "$REVIEW_ENABLED" != "true" ]]; then

            log "Review disabled."

            accept_document "$file" "$candidate"

            mark_completed "$file"

            return 0
        fi

        log "Running reviewer."

        if ! api_request \
            "$REVIEW_PROVIDER" \
            "$REVIEW_MODEL" \
            "You are an independent and highly skeptical CPU architecture reviewer." \
            "$(review_prompt "$file" "$(cat "$candidate")")" \
            "$review"
        then

            warn "Reviewer API failed."

            # IMPORTANT:
            #
            # Do not mark the research as failed.
            # Do not trigger revision.
            #
            # Return a special status so run-all can retry
            # the topic later.

            record_attempt "$file" "$round" "review" "api-error"

            return 2
        fi

        verdict="$(parse_verdict "$review")"

        if [[ "$verdict" == "INVALID" ]]; then

            log "Reviewer did not provide a machine-readable verdict."
            log "Requesting verdict-format repair."

            local repaired_review
            repaired_review="$STATE/runs/$RUN_ID/review-repaired.md"

            if repair_review_verdict \
                "$(cat "$review")" \
                "$repaired_review"
            then

                cat "$repaired_review" >> "$review"

                verdict="$(parse_verdict "$repaired_review")"

                if [[ "$verdict" != "INVALID" ]]; then
                    log "Recovered reviewer verdict: $verdict"
                fi

            fi
        fi

        echo
        echo "=========================================="
        echo "RESEARCH REVIEW"
        echo "=========================================="

        cat "$review"

        echo "=========================================="
        echo

        case "$verdict" in

            PASS)

                log "Research passed review."

                record_attempt "$file" "$round" "review" "PASS"

                accept_document "$file" "$candidate"

                mark_completed "$file"

                return 0
                ;;

            FAIL)

                log "Research rejected by reviewer."

                record_attempt "$file" "$round" "review" "FAIL"

                if (( round < MAX_RESEARCH_ROUNDS )); then

                    log "Preparing revision round $((round + 1))."

                    sleep "$DELAY_SECONDS"

                else

                    log "Maximum research rounds reached."
                    log "Leaving original document unchanged."

                    mark_failed "$file"

                    return 1
                fi
                ;;

            INVALID)

                warn "Reviewer returned no valid VERDICT."

                record_attempt "$file" "$round" "review" "invalid"

                warn "This is a reviewer failure, not a research failure."

                return 2
                ;;

        esac

    done

    return 1
}

# ------------------------------------------------------------
# Run all
# ------------------------------------------------------------

run_all() {

    start_run

    local count=0
    local file
    local result

    while true; do

        if (( MAX_ITERATIONS > 0 &&
              count >= MAX_ITERATIONS )); then

            log "MAX_ITERATIONS reached: $MAX_ITERATIONS"

            break
        fi

        file="$(find_next_task || true)"

        if [[ -z "$file" ]]; then

            log "No remaining research topics."

            break
        fi

        count=$((count + 1))

        run_single "$file"
        result=$?

        case "$result" in

            0)
                log "Topic completed successfully."
                ;;

            1)
                log "Topic failed after maximum research rounds."
                ;;

            2)
                log "Topic paused because of an API/provider failure."
                log "It remains eligible for a future run."
                ;;

            *)
                warn "Unexpected result code: $result"
                ;;
        esac

        sleep "$DELAY_SECONDS"

    done

    log "=================================================="
    log "Run complete."
    log "Topics attempted: $count"
    log "Run ID: $RUN_ID"
    log "=================================================="
}

# ------------------------------------------------------------
# Status
# ------------------------------------------------------------

status() {

    local total=0
    local completed=0
    local remaining=0

    while IFS= read -r -d '' file; do

        [[ "$file" == "$STATE"/* ]] && continue

        total=$((total + 1))

        if is_soon "$file"; then
            remaining=$((remaining + 1))
        else
            completed=$((completed + 1))
        fi

    done < <(
        find "$ROOT" \
            -type f \
            -name '*.md' \
            -not -path "$STATE/*" \
            -print0
    )

    echo
    echo "╔══════════════════════════════════════════╗"
    echo "║          XH-1 RESEARCH STATUS            ║"
    echo "╚══════════════════════════════════════════╝"
    echo
    echo "  Total documents : $total"
    echo "  Completed       : $completed"
    echo "  Remaining SOON  : $remaining"
    echo

    echo "Next topic:"

    local next
    next="$(find_next_task || true)"

    if [[ -n "$next" ]]; then
        echo "  $next"
    else
        echo "  None"
    fi

    echo
}

# ------------------------------------------------------------
# Reset failure
# ------------------------------------------------------------

reset_failure() {

    local file="${1:-}"

    [[ -n "$file" ]] ||
        die "Usage: ./xh1-research reset-failure <file>"

    [[ -f "$file" ]] ||
        die "File not found: $file"

    if [[ -n "$RUN_ID" &&
          -f "$STATE/runs/$RUN_ID/failed" ]]; then

        sed -i.bak "\|^${file}$|d" \
            "$STATE/runs/$RUN_ID/failed"

        rm -f "$STATE/runs/$RUN_ID/failed.bak"
    fi

    echo "Failure state reset for:"
    echo "  $file"
}

# ------------------------------------------------------------
# Manual review
# ------------------------------------------------------------

review_command() {

    local file="${1:-}"

    [[ -n "$file" ]] ||
        die "Usage: ./xh1-research review <file>"

    [[ -f "$file" ]] ||
        die "File not found: $file"

    RUN_ID="manual-$(timestamp)"

    mkdir -p "$STATE/runs/$RUN_ID"

    local review="$STATE/runs/$RUN_ID/review.md"

    log "Manual review: $file"

    if ! api_request \
        "$REVIEW_PROVIDER" \
        "$REVIEW_MODEL" \
        "You are an independent and highly skeptical CPU architecture reviewer." \
        "$(review_prompt "$file" "$(cat "$file")")" \
        "$review"
    then
        die "Reviewer API failed."
    fi

    echo
    echo "=========================================="
    echo "RESEARCH REVIEW"
    echo "=========================================="

    cat "$review"

    echo "=========================================="
}

# ------------------------------------------------------------
# Providers
# ------------------------------------------------------------

providers() {

    echo
    echo "XH-1 RESEARCH PROVIDERS"
    echo "======================="
    echo

    printf '%-15s %-45s %s\n' \
        "PROVIDER" "URL" "MODEL"

    printf '%-15s %-45s %s\n' \
        "openrouter" \
        "$OPENROUTER_URL" \
        "$RESEARCH_MODEL"

    printf '%-15s %-45s %s\n' \
        "ollama" \
        "$OLLAMA_URL" \
        "$REVIEW_MODEL"

    printf '%-15s %-45s %s\n' \
        "qwen" \
        "$QWEN_URL" \
        "$RESEARCH_MODEL"

    echo
    echo "Research:"
    echo "  $RESEARCH_PROVIDER / $RESEARCH_MODEL"

    echo
    echo "Review:"
    echo "  $REVIEW_PROVIDER / $REVIEW_MODEL"

    echo
    echo "Revision:"
    echo "  $REVISION_PROVIDER / $REVISION_MODEL"

    echo
}

# ------------------------------------------------------------
# Help
# ------------------------------------------------------------

help() {

    cat <<'EOF'

XH-1 RESEARCH HARNESS
=====================

Autonomous research/review/revision pipeline for XH-1.

USAGE

    ./xh1-research <command>


COMMANDS

    init

        Create the default configuration.


    status

        Show research progress and next topic.


    providers

        Show configured AI providers and models.


    next

        Show the next SOON document.


    run

        Research the next available topic.


    run <file>

        Research a specific document.


    run-all

        Process all available research topics.

        API/provider failures do NOT mark a topic failed.

        The topic remains eligible for a future run.


    loop

        Alias for run-all.


    resume

        Alias for run-all.


    review <file>

        Manually review a document.


    reset-failure <file>

        Allow a failed topic to be retried.


    help

        Show this help.


RESEARCH PIPELINE

    Research
       |
       v
    Review
       |
       +---- PASS ----> Replace SOON document
       |
       +---- FAIL ----> Revision
                           |
                           v
                         Review
                           |
                           +---- PASS
                           |
                           +---- FAIL
                           |
                           v
                     Maximum rounds


IMPORTANT SAFETY

A research document is ONLY replaced after:

    VERDICT: PASS

API failures are NOT research failures.

Examples:

    429
    timeout
    connection failure
    malformed response
    empty reviewer response
    invalid reviewer verdict

These pause the topic instead of consuming a research round.

If all research rounds genuinely fail:

    The original SOON document remains unchanged.

The topic is marked failed for that run.


PROVIDERS

Built-in:

    openrouter
    ollama

Other OpenAI-compatible providers can be configured with:

    PROVIDER_<name>_URL
    PROVIDER_<name>_KEY_ENV


ENVIRONMENT

XH1_RESEARCH_ROOT

    Default:
        research


XH1_RESEARCH_CONFIG

    Default:
        xh1-research.conf


CONFIGURATION

RESEARCH_PROVIDER

    Provider used for research.


RESEARCH_MODEL

    Model used for research.


REVIEW_PROVIDER

    Provider used for independent review.


REVIEW_MODEL

    Model used for independent review.


REVISION_PROVIDER

    Provider used for revision.


REVISION_MODEL

    Model used for revision.


OPENROUTER_URL

    Default:

        https://openrouter.ai/api/v1


OPENROUTER_KEY_ENV

    Default:

        OPENROUTER_API_KEY


OLLAMA_URL

    Default:

        http://127.0.0.1:11434/v1


OLLAMA_CONTEXT

    Ollama context size.

    Recommended for 16GB Apple Silicon:

        16384


MAX_RESEARCH_ROUNDS

    Maximum research/revision cycles per topic.

    Default:

        3


MAX_API_RETRIES

    API retry attempts.

    Default:

        4


DELAY_SECONDS

    Delay between topics/rounds.

    Default:

        5


MAX_ITERATIONS

    Maximum topics processed by run-all.

    0 = unlimited.


REVIEW_ENABLED

    true/false.


AUTO_COMMIT

    true/false.


EXAMPLE

    export OPENROUTER_API_KEY="..."

    ./xh1-research init

    ./xh1-research providers

    ./xh1-research status

    ./xh1-research run

    ./xh1-research run-all


RECOMMENDED XH-1 SETUP

    RESEARCH_PROVIDER="openrouter"
    RESEARCH_MODEL="minimax/minimax-m3:free"

    REVIEW_PROVIDER="ollama"
    REVIEW_MODEL="gemma4:12b"

    REVISION_PROVIDER="openrouter"
    REVISION_MODEL="minimax/minimax-m3:free"

    OLLAMA_CONTEXT=16384


EOF
}

# ------------------------------------------------------------
# Main
# ------------------------------------------------------------

load_config
require_dependencies

case "${1:-status}" in

    init)
        init_config
        ;;

    status)
        status
        ;;

    providers)
        providers
        ;;

    next)
        find_next_task || true
        ;;

    run)
        run_single "${2:-}"
        ;;

    run-all|loop|resume)
        run_all
        ;;

    review)
        review_command "${2:-}"
        ;;

    reset-failure)
        reset_failure "${2:-}"
        ;;

    help|-h|--help)
        help
        ;;

    *)
        die "Unknown command: $1. Use ./xh1-research help"
        ;;

esac
