import { Feedback, failOpenFeedback } from "std::agents/lib/feedback"
import { hostedSearchTools, searchTools } from "std::agents/lib/search"
import { AGENT_SPILL_OVER, llmOptions, withContext } from "std::agents/lib/shared"
import { webTools, spillTools, githubReadTools, SAVE_DRAFT_HINT, ASK_USER_HINT } from "std::agents/lib/toolkits"
import { guarded } from "std::decorators"
import { agentSkill } from "std::skills"
import { systemMessage, threadIsNew } from "std::thread"

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

/** @module
@summary Review a work product against a task and return findings.

Pass the work as text. The agent can consult web sources and read GitHub
pull requests and issues. Use `verifierAgent` to run checks on work on disk,
or `agencyReviewAgent` to include Agency parse and typecheck findings.

By default, a review that could not run returns no findings, and a review
the time or cost budget cut short returns the findings it saved. Pass
`failOpen: false` when either outcome must be reported as a failure. The
saved findings are then in the failure's data as `draft`.
*/

static const systemPrompt = """You review a work product against a task. For each finding, return a Feedback item: error=true when the work fails the task on that point (name the specific problem), error=false for a satisfied point. When you checked a point against a page, put that page's URL in `source`, so the fix can cite it. Be brief and concrete. Judge only against the task and context given; do not invent requirements.

Check the work point by point, most important points first. After each point, call `saveDraft` with all the Feedback items you have so far. Reviews run under a time budget: if it runs out mid-review, the findings you have saved are all the caller receives.

## Communicating with the user
- Make sure the user is following what you're doing. Use the `whatIAmDoing` tool frequently to tell the user what you're doing.${ASK_USER_HINT}${SAVE_DRAFT_HINT}
"""

static const skills = agentSkill("review")

// The reviewer reads and checks; it has no tools that change anything. The
// GitHub tools are the read bundle only, so it can read a pull request it is
// asked to review and cannot post to it.
export def buildTools(): any[] {
  """
  Return the reviewer's web and GitHub lookup tools.
  """
  return [...webTools(), ...spillTools(), ...githubReadTools(), ...searchTools(), skills, ...communicationTools()]
}

def judgeWork(
  work: string,
  task: string,
  context: string,
  model: string,
  provider: string,
  extraTools: any[],
): Feedback[] {
  if (threadIsNew()) {
    systemMessage(systemPrompt)
  }
  const prompt = """
Task:
${withContext(task: task, context: context)}

Work product:
${work}
"""
  // Return position (not `const items = llm(...)`) so a saveDraft the model
  // files mid-review propagates out of this function; an assignment here
  // would drop it before the guard could salvage it.
  return llm(
    prompt,
    llmOptions(
    model: model,
    provider: provider,
    tools: [...buildTools(), ...extraTools, saveDraft],
    spillToolResultsOver: AGENT_SPILL_OVER,
    hostedTools: hostedSearchTools(model, provider),
  ),
  )
}

export def reviewAgent(
  work: string,
  task: string = "",
  context: string = "",
  maxCost: number = $10.00,
  maxTime: number = 10m,
  model: string = "",
  provider: string = "",
  session: string = "",
  extraTools: any[] = [],
  failOpen: boolean = true,
): Result<Feedback[]> {
  """
  Review a work product against a task and return satisfied points and problems.

  @param work - The work product as text.
  @param maxCost - Cost budget in USD.
  @param maxTime - Time budget in milliseconds.
  @param model - Model override, or "" for the ambient model.
  @param provider - Provider for the model override, ignored when model is empty.
  @param session - Name a conversation to reuse across calls, or "" for an isolated thread.
  @param extraTools - Tools added to the built-in lookup tools.
  @param failOpen - False makes failed or partial reviews return failures instead of empty or partial findings.
  """
  const reviewed = reviewUnderBudget(
    work: work,
    task: task,
    context: context,
    model: model,
    provider: provider,
    session: session,
    extraTools: extraTools,
    maxCost: maxCost,
    maxTime: maxTime,
    keepDraft: failOpen,
  )
  if (failOpen) {
    return failOpenFeedback(reviewed)
  }
  return reviewed
}

// The guarded half of reviewAgent. It is its own def because reviewAgent
// post-processes the guard's Result, and the decorator's return is the
// def's return. Return position: when the budget runs out, the findings
// the model saved reach the guard and come back as the result, unless
// keepDraft is false. Saved findings cover only the points checked so
// far, so a strict caller gets a failure instead.
@guarded(cost: maxCost, time: maxTime, label: "reviewAgent", keepDraft: keepDraft)
def reviewUnderBudget(
  work: string,
  task: string,
  context: string,
  model: string,
  provider: string,
  session: string,
  extraTools: any[],
  maxCost: number,
  maxTime: number,
  keepDraft: boolean,
): Result<Feedback[]> {
  thread(label: "reviewAgent", session: session) {
    return judgeWork(
      work: work,
      task: task,
      context: context,
      model: model,
      provider: provider,
      extraTools: extraTools,
    )
  }
}
