import { typecheck, parseAST, testFile } from "std::agency"
import { question, todoWrite, todoList } from "std::agent"
import { edit } from "std::fs"
import {
  gitIsRepo,
  gitStatus,
  gitLog,
  gitDiff,
  gitShow,
  gitBranchList,
  gitRemoteList,
  gitBlame,
  gitStashList,
  gitAdd,
  gitCommit,
  gitCheckout,
  gitSwitch,
  gitBranchCreate,
  gitBranchDelete,
  gitStashPush,
  gitStashPop,
  gitRestore,
 } from "std::git"
import {
  ghPrGet,
  ghPrList,
  ghPrDiff,
  ghPrFiles,
  ghPrReviews,
  ghPrReviewComments,
  ghPrChecks,
  ghIssueGet,
  ghIssueList,
  ghIssueComments,
  ghIssueSearch,
  ghPrReviewComment,
  ghPrReview,
  ghPrApprove,
  ghIssueCreate,
  ghIssueComment,
  ghIssueClose,
  ghIssueLabel,
 } from "std::github"
import { fetchMarkdown } from "std::http"
import { remember, recall } from "std::memory"
import { safeBash } from "std::safeBash"
import { ls, glob, grep, exec } from "std::shell"
import { readSpill, grepSpill } from "std::spill"
import { docsEntries, docsToolFromEntries } from "std::skills"
import {
  search as wikipediaSearch,
  summary as wikipediaSummary,
  article as wikipediaArticle,
 } from "std::wikipedia"

/** @module
@summary Compose agents from named bundles of file, web, GitHub, and other tools.

Build an agent's tool list by combining the bundles it needs. Construct
lists inside a function when they include `searchTools`, so API-key
selection happens at call time. Bundle membership describes the tools
available; the caller's handlers still decide their interrupts.

```ts
import { readOnlyFileTools, communicationTools } from "std::agents/lib/toolkits"

node main() {
  const tools = [...readOnlyFileTools(), ...communicationTools()]
  const answer: string = llm("Summarize README.md", { tools: tools })
  print(answer)
}
```
*/

// Every bundle is a function rather than a constant, matching searchTools()
// in std::agents/lib/search, which must be a function because it reads API
// keys at call time.

// Bundled Agency docs. `static` caches each tool across calls. Each gets an
// explicit name: the name docsSkill derives from a directory embeds the
// absolute install path, which differs per machine and is truncated to fit.
// Each section is scanned once, and its full and brief tools are built from
// the same entries.
static const guideEntries = docsEntries("guide")
static const cliEntries = docsEntries("cli")
static const diagnosticsEntries = docsEntries("diagnostics")
static const stdlibEntries = docsEntries("stdlib")
static const agentEntries = docsEntries("agent")

static const docSkill = docsToolFromEntries("guide", guideEntries).rename("agencyGuide")
static const cliSkill = docsToolFromEntries("cli", cliEntries).rename("agencyCliDocs")
static const diagnosticsSkill = docsToolFromEntries("diagnostics", diagnosticsEntries).rename("agencyDiagnostics")
static const stdlibSkill = docsToolFromEntries("stdlib", stdlibEntries).rename("agencyStdlib")
static const agentSkill = docsToolFromEntries("agent", agentEntries).rename("agencyAgent")

static const briefDocSkill = docsToolFromEntries("guide", guideEntries, brief: true).rename("agencyGuide")
static const briefCliSkill = docsToolFromEntries("cli", cliEntries, brief: true).rename("agencyCliDocs")
static const briefDiagnosticsSkill = docsToolFromEntries("diagnostics", diagnosticsEntries, brief: true).rename("agencyDiagnostics")
static const briefStdlibSkill = docsToolFromEntries("stdlib", stdlibEntries, brief: true).rename("agencyStdlib")
static const briefAgentSkill = docsToolFromEntries("agent", agentEntries, brief: true).rename("agencyAgent")

export def whatIAmDoing(message: string): string {
  """
  Tell the user what you are doing. Use this tool often to update the user on what you're up to.
  """
  print(message)
  return "Delivered message to user."
}

/** `question` raises an interrupt the host or a handler answers with a value.
Use it in hosted as well as terminal agents. Approval policy rules cannot
supply a question's reply. */
export def communicationTools(): any[] {
  """
  Return progress-reporting and user-question tools.
  """
  return [whatIAmDoing, question]
}

/** The grep tool always respects `.gitignore`. The setting is bound so the
model cannot include ignored files by changing an argument. */
export def readOnlyFileTools(): any[] {
  """
  Return file inspection tools rooted in the agent's working directory.
  """
  return [
    read.partial(useAgentCwd: true),
    ls.partial(useAgentCwd: true),
    glob.partial(useAgentCwd: true),
    grep.partial(useAgentCwd: true, respectGitignore: true),
  ]
}

export def writableFileTools(): any[] {
  """
  Return file inspection, write, and edit tools.
  """
  return [
    ...readOnlyFileTools(),
    write.partial(useAgentCwd: true),
    edit.partial(useAgentCwd: true),
  ]
}

// safeBash rather than raw bash: it raises the narrowest effects that
// describe a command (a recognized write raises std::write, a git read raises
// its git effect), so a policy can pre-approve what raw bash never could.
// safeBash needs no useAgentCwd binding: its empty cwd already resolves to
// the agent working directory (resolveCwd in std::safeBash).
/** `safeBash` parses pipelines and raises effects for recognized operations.
`exec` runs one binary with arguments. Both use the agent's working directory.
The saved-output readers retrieve output too large to return inline. */
export def shellTools(): any[] {
  """
  Return command execution and saved-output readers for the agent's working directory.
  """
  return [safeBash, exec.partial(useAgentCwd: true), readSpill, grepSpill]
}

