<commit_before>package core.generator

import core._
import Text._
import ScalaUtil._

object Play2ClientGenerator {
  def apply(json: String): String = {
    val sd = ServiceDescription(json)
    val ssd = new ScalaServiceDescription(sd)
    apply(ssd)
  }

  def apply(ssd: ScalaServiceDescription): String = {
s"""${Play2Models(ssd)}

${client(ssd)}"""
  }

  private[generator] def errorTypeClass(response: ScalaResponse): String = {
    require(!response.isSuccess)

    Seq(
      s"case class ${response.errorClassName}(response: play.api.libs.ws.Response) extends Exception {",
      "",
      s"  lazy val ${response.errorVariableName} = response.json.as[${response.resultType}]",
      "",
      "}"
    ).mkString("\n")
  }

  private def errors(ssd: ScalaServiceDescription): Option[String] = {
    val errorTypes = ssd.resources.flatMap(_.operations).flatMap(_.responses).filter(!_.isSuccess)

    if (errorTypes.isEmpty) {
      None
    } else {
      println("errorTypes: " + errorTypeClass(errorTypes.head))

      Some(
        Seq(
          "package error {",
          "",
          s"  import ${ssd.packageName}.models.json._",
          "",
          errorTypes.map { t => errorTypeClass(t) }.distinct.sorted.mkString("\n\n").indent(2),
          "}"
        ).mkString("\n").indent(2)
      )
    }
  }

  private def client(ssd: ScalaServiceDescription): String = {
    val errorsString = errors(ssd) match {
      case None => ""
      case Some(s: String) => {
        println("S: " + s)
        s"\n\n$s\n"
      }
    }

s"""package ${ssd.packageName} {

  case class FailedResponse(response: play.api.libs.ws.Response) extends Exception$errorsString

  class Client(apiUrl: String, apiToken: scala.Option[String] = None) {
    import ${ssd.packageName}.models._
    import ${ssd.packageName}.models.json._

    private val logger = play.api.Logger("${ssd.packageName}.client")

    logger.info(s"Initializing ${ssd.packageName}.client for url $$apiUrl")

    def requestHolder(path: String): play.api.libs.ws.WSRequestHolder = {
      import play.api.Play.current

      val holder = play.api.libs.ws.WS.url(apiUrl + path)
      apiToken.fold(holder) { token =>
        holder.withAuth(token, "", play.api.libs.ws.WSAuthScheme.BASIC)
      }
    }

    def logRequest(method: String, req: play.api.libs.ws.WSRequestHolder)(implicit ec: scala.concurrent.ExecutionContext): play.api.libs.ws.WSRequestHolder = {
      val queryComponents = for {
        (name, values) <- req.queryString
        value <- values
      } yield name -> value
      val url = s"$${req.url}$${queryComponents.mkString("?", "&", "")}"
      apiToken.fold(logger.info(s"curl -X $$method $$url")) { _ =>
        logger.info(s"curl -X $$method -u '[REDACTED]:' $$url")
      }
      req
    }

    private def POST(path: String, data: play.api.libs.json.JsValue)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      logRequest("POST", requestHolder(path)).post(data)
    }

    private def GET(path: String, q: Seq[(String, String)])(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      logRequest("GET", requestHolder(path).withQueryString(q:_*)).get()
    }

    private def PUT(path: String, data: play.api.libs.json.JsValue)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      logRequest("PUT", requestHolder(path)).put(data)
    }

    private def PATCH(path: String, data: play.api.libs.json.JsValue)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      logRequest("PATCH", requestHolder(path)).patch(data)
    }

    private def DELETE(path: String)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      logRequest("DELETE", requestHolder(path)).delete()
    }

${modelClients(ssd).indent(4)}
  }
}"""
  }

  private def modelClients(ssd: ScalaServiceDescription): String = {
    val grouped = ssd.resources.groupBy(_.model.plural)
    grouped.toSeq.sortBy(_._1).map { case (plural, resources) =>
      s"  trait $plural {\n" +
      clientMethods(ssd, resources).map(_.interface).mkString("\n\n").indent(4) +
      "  }\n\n" +
      s"  object $plural extends $plural {\n" +
      clientMethods(ssd, resources).map(_.code).mkString("\n\n").indent(4) +
      "\n  }"
    }.mkString("\n\n")
  }

