src/gleeps/dev.gleam

import argv
import clip
import clip/arg
import clip/help
import clip/opt
import envoy
import filepath
import gleam/bool
import gleam/dict
import gleam/function
import gleam/http
import gleam/http/request
import gleam/httpc
import gleam/io
import gleam/json
import gleam/list
import gleam/result
import gleam/string
import simplifile
import youid/uuid

type RawArgs {
  RawPushArgs(path: String, url: String)
}

type Push {
  Push(path: String, url: String, user_id: uuid.Uuid, api_key: uuid.Uuid)
}

pub fn main() -> Nil {
  clip()
}

fn clip() -> Nil {
  let result =
    clip.subcommands([#("push", push_command())])
    |> clip.help(help.simple(
      "gleam run -m gleeps/dev",
      "Tools for developing a gleeps bot",
    ))
    |> clip.run(argv.load().arguments)

  case result {
    Error(e) -> io.println_error(e)
    Ok(RawPushArgs(..) as args) -> {
      use user_id <- ok_or_print(
        envoy.get("GLEEPS_USERID")
          |> result.try(uuid.from_base64),
        "The GLEEPS_USERID environment variable is missing",
      )

      use api_key <- ok_or_print(
        envoy.get("GLEEPS_APIKEY")
          |> result.try(uuid.from_base64),
        "The GLEEPS_APIKEY environment variable is missing",
      )

      case do_push(Push(path: args.path, url: args.url, api_key:, user_id:)) {
        Ok(_) -> Nil
        Error(message) -> io.println_error(message)
      }
    }
  }
}

fn push_command() -> clip.Command(RawArgs) {
  clip.command({
    use path <- clip.parameter
    use url <- clip.parameter

    RawPushArgs(path:, url:)
  })
  |> clip.arg(path_arg())
  |> clip.opt(url_opt())
  |> clip.help(help.simple("push", "Submit your code to the gleeps server"))
}

fn url_opt() -> opt.Opt(String) {
  opt.new("url")
  |> opt.default("http://localhost:3000")
  |> opt.help("The url of the server you want to push to")
}

fn path_arg() -> arg.Arg(String) {
  arg.new("path")
  |> arg.default("./src")
  |> arg.help("Path to your ./src directory")
}

fn ok_or_print(
  result: Result(a, Nil),
  error_to_print: String,
  continue: fn(a) -> Nil,
) -> Nil {
  case result {
    Ok(value) -> continue(value)
    Error(_) -> io.println_error(error_to_print)
  }
}

fn do_push(args: Push) {
  use files <- result.try(
    recursively_read_directory(args.path)
    |> result.map_error(fn(error) {
      "Failed to read directory: " <> simplifile.describe_error(error)
    }),
  )

  use code <- result.try(read_file_content(files))

  use req <- result.try(
    request.to(args.url)
    |> result.replace_error("The url you supplied is invalid"),
  )

  let resp =
    req
    |> request.set_method(http.Post)
    |> request.set_path("code/submit")
    |> request.set_header("user-id", args.user_id |> uuid.to_base64())
    |> request.set_header("api-key", args.api_key |> uuid.to_base64())
    |> request.set_body(modules_to_json_string(code))
    |> httpc.send()

  use resp <- result.try(
    resp
    |> result.map_error(fn(error) {
      "The request failed: " <> string.inspect(error)
    }),
  )

  { "Server responded:\n" <> resp.body }
  |> io.println()
  |> Ok
}

fn modules_to_json_string(code: dict.Dict(String, String)) -> String {
  json.object([
    #("modules", json.dict(code, function.identity, json.string)),
  ])
  |> json.to_string
}

fn read_file_content(
  files: List(String),
) -> Result(dict.Dict(String, String), String) {
  files
  |> list.map(fn(module) {
    use content <- result.try(
      module
      |> simplifile.read()
      |> result.map_error(fn(error) {
        "Failed to read file content '"
        <> module
        <> "' "
        <> simplifile.describe_error(error)
        <> " "
      }),
    )

    Ok(#(module, content))
  })
  |> result.all()
  |> result.map(dict.from_list)
}

fn recursively_read_directory(path: String) -> Result(List(String), _) {
  use is_dir <- result.try(simplifile.is_directory(path))

  use <- bool.guard(bool.negate(is_dir), Ok([path]))

  use directory <- result.try(simplifile.read_directory(path))

  list.map(directory, fn(file) {
    path
    |> filepath.join(file)
    |> recursively_read_directory
  })
  |> result.all()
  |> result.map(list.flatten)
}