/** Add these when an agent enables `spillToolResultsOver`, so it can retrieve
long tool results. `shellTools` already includes them. */
export def spillTools(): any[] {
  """
  Return tools for reading and searching saved tool output.
  """
  return [readSpill, grepSpill]
}

/** Use `gitIsRepo` to detect a repository from a package subdirectory.
Git finds the repository root even when `.git` is in a parent directory. */
export def readOnlyGitTools(): any[] {
  """
  Return Git repository inspection tools.
  """
  return [
    gitIsRepo,
    gitStatus,
    gitLog,
    gitDiff,
    gitShow,
    gitBranchList,
    gitRemoteList,
    gitBlame,
    gitStashList,
  ]
}

/** Repository modifications raise approval interrupts. The inspection tools
are also available separately from `readOnlyGitTools`. */
export def gitTools(): any[] {
  """
  Return Git repository inspection and modification tools.
  """
  return [
    ...readOnlyGitTools(),
    gitAdd,
    gitCommit,
    gitCheckout,
    gitSwitch,
    gitBranchCreate,
    gitBranchDelete,
    gitStashPush,
    gitStashPop,
    gitRestore,
  ]
}

/** The tools resolve the repository from the origin remote under the agent's
working directory. They read remote data and cannot post reviews or comments. */
export def githubReadTools(): any[] {
  """
  Return tools for reading GitHub pull requests, issues, and their comments.
  """
  return [
    ghPrGet,
    ghPrList,
    ghPrDiff,
    ghPrFiles,
    ghPrReviews,
    ghPrReviewComments,
    ghPrChecks,
    ghIssueGet,
    ghIssueList,
    ghIssueComments,
    ghIssueSearch,
  ]
}

/** Each remote write raises an approval interrupt. Pull-request approval
has its own effect, so policies can permit comments while refusing approvals. */
export def githubTools(): any[] {
  """
  Return GitHub lookup, review, comment, and issue-management tools.
  """
  return [
    ...githubReadTools(),
    ghPrReviewComment,
    ghPrReview,
    ghPrApprove,
    ghIssueCreate,
    ghIssueComment,
    ghIssueClose,
    ghIssueLabel,
  ]
}

// One fetch, not three. fetchMarkdown returns an HTML page as text the model
// can read and any other body (JSON included) as it came, so it covers what
// fetch and fetchJSON did. Three fetch tools meant three effects for a policy
// to cover: in trace wX5GvrUq the user's policy approved two of them, the
// model used the third, and 26 of 57 minutes went to approval prompts
// (ADI-44).
/** Add search tools when an agent must discover sources before fetching them.
The HTTP tool returns HTML as Markdown and other bodies as received.
Its `refresh` argument is bound to false to reuse a page within a conversation. */
export def webTools(): any[] {
  """
  Return HTTP and Wikipedia lookup tools for named sources.
  """
  return [
    fetchMarkdown.partial(refresh: false),
    wikipediaSearch.rename("wikipedia_search"),
    wikipediaSummary.rename("wikipedia_summary"),
    wikipediaArticle.rename("wikipedia_article"),
  ]
}

/** The bundles cover the guide, CLI, diagnostics, stdlib, and agent docs.
Reads through these tools still raise `std::read`. */
export def agencyDocTools(): any[] {
  """
  Return the five packaged Agency documentation tools.
  """
  return [docSkill, cliSkill, diagnosticsSkill, stdlibSkill, agentSkill]
}

/** Use compact listings when the prompt already teaches the language.
Offer this bundle or `agencyDocTools`: both use the same tool names. */
export def agencyDocToolsBrief(): any[] {
  """
  Return packaged documentation tools with compact page listings.
  """
  return [
    briefDocSkill,
    briefCliSkill,
    briefDiagnosticsSkill,
    briefStdlibSkill,
    briefAgentSkill,
  ]
}

/** `testFile` runs a saved `.test.json` harness in a subprocess.
Use parse and typecheck for source text that has not been saved. */
export def agencyCodeTools(): any[] {
  """
  Return Agency parsing, typechecking, and test-harness tools.
  """
  return [typecheck, parseAST, testFile]
}

export def memoryTools(): any[] {
  """
  Return fact storage and retrieval tools for enabled memory.
  """
  return [remember, recall]
}

// saveDraft is deliberately not here. Every worker offers saveDraft itself at
// its own llm call, so a caller can hand this bundle to an agent as extra tools
// without landing saveDraft in the list twice, which the provider rejects.
export def planningTools(): any[] {
  """
  Return tools for replacing and reading the agent's todo list.
  """
  return [todoWrite, todoList]
}

// A model-facing instruction, spliced into an agent's prompt when the agent
// offers the `question` tool. Without it the tool sits unused: a model that
// has not been told to ask will guess instead.
export static const ASK_USER_HINT = "\n\nWhen the work turns on something only the user can tell you — which account they mean, which of two readings of the request, a value you cannot look up — ask with the `question` tool, and ask before you build on the guess rather than after. Do not ask for anything you could find yourself, and do not ask the user to narrow a scope they left broad on purpose. When nobody can answer, your question comes back rejected; when that happens, pick the most reasonable reading, say which one you picked, and carry on."

// A model-facing instruction, spliced into an agent's prompt when the agent
// offers the `saveDraft` tool. Tells the model to checkpoint its best answer
// so a run cut short by a budget guard still returns something useful.
export static const SAVE_DRAFT_HINT = "\n\nIf you might run low on time or budget, call `saveDraft` with your best answer so far as you work, and update it as you improve. If the run is cut short, the last draft you saved is what the user receives — a run that saved nothing returns nothing."