  private def clientMethods(ssd: ScalaServiceDescription, resources: Seq[ScalaResource]): Seq[ClientMethod] = {
    resources.flatMap(_.operations).map { op =>
      val path: String = Play2Util.pathParams(op)
      def payload = Play2Util.formParams(op)
      val methodCall: String = op.method match {
        case "GET" => {
          s"""${Play2Util.queryParams(op)}

GET($path, queryBuilder.result)"""
        }

        case "POST" => {
          s"""$payload

POST($path, payload)"""
        }

        case "PUT" => {
          s"""$payload

PUT($path, payload)"""
        }

        case "PATCH" => {
          s"""$payload<commit_msg>Add top level methods to access resources - enables simpler mocks for testing<commit_after>package core.generator

import core._
import Text._
import ScalaUtil._

object Play2ClientGenerator {
  def apply(json: String): String = {
    val sd = ServiceDescription(json)
    val ssd = new ScalaServiceDescription(sd)
    apply(ssd)
  }

  def apply(ssd: ScalaServiceDescription): String = {
s"""${Play2Models(ssd)}

${client(ssd)}"""
  }

  private[generator] def errorTypeClass(response: ScalaResponse): String = {
    require(!response.isSuccess)

    Seq(
      s"case class ${response.errorClassName}(response: play.api.libs.ws.Response) extends Exception {",
      "",
      s"  lazy val ${response.errorVariableName} = response.json.as[${response.resultType}]",
      "",
      "}"
    ).mkString("\n")
  }

  private def errors(ssd: ScalaServiceDescription): Option[String] = {
    val errorTypes = ssd.resources.flatMap(_.operations).flatMap(_.responses).filter(!_.isSuccess)

    if (errorTypes.isEmpty) {
      None
    } else {
      println("errorTypes: " + errorTypeClass(errorTypes.head))

      Some(
        Seq(
          "package error {",
          "",
          s"  import ${ssd.packageName}.models.json._",
          "",
          errorTypes.map { t => errorTypeClass(t) }.distinct.sorted.mkString("\n\n").indent(2),
          "}"
        ).mkString("\n").indent(2)
      )
    }
  }

  private def client(ssd: ScalaServiceDescription): String = {
    val errorsString = errors(ssd) match {
      case None => ""
      case Some(s: String) => {
        println("S: " + s)
        s"\n\n$s\n"
      }
    }

    val accessors = ssd.resources.map(_.model.plural).sorted.map { plural =>
      val methodName = Text.snakeToCamelCase(Text.camelCaseToUnderscore(plural).toLowerCase)
      s"def ${methodName} = ${plural}"
    }.mkString("\n\n")

s"""package ${ssd.packageName} {

  case class FailedResponse(response: play.api.libs.ws.Response) extends Exception$errorsString

  class Client(apiUrl: String, apiToken: scala.Option[String] = None) {
    import ${ssd.packageName}.models._
    import ${ssd.packageName}.models.json._

    private val logger = play.api.Logger("${ssd.packageName}.client")

    logger.info(s"Initializing ${ssd.packageName}.client for url $$apiUrl")

${accessors.indent(4)}

    def _requestHolder(path: String): play.api.libs.ws.WSRequestHolder = {
      import play.api.Play.current

      val holder = play.api.libs.ws.WS.url(apiUrl + path)
      apiToken.fold(holder) { token =>
        holder.withAuth(token, "", play.api.libs.ws.WSAuthScheme.BASIC)
      }
    }

    def _logRequest(method: String, req: play.api.libs.ws.WSRequestHolder)(implicit ec: scala.concurrent.ExecutionContext): play.api.libs.ws.WSRequestHolder = {
      val queryComponents = for {
        (name, values) <- req.queryString
        value <- values
      } yield name -> value
      val url = s"$${req.url}$${queryComponents.mkString("?", "&", "")}"
      apiToken.fold(logger.info(s"curl -X $$method $$url")) { _ =>
        logger.info(s"curl -X $$method -u '[REDACTED]:' $$url")
      }
      req
    }

    private def POST(path: String, data: play.api.libs.json.JsValue)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      _logRequest("POST", _requestHolder(path)).post(data)
    }

    private def GET(path: String, q: Seq[(String, String)])(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      _logRequest("GET", _requestHolder(path).withQueryString(q:_*)).get()
    }

    private def PUT(path: String, data: play.api.libs.json.JsValue)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      _logRequest("PUT", _requestHolder(path)).put(data)
    }

    private def PATCH(path: String, data: play.api.libs.json.JsValue)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      _logRequest("PATCH", _requestHolder(path)).patch(data)
    }

    private def DELETE(path: String)(implicit ec: scala.concurrent.ExecutionContext): scala.concurrent.Future[play.api.libs.ws.WSResponse] = {
      _logRequest("DELETE", _requestHolder(path)).delete()
    }

${modelClients(ssd).indent(2)}
  }
}"""
  }

  private def modelClients(ssd: ScalaServiceDescription): String = {
    ssd.resources.groupBy(_.model.plural).toSeq.sortBy(_._1).map { case (plural, resources) =>
      s"  trait $plural {\n" +
      clientMethods(ssd, resources).map(_.interface).mkString("\n\n").indent(4) +
      "\n  }\n\n" +
      s"  object $plural extends $plural {\n" +
      clientMethods(ssd, resources).map(_.code).mkString("\n\n").indent(4) +
      "\n  }"
    }.mkString("\n\n")
  }

  private def clientMethods(ssd: ScalaServiceDescription, resources: Seq[ScalaResource]): Seq[ClientMethod] = {
    resources.flatMap(_.operations).map { op =>
      val path: String = Play2Util.pathParams(op)
      def payload = Play2Util.formParams(op)
      val methodCall: String = op.method match {
        case "GET" => {
          s"""${Play2Util.queryParams(op)}

GET($path, queryBuilder.result)"""
        }

        case "POST" => {
          s"""$payload

POST($path, payload)"""
        }

        case "PUT" => {
          s"""$payload

PUT($path, payload)"""
        }

        case "PATCH" => {
          s"""$payload
{'fraction_non_alphanumeric': 0.15913166588013214, 'fraction_numerical': 0.0021708352996696557, 'mean_word_length': 3.1374463100351426, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3226115', 'n_tokens_mistral': 3486, 'n_tokens_neox': 3396, 'n_words': 594}