///|
/// Dynamic runtime tool registration. Implements ToolProvider.
pub(all) struct ToolRegistry {
  mut tools : Map[String, @kernel.ToolDef]
  mut executors : Map[String, (@port.ToolCallContext) -> @kernel.ToolOutcome]
}

///|
pub fn ToolRegistry::ToolRegistry() -> ToolRegistry {
  { tools: Map::from_array([]), executors: Map::from_array([]), }
}

///|
/// Register (or deliberately replace) a tool at runtime. Re-registering an
/// existing name overwrites both the definition and the executor — that is
/// intentional hot-replacement semantics for dynamic registries. Callers
/// that did NOT mean to replace should use `register_strict`, which fails
/// fast on a name collision instead of silently shadowing the prior tool.
pub fn ToolRegistry::register(
  self : ToolRegistry,
  tool : @kernel.ToolDef,
  executor : (@port.ToolCallContext) -> @kernel.ToolOutcome,
) -> Unit {
  self.tools[tool.name.to_string()] = tool
  self.executors[tool.name.to_string()] = executor
}

///|
/// Register a tool, failing fast with `RuntimeError::ToolAlreadyRegistered`
/// when the name is already taken. This mirrors the composition-time rule:
/// a name collision is never silently resolved by last-wins.
pub fn ToolRegistry::register_strict(
  self : ToolRegistry,
  tool : @kernel.ToolDef,
  executor : (@port.ToolCallContext) -> @kernel.ToolOutcome,
) -> Unit raise @error.RuntimeError {
  let key = tool.name.to_string()
  if self.tools.contains(key) {
    raise ToolAlreadyRegistered("tool '\{key}' is already registered")
  }
  self.tools[key] = tool
  self.executors[key] = executor
}

///|
pub fn ToolRegistry::unregister(self : ToolRegistry, name : String) -> Unit {
  self.tools.remove(name)
  self.executors.remove(name)
}

///|
pub extend ToolRegistry with @port.ToolProvider::{execute, list_tools}

///|
pub impl @port.ToolProvider for ToolRegistry with fn list_tools(self) {
  let all : Array[@kernel.ToolDef] = []
  for v in self.tools.values() {
    all.push(v)
  }
  all
}

///|
pub impl @port.ToolProvider for ToolRegistry with fn execute(
  self,
  ctx : @port.ToolCallContext,
) -> @kernel.ToolOutcome raise @error.RuntimeError {
  let name = ctx.name.to_string()
  match self.executors.get(name) {
    Some(exec) => exec(ctx)
    None => raise UnknownTool("No executor registered for tool '\{name}'")
  }
}