///|
priv struct Opts {
  queries : Array[String]
  api_key : String?
  base_url : String
  model : String
  max_results : Int
  max_uses : Int
  max_tokens : Int
  timeout_ms : Int
  read_stdin : Bool
}

///|
priv enum ParseResult {
  Parsed(Opts)
  PrintHelp
  UsageError(String)
}

///|
fn usage() -> String {
  let text =
    #|usage: moonx tonyfettes/skills-deepseek-web-search [options]  [...]
    #|
    #|Search the web through DeepSeek's native web search and print a bounded,
    #|citeable source list. The DeepSeek API key comes from the DEEPSEEK
    #|environment variable or --api-key.
    #|
    #|options:
    #|  --api-key      DeepSeek API key (default: $DEEPSEEK)
    #|  --base-url     Anthropic-compatible Messages base (default: DeepSeek /anthropic/v1)
    #|  --model       auxiliary search model (default: deepseek-v4-flash)
    #|  --max-results    cap on sources per query (default 8)
    #|  --max-uses       native web_search tool uses per query (default 5)
    #|  --max-tokens     generated-token cap for the search call (default 4096)
    #|  --timeout-ms     wall timeout for one search call (default 120000)
    #|  --stdin             additionally read one query per line from stdin
    #|  -h, --help          show this help
  text
}

///|
fn parse_int_arg(v : String) -> Int? {
  let n : Int = @string.from_str(v) catch { _ => return None }
  Some(n)
}

///|
fn is_flag_arg(s : String) -> Bool {
  s.has_prefix("-")
}

///|
fn looks_like_program_path(s : String) -> Bool {
  s.contains("/") ||
  s.contains("\\") ||
  s.contains(".exe") ||
  s.contains(".wasm")
}

///|
/// Some runners pass argv[0] (the program path) as the first argument, others
/// do not. Drop a leading non-option token that looks like a path so both
/// invocation styles work.
fn args_without_program(args : Array[String]) -> Array[String] {
  if args.is_empty() {
    return args
  }
  let first = args[0]
  if is_flag_arg(first) || !looks_like_program_path(first) {
    return args
  }
  let rest : Array[String] = []
  for i in 1.. ParseResult {
  let args = args_without_program(raw_args)
  let queries : Array[String] = []
  let mut api_key : String? = None
  let mut base_url = @search.DefaultBaseUrl
  let mut model = @search.DefaultSearchModel
  let mut max_results = @search.DefaultMaxResults
  let mut max_uses = @search.DefaultMaxUses
  let mut max_tokens = @search.DefaultMaxTokens
  let mut timeout_ms = @search.DefaultTimeoutMs
  let mut read_stdin = false
  let mut i = 0
  while i < args.length() {
    let arg = args[i]
    if arg == "-h" || arg == "--help" {
      return PrintHelp
    }
    if arg == "--stdin" {
      read_stdin = true
    } else if arg == "--api-key" ||
      arg == "--base-url" ||
      arg == "--model" ||
      arg == "--max-results" ||
      arg == "--max-uses" ||
      arg == "--max-tokens" ||
      arg == "--timeout-ms" {
      if i + 1 >= args.length() {
        return UsageError("missing value for \{arg}")
      }
      i = i + 1
      let value = args[i]
      match arg {
        "--api-key" => api_key = Some(value)
        "--base-url" => base_url = value
        "--model" => model = value
        "--max-results" =>
          match parse_int_arg(value) {
            Some(n) =>
              if n > 0 {
                max_results = n
              } else {
                return UsageError("--max-results must be positive")
              }
            None => return UsageError("invalid --max-results value: \{value}")
          }
        "--max-uses" =>
          match parse_int_arg(value) {
            Some(n) =>
              if n > 0 {
                max_uses = n
              } else {
                return UsageError("--max-uses must be positive")
              }
            None => return UsageError("invalid --max-uses value: \{value}")
          }
        "--max-tokens" =>
          match parse_int_arg(value) {
            Some(n) =>
              if n > 0 {
                max_tokens = n
              } else {
                return UsageError("--max-tokens must be positive")
              }
            None => return UsageError("invalid --max-tokens value: \{value}")
          }
        "--timeout-ms" =>
          match parse_int_arg(value) {
            Some(n) =>
              if n > 0 {
                timeout_ms = n
              } else {
                return UsageError("--timeout-ms must be positive")
              }
            None => return UsageError("invalid --timeout-ms value: \{value}")
          }
        _ => ()
      }
    } else if arg.has_prefix("--") {
      return UsageError("unknown option: \{arg}")
    } else {
      if arg.is_blank() {
        return UsageError("blank query")
      }
      if arg.length() > @search.MaxQueryChars {
        return UsageError(
          "query exceeds \{@search.MaxQueryChars} chars — ask one focused question",
        )
      }
      queries.push(arg)
    }
    i = i + 1
  }
  if queries.is_empty() && !read_stdin {
    return UsageError("no queries given")
  }
  Parsed({
    queries,
    api_key,
    base_url,
    model,
    max_results,
    max_uses,
    max_tokens,
    timeout_ms,
    read_stdin,
  })
}

///|
fn print_bounded(s : String, limit : Int) -> Unit {
  if s.length() > limit {
    println(s[:limit].to_owned() + "\n[...]")
  } else {
    println(s)
  }
}

///|
async fn run_one(opts : Opts, query : String, multiple : Bool) -> Bool {
  let key = match opts.api_key {
    Some(key) => key
    None =>
      match @env.get_env_var("DEEPSEEK") {
        Some(key) if key != "" => key
        _ => {
          println("error: no DeepSeek API key: set DEEPSEEK or pass --api-key")
          return true
        }
      }
  }
  let outcome = @search.perform_search(
    api_key=key,
    base_url=opts.base_url,
    model=opts.model,
    max_results=opts.max_results,
    max_uses=opts.max_uses,
    max_tokens=opts.max_tokens,
    timeout_ms=opts.timeout_ms,
    query,
  ) catch {
    @search.SearchFailure(message) => {
      println("error: \{message}")
      return true
    }
    _ => {
      println("error: web_search failed for: \{query}")
      return true
    }
  }
  if multiple {
    println("== \{query} ==")
  }
  print_bounded(@search.format_output(outcome), 6000)
  false
}

///|
async fn collect_queries(opts : Opts) -> Array[String] {
  if !opts.read_stdin {
    return opts.queries
  }
  let all = opts.queries
  let content = @stdio.stdin.read_all().text() catch { _ => "" }
  for line in content.split("\n") {
    let query = line.trim().to_owned()
    if !query.is_empty() && query.length() <= @search.MaxQueryChars {
      all.push(query)
    }
  }
  all
}

///|
async fn main {
  match parse_args(@env.args()) {
    PrintHelp => println(usage())
    UsageError(message) => {
      println("skills-deepseek-web-search: error: \{message}")
      println("")
      println(usage())
      @sys.exit(2)
    }
    Parsed(opts) => {
      let queries = collect_queries(opts)
      if queries.is_empty() {
        println("skills-deepseek-web-search: error: no non-blank queries given")
        println("")
        println(usage())
        @sys.exit(2)
      }
      let multiple = queries.length() > 1
      let mut failed = false
      for query in queries {
        if run_one(opts, query, multiple) {
          failed = true
        }
      }
      if failed {
        @sys.exit(1)
      }
    }
  }
}