///|
/// moon-agent chat — interactive AI assistant in the terminal.
///
/// Configuration:
///   Create `.env` at project root (see `.env.example`):
///     OPENAI_API_KEY=sk-...
///     OPENAI_BASE_URL=https://api.deepseek.com
///     OPENAI_MODEL=deepseek-chat
///
/// Usage:
///   moon run cmd/chat --target js
///     → interactive REPL mode (default)
///
///   $env:MOON_AGENT_INPUT = "What is 15 * 23 + 100?"
///   moon run cmd/chat --target js
///     → single-question mode (legacy)
///
///   $env:MOON_AGENT_LIST_TOOLS = "1"
///   moon run cmd/chat --target js
///     → list all registered tools
///
/// REPL commands:
///   /exit, /quit    — exit
///   /help           — show commands
///   /tools          — list registered tools
///   /clear          — clear conversation memory
///   /usage          — show accumulated token usage & cost
///   /trace          — show last turn's decision trail
///   /errors         — show structured error log
///
/// Built-in tools: calculator, get_current_time (2 in REPL)

///|
fn main {
  let config = @config.load_config_or_abort()

  // Legacy env-var modes (backward compatible)
  let list_tools = @env.get_env_var("MOON_AGENT_LIST_TOOLS")
  match list_tools {
    Some(_) => {
      list_all_tools()
      return
    }
    None => ()
  }
  let user_input = @env.get_env_var("MOON_AGENT_INPUT")
  match user_input {
    Some(input) => {
      println("=== moon-agent chat v" + @agent.version + " ===")
      println("Endpoint: " + config.base_url + " | Model: " + config.model)
      println("")
      let provider = build_provider(config)
      let registry = build_registry()
      chat_turn(
        provider,
        registry,
        @memory.BufferMemory::new(),
        @observability.UsageTracker::new(),
        @observability.CallTrace::new(),
        @observability.ErrorLogger::new(),
        input,
      )
      return
    }
    None => ()
  }

  // REPL mode (default)
  repl(config)
}

///|
fn build_provider(config : @config.ChatConfig) -> @llm.BoxedProvider {
  let provider = @openai.OpenAIProvider::new_compat(
    config.base_url,
    config.model,
    api_key=config.api_key,
    max_tokens=config.max_tokens,
    system_prompt=config.system_prompt,
  )
  @llm.BoxedProvider::new(provider)
}

///|
fn build_registry() -> @llm_tools.ToolRegistry {
  let registry = @llm_tools.ToolRegistry::new()
  @tools.register_into(registry, @tools.CalculatorTool::new())
  @tools.register_into(registry, @tools.DateTimeTool::new())
  registry
}

///|
fn list_all_tools() -> Unit {
  println("=== moon-agent built-in tools ===")
  println("  [calculator]       integer arithmetic (+, -, *, /)")
  println("  [get_current_time] current ISO-8601 timestamp")
  println("  [http_request]     HTTP GET/POST requests")
  println("  [read_file]        read local file contents")
  println("  [write_file]       write content to a file")
  println("  [shell_exec]       execute system commands (sandboxed)")
  println("")
  println("Middleware: ToolGuard (allowlist/denylist), RetryTool, with_retry")
  println("")
  println("Configure via .env (see .env.example) or config.json.")
}

