package {{ package }}

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}

object Codecs {
{% if "java.net.URI" in custom_types %}
  implicit val uriDecoder: Decoder[java.net.URI] =
    Decoder.decodeString.map(java.net.URI.create)
  implicit val uriEncoder: Encoder[java.net.URI] =
    Encoder.encodeString.contramap(_.toString)
{% endif %}
{% if "java.time.LocalDate" in custom_types %}
  implicit val localDateDecoder: Decoder[java.time.LocalDate] =
    Decoder.decodeString.map(java.time.LocalDate.parse)
  implicit val localDateEncoder: Encoder[java.time.LocalDate] =
    Encoder.encodeString.contramap(_.toString)
{% endif %}
{% if "java.time.Instant" in custom_types %}
  implicit val instantDecoder: Decoder[java.time.Instant] =
    Decoder.decodeString.map(java.time.Instant.parse)
  implicit val instantEncoder: Encoder[java.time.Instant] =
    Encoder.encodeString.contramap(_.toString)
{% endif %}
{% for enum in enums %}
  implicit val {{ enum.name[0]|lower }}{{ enum.name[1:] }}Decoder: Decoder[{{ enum.name }}] =
    Decoder.decodeString.emap {
{% for value in enum.enum_values %}
      case "{{ value.linkml_name }}" => Right({{ enum.name }}.{{ value.name }})
{% endfor %}
      case other => Left(s"Unknown {{ enum.name }}: $other")
    }

  implicit val {{ enum.name[0]|lower }}{{ enum.name[1:] }}Encoder: Encoder[{{ enum.name }}] =
    Encoder.encodeString.contramap {
{% for value in enum.enum_values %}
      case {{ enum.name }}.{{ value.name }} => "{{ value.linkml_name }}"
{% endfor %}
    }

  def {{ enum.name[0]|lower }}{{ enum.name[1:] }}FromJson(json: String): Either[io.circe.Error, {{ enum.name }}] =
    io.circe.parser.decode[{{ enum.name }}](json)
  def {{ enum.name[0]|lower }}{{ enum.name[1:] }}ToJson(instance: {{ enum.name }}): String =
    {{ enum.name[0]|lower }}{{ enum.name[1:] }}Encoder(instance).noSpaces
{% endfor %}
{% for cls in case_classes %}
{% if cls.has_validation %}
  private val raw{{ cls.name }}Decoder: Decoder[{{ cls.name }}] = deriveDecoder[{{ cls.name }}]
  implicit val {{ cls.name[0]|lower }}{{ cls.name[1:] }}Decoder: Decoder[{{ cls.name }}] = raw{{ cls.name }}Decoder.emap { instance =>
    {{ cls.name }}.validate(instance) match {
      case Nil    => Right(instance)
      case errors => Left(errors.mkString("; "))
    }
  }
{% else %}
  implicit val {{ cls.name[0]|lower }}{{ cls.name[1:] }}Decoder: Decoder[{{ cls.name }}] = deriveDecoder[{{ cls.name }}]
{% endif %}
  implicit val {{ cls.name[0]|lower }}{{ cls.name[1:] }}Encoder: Encoder[{{ cls.name }}] = deriveEncoder[{{ cls.name }}]

  def {{ cls.name[0]|lower }}{{ cls.name[1:] }}FromJson(json: String): Either[io.circe.Error, {{ cls.name }}] =
    io.circe.parser.decode[{{ cls.name }}](json)
  def {{ cls.name[0]|lower }}{{ cls.name[1:] }}ToJson(instance: {{ cls.name }}): String =
    {{ cls.name[0]|lower }}{{ cls.name[1:] }}Encoder(instance).noSpaces
  def {{ cls.name[0]|lower }}{{ cls.name[1:] }}FromYaml(yaml: String): Either[io.circe.Error, {{ cls.name }}] =
    io.circe.yaml.parser.parse(yaml).flatMap(_.as[{{ cls.name }}])
  def {{ cls.name[0]|lower }}{{ cls.name[1:] }}ToYaml(instance: {{ cls.name }}): String =
    io.circe.yaml.Printer().pretty({{ cls.name[0]|lower }}{{ cls.name[1:] }}Encoder(instance))
{% endfor %}
}
