/** @module
  @summary Verifies completed work on disk against a task: derives measurable
  criteria and computes each one with tools.

  The verifier runs; review reads. To judge a work product handed to you as
  text without touching the file system, use reviewAgent instead.
*/
import { Feedback, failOpenFeedback } from "std::agents/lib/feedback"
import { llmOptions } from "std::agents/lib/shared"
import { readOnlyFileTools, readOnlyGitTools, shellTools, SAVE_DRAFT_HINT } from "std::agents/lib/toolkits"
import { concurrencyPool } from "std::concurrency"
import { agentSkill } from "std::skills"
import { systemMessage, threadIsNew } from "std::thread"

import { communicationTools } from "./lib/toolkits.agency"

static const POOL_SIZE = 4

static const skills = agentSkill("verifier")

export def buildTools(): any[] {
  """
  Return the verifier's tools. It measures with shell pipelines and reads
  bytes, because a criterion checked by eye is a criterion not checked. It
  has no write tools: a verifier that can fix things is no longer a verifier.
  """
  return [
    ...readOnlyFileTools(),
    ...readOnlyGitTools(),
    ...shellTools(),
    ...communicationTools(),
    readBinary.partial(useAgentCwd: true),
    skills,
  ]
}

static const systemPrompt = """You verify whether a coding task was completed correctly. You do NOT fix anything. Inspect, COMPUTE, and report.

The grading tests are hidden and NOT in this environment, so you must reconstruct and ACTUALLY RUN every check the task states — never eyeball the artifact.

Method:
1. Enumerate EVERY explicit success criterion in the task. Two kinds: the output contract (exact file path and name, format, JSON keys, field types, units, signed-vs-unsigned, the stated whitespace / trailing-newline requirement) AND every quantitative rule (lengths, ranges, counts, thresholds, temperatures, GC content, percentages, etc.).
2. For EACH criterion, use the `exec` tool to COMPUTE the artifact's actual value and compare it to the requirement. If the task names a specific tool or command to compute a value (e.g. a CLI with exact flags), install if needed and run THAT tool — it is the ground truth; do not approximate it with your own estimate.
3. A criterion you did NOT computationally verify is itself a gap — report it as one. A near-miss on any numeric constraint is an error: report the actual computed value vs. the requirement (e.g. "Tm difference 6.5°C, limit 5°C").

Never say the artifact "appears" or "should" satisfy something. Run it, measure it, and report a concrete pass/fail per criterion with the numbers.${SAVE_DRAFT_HINT}"""

// The judgment call, split out so its declared `Feedback[]` return type
// flows into `llm` and a model saveDraft propagates as typed partial
// findings when an outer guard trips.
def judgeCriteria(
  prompt: string,
  model: string,
  provider: string,
  extraTools: any[],
): Feedback[] {
  return llm(
    prompt,
    llmOptions(
    model: model,
    provider: provider,
    tools: [...buildTools(), ...extraTools, saveDraft],
  ),
  )
}

export def verifierAgent(
  task: string,
  criteria: string[] = [],
  context: string = "",
  maxCost: number = $20.00,
  maxTime: number = 15m,
  model: string = "",
  provider: string = "",
  session: string = "",
  extraTools: any[] = [],
): Result<Feedback[]> {
  """
  Verify work on disk against a task: derive measurable success criteria,
  compute each one with tools, and return one pass/fail finding per
  criterion.

  @param task - The task the work must satisfy
  @param criteria - Known acceptance criteria to check, extended with derived ones
  @param context - Extra material for the judgment, or ""
  @param maxCost - Hard spend cap
  @param maxTime - Hard wall-clock cap
  @param model - Model override, or "" for the ambient model
  @param provider - Provider for the model override
  @param session - Session name to share a thread across calls, or "" for isolated
  @param extraTools - Extra tools to offer the LLM, appended to the built-in set
  """
  const captured = guard(cost: maxCost, time: maxTime, label: "verifierAgent") {
    let results: Feedback[][] = []
    thread(label: "verifierAgent", session: session) {
      if (threadIsNew()) {
        systemMessage(systemPrompt)
      }
      const criteriaString = criteriaAsString(criteria)
      /*       const authoritativeCriteria = """
      Here is some example criteria that has already been provided for this task.
      Use it as a reference, but do not assume it is complete. Don't duplicate any of these,
      come up with new criteria.
      <criteria>
      ${criteriaString}
      </criteria>
    """
      const criteriaBlock = if criteria.length == 0 then "" else authoritativeCriteria
 */
      const generateCriteriaPrompt = """
    Given this task, come up with measurable criteria for success.
    <task>${task}</task>
    """

      const generatedCriteria: string[] = llm(
        generateCriteriaPrompt,
        llmOptions(model: model, provider: provider, tools: extraTools),
      )

      const allCriteria = [...criteria, ...generatedCriteria]

      const prompt = """
      Given this task:
      <task>${task}</task>

      Judge it against this criterion:
      <criterion>${criteriaAsString(allCriteria)}</criterion>

      Verify, don't assume. Use your tools to see what code has been produced, and compare it to the criterion.
      Report a concrete pass/fail. If the criterion involves a number, compute that number.
      A criterion you did not actually compute counts as a failure.
      """

      // A typed helper in return position so a saveDraft the model files
      // mid-verification propagates out to the guard as Feedback[]. Inlining
      // `return llm(...)` here would infer string (no declared return type to
      // thread), and `const items = llm(...)` would drop the partial.
      return judgeCriteria(
        prompt: prompt,
        model: model,
        provider: provider,
        extraTools: extraTools,
      )
    }
  }

  return failOpenFeedback(captured)
}

def criteriaAsString(criteria: string[]): string {
  if (criteria.length == 0) {
    return ""
  }
  return map(criteria, \criterion -> "- ${criterion}").join("\n")
}