///|
fn repl(config : @config.ChatConfig) -> Unit {
  println("=== moon-agent chat v" + @agent.version + " ===")
  println("Endpoint: " + config.base_url + " | Model: " + config.model)
  println("Type /help for commands, /exit to quit")
  println("")

  let provider = build_provider(config)
  let registry = build_registry()
  let memory = @memory.BufferMemory::new()
  let usage = @observability.UsageTracker::new()
  let error_logger = @observability.ErrorLogger::new()

  while true {
    print("You: ")
    let line = read_line()
    if line == "" {
      continue
    }
    let trimmed : String = line.trim().to_owned()
    if trimmed == "" {
      continue
    }

    // Commands
    if trimmed == "/exit" || trimmed == "/quit" {
      println("")
      println(usage.format())
      if error_logger.total_errors() > 0 {
        println(error_logger.format())
      }
      println("Goodbye!")
      break
    }
    if trimmed == "/help" {
      show_repl_help()
      continue
    }
    if trimmed == "/tools" {
      list_all_tools()
      continue
    }
    if trimmed == "/clear" {
      memory.clear()
      println("[Memory cleared]")
      continue
    }
    if trimmed == "/usage" {
      println(usage.format())
      continue
    }
    if trimmed == "/errors" {
      println(error_logger.format())
      continue
    }

    // Chat turn
    let trace = @observability.CallTrace::new()
    if trimmed == "/trace" {
      // /trace alone: show last trace (stored from previous turn)
      println("(no previous turn to trace)")
      continue
    }
    chat_turn(provider, registry, memory, usage, trace, error_logger, trimmed)
    println("")
  }
}

///|
fn show_repl_help() -> Unit {
  println("Commands:")
  println("  /exit, /quit    Exit the chat")
  println("  /help           Show this help")
  println("  /tools          List registered tools")
  println("  /clear          Clear conversation memory")
  println("  /usage          Show token usage & cost")
  println("  /errors         Show structured error log")
  println("")
  println("Anything else is sent to the AI agent.")
  println("Built-in tools: calculator, get_current_time")
}

///|
/// Run a single chat turn with memory persistence, event visibility,
/// and observability (token usage + call trace + error logging).
fn chat_turn(
  provider : @llm.BoxedProvider,
  registry : @llm_tools.ToolRegistry,
  memory : @memory.BufferMemory,
  usage : @observability.UsageTracker,
  trace : @observability.CallTrace,
  error_logger : @observability.ErrorLogger,
  question : String,
) -> Unit {
  // Build messages from memory (preserves conversation history)
  let messages = memory.load_messages()
  let user_msg = @llm.Message::user(question)
  messages.push(user_msg)

  println("Agent: ")
  // Use cell arrays for closure-captured mutable state
  let has_output : Array[Bool] = [false]
  let response_text : Array[String] = [""]

  // Run agent loop with observability hooks
  @llm.run_agent(
    provider,
    registry,
    messages,
    @llm.StopCondition::MaxSteps(8),
    fn(event : @llm.AgentEvent) -> Unit {
      usage.record(event)
      trace.record(event)
      match event {
        @llm.AgentEvent::Stream(e) =>
          match e {
            @llm.StreamEvent::TextDelta(s) => {
              print(s)
              has_output[0] = true
              response_text[0] = response_text[0] + s
            }
            @llm.StreamEvent::Error(msg) => {
              println("\n  [API Error] " + msg)
              error_logger.log_api_error(msg, "Agent stream error")
            }
            _ => ()
          }
        @llm.AgentEvent::ToolExecute(name~, input~) =>
          println(
            "\n  [→ calling tool: " + name + "] input: " + input.stringify(),
          )
        @llm.AgentEvent::ToolResult(name~, result~, is_error~) => {
          let preview = if result.length() > 100 {
            result[0:100].to_owned() + "..."
          } else {
            result
          }
          let tag = if is_error { " (ERROR)" } else { "" }
          println("  [← " + name + " result" + tag + "] " + preview)
          if is_error {
            error_logger.log_tool_error(name, result)
          }
        }
        _ => ()
      }
    },
  )

  // Flush final newline if there was output
  if has_output[0] {
    println("")
  }

  // Save to memory: user message + assistant response
  memory.save_message(user_msg)
  if response_text[0] != "" {
    memory.save_message(@llm.Message::assistant(response_text[0]))
  }

  // Show token stats
  if usage.total_tokens() > 0 {
    println(usage.format())
  }
}