// `_*` exports read ctx from the current run (see
// `lib/runtime/asyncContext.ts`); no `__ctx` arg is plumbed at the
// call site. The deprecated `__internal_*` exports remain in
// `lib/stdlib/memory.ts` for the migration window.
import {
  _setMemoryId,
  _getMemoryId,
  _shouldRunMemory,
  _buildExtractionPrompt,
  _applyExtractionResult,
  _buildForgetPrompt,
  _applyForgetResult,
  _recall,
  _enableMemory,
  _disableMemory,
  _pushMemoryFrame,
  _popMemoryFrame,
 } from "agency-lang/stdlib-lib/memory.js"

/** @module
@summary Store facts across runs and recall them for an agent.

Enable memory for a directory, then use `remember` to extract facts and
`recall` to retrieve them. Memory configuration and scope changes stay
local to the current execution branch. The files persist across runs.

```ts
import { enableMemory, setMemoryId, remember, recall } from "std::memory"

node main() {
  enableMemory({ dir: "./mem" }) with approve
  setMemoryId("alice")
  remember("Alice's favorite color is blue") with approve
  const facts = recall("Alice's favorite color") with approve
  print(facts)
}
```

`remember` and `forget` do nothing while memory is disabled. `recall`
still raises its approval request and then returns an empty string.
*/

// Agency types supply the structured-output schemas for extraction and forgetting.
type ExtractionResult = {
  entities: { name: string; type: string; observations: string[] }[];
  relations: { from: string; to: string; type: string }[];
  expirations: { entityName: string; observationContent: string }[]
}

type ForgetResult = {
  observations: { entityName: string; observationContent: string }[];
  relations: { fromName: string; toName: string; type: string }[]
}

/** Configure the storage directory and optional memory model, extraction,
compaction, and embedding settings. The same settings can be supplied in
the `memory` block of `agency.json`. Relative `dir` paths use the working
directory, and `~` expands to the home directory. */
export type MemoryConfig = {
  dir: string;
  model?: string;
  autoExtract?: { interval: number | undefined };
  compaction?: { trigger: "token" | "messages" | undefined; threshold: number | undefined };
  embeddings?: { model: string | undefined; provider: string | undefined }
}

effect std::memory::enableMemory { dir: string }
effect std::memory::disableMemory {}
effect std::memory::remember { contentLength: number }
effect std::memory::recall { query: string }
effect std::memory::forget { query: string }

/** Check this before offering memory tools when memory may be disabled. */
export def isMemoryActive(): boolean {
  """
  Return whether memory is enabled for the current execution branch.
  """
  return _shouldRunMemory()
}

/** A branch inherits its memory scope when it forks. Changing the scope
inside a branch leaves the parent's scope unchanged. Switching storage
directories keeps the scope, so set it again when you want a different one. */
export def setMemoryId(id: string) {
  """
  Choose the user, conversation, or workspace scope for memory reads and writes.
  Enable memory before setting the scope.

  @param id - Nonempty scope using letters, digits, dots, underscores, or hyphens, excluding `.` and `..`.
  """
  _setMemoryId(id)
}

export def getMemoryId(): string {
  """
  Return the memory scope, or "default" when no scope is set or memory is disabled.
  """
  return _getMemoryId()
}

/** Runs on the same host that use the same absolute directory share a store.
Enabling the active directory again leaves its configuration unchanged.
A different directory becomes the active configuration until it is popped
by `disableMemory`. Prefer the `memory` block form for temporary changes.
The storage directory is created when the store first writes data. */
@capabilities(fileRead, fileWrite)
export def enableMemory(config: MemoryConfig) raises <std::memory::enableMemory> {
  """
  Enable persistent memory for the current execution branch.
  Enabling the active directory again leaves its configuration unchanged.

  @param config - Supply a nonempty `dir` for the memory store.
  """
  return interrupt std::memory::enableMemory("Are you sure you want to enable memory at this directory?", {
    dir: config.dir
  })

  _enableMemory(config)
}

/** This also removes a configuration loaded from `agency.json`.
Use the `memory` block form when the previous configuration must be restored. */
export def disableMemory() raises <std::memory::disableMemory> {
  """
  Remove the current branch's most recently enabled memory configuration.
  An earlier configuration becomes active again if one remains.
  """
  return interrupt std::memory::disableMemory("Are you sure you want to disable memory (pop the top memory frame)?", {})

  _disableMemory()
}

/** ```ts
const result = memory({ dir: "./temporary-memory" }) as {
  remember("Alice's favorite color is blue") with approve
}
```

The block returns a `Result` and restores the prior configuration after
normal completion or failure. This helper does not raise the enable-memory
approval, so the caller decides when to expose it. */
@capabilities(fileRead, fileWrite)
export def memory(config: MemoryConfig, block: () -> any = null): Result {
  """
  Run a block with a temporary memory configuration, then restore the previous one.

  @param config - Supply a nonempty `dir` for the memory store.
  @param block - Required block whose return value becomes the successful result.
  """
  const pushed = _pushMemoryFrame(config)
  const result = try block()
  if (pushed) {
    _popMemoryFrame()
  }
  return result
}

@capabilities(fileRead, fileWrite, llm)
export def remember(content: string) raises <std::memory::remember> {
  """
  Extract facts from text and persist them to memory.
  Does nothing while memory is disabled.
  """
  if (_shouldRunMemory()) {
    return interrupt std::memory::remember("Are you sure you want to extract and persist facts to memory?", {
      contentLength: content.length
    })

    destructive {
      thread {
        const prompt = _buildExtractionPrompt(content)
        // Bang validation: a malformed extraction is a failure Result, not
        // a mistyped value. Before this, a provider returning prose or an
        // empty string crashed the tool and memory silently stopped
        // persisting (issue #494).
        const result: ExtractionResult! = llm(prompt)
        if (result is success(extraction)) {
          _applyExtractionResult(extraction)
        } else {
          return result
        }
      }
    }
  }
}

@capabilities(fileRead, llm)
export def recall(query: string): string raises <std::memory::recall> {
  """
  Retrieve relevant stored facts as text for a question or topic.
  Return an empty string when memory is disabled or no facts match.
  """
  return interrupt std::memory::recall("Are you sure you want to recall memories based on this query?", {
    query: query
  })

  return _recall(query)
}

/** Forgetting expires matching observations and relations. The stored history
remains available for auditing. */
@capabilities(fileRead, fileWrite, llm)
export def forget(query: string) raises <std::memory::forget> {
  """
  Stop recalling facts that match a natural-language description.
  This preserves stored history and does nothing while memory is disabled.
  """
  if (_shouldRunMemory()) {
    return interrupt std::memory::forget("Are you sure you want to soft-delete facts matching this query from memory?", {
      query: query
    })

    thread {
      const prompt = _buildForgetPrompt(query)
      // Bang validation, same contract as remember: malformed output is
      // a failure Result the caller can see, never a crash (issue #494).
      const result: ForgetResult! = llm(prompt)
      if (result is success(forgetPlan)) {
        _applyForgetResult(forgetPlan)
      } else {
        return result
      }
    }
  }
}
