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

/** @module
@summary Review TypeScript readability, architecture, and project conventions.

Pass a diff or source text and optional project guidelines. The reviewer
can read repository files and Git history to check duplication and
conventions. It assumes compilers, linters, and tests run separately.

A stopped review may return saved partial findings. A failed review
returns no findings, so an empty list alone does not prove a completed review.
*/

static const systemPrompt = """You review TypeScript code for readability and architecture.
Correctness is not your lane: compilers, linters, formatters, and tests
run separately, so never report type errors, formatting, or anything a
mechanical tool catches. Your job is the judgment layer — code that works
but should not merge as written.

The anti-pattern catalog. Every finding names the pattern it violates:

- Duplicating existing code: reimplementing something the codebase
  already has. This is the most valuable finding you can make and the
  easiest to get wrong: SEARCH the repository for an existing helper
  before writing it, and cite the file you found. No citation, no finding.
- Imperative sprawl: the "what" and the "how" tangled together — loops,
  conditionals, and accumulators inline where a small declarative
  interface should hide them. Propose the interface, not just the
  complaint.
- Order-dependent mutable state: let-variables that must be assigned in
  a specific sequence to be correct. Each value should be a const derived
  from its inputs.
- Leaky abstraction: callers reading internal fields or reconstructing
  internal state instead of going through a boundary that hides it.
- Wrong altitude: functionality pushed into a file that is already a grab
  bag, or one file mixing two concepts, when a focused module should own
  it. Cite how big the file already is or what two concepts collide.
- Inconsistent with convention: the same operation the codebase already
  does one way, done a new way. Cite the convention you found.
- Useless special case: a branch whose body the general path already
  handles correctly.
- Swallowed errors: a catch that neither logs nor rethrows.
- Comments that narrate: restating what the next line does, or talking to
  a reviewer about the change ("now we correctly…"). Comments earn their
  place only by stating what the code cannot say.
- Density and naming: nested ternaries, one-line if bodies, several
  chained operations crammed into one line, magic numbers without a named
  constant, single-character names outside a trivial lambda, deeply
  nested object types that should be named types.

How to review:
1. Work out what the change is trying to do; judge structure against that
   intent, not against requirements you invent.
2. Explore before judging. Use the repository tools to read the files the
   change touches in full, find the conventions it should follow, and
   check whether a helper it hand-rolls already exists. A duplication or
   convention finding without a cited file is not a finding.
3. error=true means the change should not merge as written: name the
   pattern, the exact place, and the concrete fix. error=false is
   advisory — a real improvement that could also wait.
4. When the caller supplies project guidelines, they outrank this
   catalog: apply both, and where they conflict, the guidelines win.
5. A clean verdict is a fine outcome. Never pad a review with generic
   advice, and never restate the same finding at two places it repeats —
   report it once and say it repeats.

Keep each finding brief, concrete, and pointed at the construct it is
about.${ASK_USER_HINT}${SAVE_DRAFT_HINT}"""

static const skills = agentSkill("typescript/review")

export def buildTools(): any[] {
  """
  Return repository and web lookup tools for TypeScript reviews.
  """
  return [
    ...readOnlyFileTools(),
    ...readOnlyGitTools(),
    ...webTools(),
    ...spillTools(),
    ...searchTools(),
    skills,
    ...communicationTools(),
  ]
}

def judgeCode(
  work: string,
  task: string,
  guidelines: string,
  context: string,
  model: string,
  provider: string,
  extraTools: any[],
): Feedback[] {
  if (threadIsNew()) {
    systemMessage(systemPrompt)
  }
  let guidelinesSection = ""
  if (guidelines != "") {
    guidelinesSection = """This project's own guidelines. They outrank the built-in catalog:
<guidelines>
${guidelines}
</guidelines>

"""
  }
  const prompt = """
${guidelinesSection}The change was made for this task:
<task>
${withContext(task: task, context: context)}
</task>

<code>
${work}
</code>

Return a list of feedback items.
"""
  // Return position so a model saveDraft propagates as partial findings.
  return llm(
    prompt,
    llmOptions(
      model: model,
      provider: provider,
      tools: buildTools().concat(extraTools).concat([saveDraft]),
      spillToolResultsOver: AGENT_SPILL_OVER,
      hostedTools: hostedSearchTools(model, provider),
    ),
  )
}

export def typescriptReviewAgent(
  work: string,
  task: string = "",
  guidelines: string = "",
  context: string = "",
  maxCost: number = $10.00,
  maxTime: number = 10m,
  model: string = "",
  provider: string = "",
  session: string = "",
  extraTools: any[] = [],
): Result<Feedback[]> {
  """
  Review TypeScript readability, architecture, and project conventions.
  A failed review returns no findings.

  @param work - A diff, a file, or several files supplied as text.
  @param guidelines - Project standards that override conflicting built-in review rules.
  @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 repository and source lookup tools.
  """
  return failOpenFeedback(typescriptReviewUnderBudget(
    work: work,
    task: task,
    guidelines: guidelines,
    context: context,
    model: model,
    provider: provider,
    session: session,
    extraTools: extraTools,
    maxCost: maxCost,
    maxTime: maxTime,
  ))
}

// The guarded half of typescriptReviewAgent, a def of its own because the
// exported def post-processes the guard's Result. Return position: when
// the budget runs out, the findings the model saved reach the guard and
// come back as the result.
@guarded(cost: maxCost, time: maxTime, label: "typescriptReviewAgent")
def typescriptReviewUnderBudget(
  work: string,
  task: string,
  guidelines: string,
  context: string,
  model: string,
  provider: string,
  session: string,
  extraTools: any[],
  maxCost: number,
  maxTime: number,
): Result<Feedback[]> {
  thread(label: "typescriptReviewAgent", session: session) {
    return judgeCode(
      work: work,
      task: task,
      guidelines: guidelines,
      context: context,
      model: model,
      provider: provider,
      extraTools: extraTools,
    )
  }
}

/** What an eval hands the reviewer: the task the code was written for, and
  the file holding that code, seeded into the working directory by the
  test's `files/`. The shape the `evals/typescript-review` suite uses as
  its input. */
export type TsReviewEvalInput = {
  assignment: string;
  sourceFile: string
}

// Any other reviewer scored on the same suite supplies a node with this
// signature. Effects are decided at this boundary. Reading the seeded input
// file is this node's own decision (`with approve`). The reviewer runs under
// `docsOnlyHandler`, which lets it read the packaged docs and rejects every
// other interrupt, so a budget trip stays a failure and the reviewer's
// fail-open result comes back instead of an interrupt escaping the entry
// point.
/** Run this module through an eval suite with `agency eval run stdlib/agents/typescript/review.agency:evalMain --suite <dir>`. */
node evalMain(input: TsReviewEvalInput): Feedback[] {
  evalValue(input)
  const source = read(input.sourceFile) with approve
  if (source is failure(readErr)) {
    return [{ error: true, feedback: "could not read ${input.sourceFile}: ${readErr}" }]
  }
  let reviewed: Result<Feedback[]> = failure("the reviewer never ran")
  handle {
    reviewed = typescriptReviewAgent(work: source.value, task: input.assignment)
  } with docsOnlyHandler
  if (reviewed is failure(err)) {
    return [{ error: true, feedback: "review agent failed: ${err}" }]
  }
  return reviewed.value
}
