// File generated from our OpenAPI spec by Stainless.

package com.openai.models.finetuning.alpha.graders

import com.fasterxml.jackson.annotation.JsonAnyGetter
import com.fasterxml.jackson.annotation.JsonAnySetter
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.ObjectCodec
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import com.openai.core.BaseDeserializer
import com.openai.core.BaseSerializer
import com.openai.core.ExcludeMissing
import com.openai.core.JsonField
import com.openai.core.JsonMissing
import com.openai.core.JsonValue
import com.openai.core.allMaxBy
import com.openai.core.getOrThrow
import com.openai.errors.OpenAIInvalidDataException
import com.openai.models.graders.gradermodels.MultiGrader
import com.openai.models.graders.gradermodels.PythonGrader
import com.openai.models.graders.gradermodels.ScoreModelGrader
import com.openai.models.graders.gradermodels.StringCheckGrader
import com.openai.models.graders.gradermodels.TextSimilarityGrader
import java.util.Collections
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull

class GraderValidateResponse
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
    private val grader: JsonField<Grader>,
    private val additionalProperties: MutableMap<String, JsonValue>,
) {

    @JsonCreator
    private constructor(
        @JsonProperty("grader") @ExcludeMissing grader: JsonField<Grader> = JsonMissing.of()
    ) : this(grader, mutableMapOf())

    /**
     * The grader used for the fine-tuning job.
     *
     * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
     *   server responded with an unexpected value).
     */
    fun grader(): Optional<Grader> = grader.getOptional("grader")

    /**
     * Returns the raw JSON value of [grader].
     *
     * Unlike [grader], this method doesn't throw if the JSON field has an unexpected type.
     */
    @JsonProperty("grader") @ExcludeMissing fun _grader(): JsonField<Grader> = grader

    @JsonAnySetter
    private fun putAdditionalProperty(key: String, value: JsonValue) {
        additionalProperties.put(key, value)
    }

    @JsonAnyGetter
    @ExcludeMissing
    fun _additionalProperties(): Map<String, JsonValue> =
        Collections.unmodifiableMap(additionalProperties)

    fun toBuilder() = Builder().from(this)

    companion object {

        /** Returns a mutable builder for constructing an instance of [GraderValidateResponse]. */
        @JvmStatic fun builder() = Builder()
    }

    /** A builder for [GraderValidateResponse]. */
    class Builder internal constructor() {

        private var grader: JsonField<Grader> = JsonMissing.of()
        private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()

        @JvmSynthetic
        internal fun from(graderValidateResponse: GraderValidateResponse) = apply {
            grader = graderValidateResponse.grader
            additionalProperties = graderValidateResponse.additionalProperties.toMutableMap()
        }

        /** The grader used for the fine-tuning job. */
        fun grader(grader: Grader) = grader(JsonField.of(grader))

        /**
         * Sets [Builder.grader] to an arbitrary JSON value.
         *
         * You should usually call [Builder.grader] with a well-typed [Grader] value instead. This
         * method is primarily for setting the field to an undocumented or not yet supported value.
         */
        fun grader(grader: JsonField<Grader>) = apply { this.grader = grader }

        /** Alias for calling [grader] with `Grader.ofStringCheck(stringCheck)`. */
        fun grader(stringCheck: StringCheckGrader) = grader(Grader.ofStringCheck(stringCheck))

        /** Alias for calling [grader] with `Grader.ofTextSimilarity(textSimilarity)`. */
        fun grader(textSimilarity: TextSimilarityGrader) =
            grader(Grader.ofTextSimilarity(textSimilarity))

        /** Alias for calling [grader] with `Grader.ofPython(python)`. */
        fun grader(python: PythonGrader) = grader(Grader.ofPython(python))

        /** Alias for calling [grader] with `Grader.ofScoreModel(scoreModel)`. */
        fun grader(scoreModel: ScoreModelGrader) = grader(Grader.ofScoreModel(scoreModel))

        /** Alias for calling [grader] with `Grader.ofMulti(multi)`. */
        fun grader(multi: MultiGrader) = grader(Grader.ofMulti(multi))

        fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
            this.additionalProperties.clear()
            putAllAdditionalProperties(additionalProperties)
        }

        fun putAdditionalProperty(key: String, value: JsonValue) = apply {
            additionalProperties.put(key, value)
        }

        fun putAllAdditionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
            this.additionalProperties.putAll(additionalProperties)
        }

        fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }

        fun removeAllAdditionalProperties(keys: Set<String>) = apply {
            keys.forEach(::removeAdditionalProperty)
        }

        /**
         * Returns an immutable instance of [GraderValidateResponse].
         *
         * Further updates to this [Builder] will not mutate the returned instance.
         */
        fun build(): GraderValidateResponse =
            GraderValidateResponse(grader, additionalProperties.toMutableMap())
    }

    private var validated: Boolean = false

    /**
     * Validates that the types of all values in this object match their expected types recursively.
     *
     * This method is _not_ forwards compatible with new types from the API for existing fields.
     *
     * @throws OpenAIInvalidDataException if any value type in this object doesn't match its
     *   expected type.
     */
    fun validate(): GraderValidateResponse = apply {
        if (validated) {
            return@apply
        }

        grader().ifPresent { it.validate() }
        validated = true
    }

    fun isValid(): Boolean =
        try {
            validate()
            true
        } catch (e: OpenAIInvalidDataException) {
            false
        }

    /**
     * Returns a score indicating how many valid values are contained in this object recursively.
     *
     * Used for best match union deserialization.
     */
    @JvmSynthetic internal fun validity(): Int = (grader.asKnown().getOrNull()?.validity() ?: 0)

    /** The grader used for the fine-tuning job. */
    @JsonDeserialize(using = Grader.Deserializer::class)
    @JsonSerialize(using = Grader.Serializer::class)
    class Grader
    private constructor(
        private val stringCheck: StringCheckGrader? = null,
        private val textSimilarity: TextSimilarityGrader? = null,
        private val python: PythonGrader? = null,
        private val scoreModel: ScoreModelGrader? = null,
        private val multi: MultiGrader? = null,
        private val _json: JsonValue? = null,
    ) {

        /**
         * A StringCheckGrader object that performs a string comparison between input and reference
         * using a specified operation.
         */
        fun stringCheck(): Optional<StringCheckGrader> = Optional.ofNullable(stringCheck)

        /** A TextSimilarityGrader object which grades text based on similarity metrics. */
        fun textSimilarity(): Optional<TextSimilarityGrader> = Optional.ofNullable(textSimilarity)

        /** A PythonGrader object that runs a python script on the input. */
        fun python(): Optional<PythonGrader> = Optional.ofNullable(python)

        /** A ScoreModelGrader object that uses a model to assign a score to the input. */
        fun scoreModel(): Optional<ScoreModelGrader> = Optional.ofNullable(scoreModel)

        /**
         * A MultiGrader object combines the output of multiple graders to produce a single score.
         */
        fun multi(): Optional<MultiGrader> = Optional.ofNullable(multi)

        fun isStringCheck(): Boolean = stringCheck != null

        fun isTextSimilarity(): Boolean = textSimilarity != null

        fun isPython(): Boolean = python != null

        fun isScoreModel(): Boolean = scoreModel != null

        fun isMulti(): Boolean = multi != null

        /**
         * A StringCheckGrader object that performs a string comparison between input and reference
         * using a specified operation.
         */
        fun asStringCheck(): StringCheckGrader = stringCheck.getOrThrow("stringCheck")

        /** A TextSimilarityGrader object which grades text based on similarity metrics. */
        fun asTextSimilarity(): TextSimilarityGrader = textSimilarity.getOrThrow("textSimilarity")

        /** A PythonGrader object that runs a python script on the input. */
        fun asPython(): PythonGrader = python.getOrThrow("python")

        /** A ScoreModelGrader object that uses a model to assign a score to the input. */
        fun asScoreModel(): ScoreModelGrader = scoreModel.getOrThrow("scoreModel")

        /**
         * A MultiGrader object combines the output of multiple graders to produce a single score.
         */
        fun asMulti(): MultiGrader = multi.getOrThrow("multi")

        fun _json(): Optional<JsonValue> = Optional.ofNullable(_json)

        /**
         * Maps this instance's current variant to a value of type [T] using the given [visitor].
         *
         * Note that this method is _not_ forwards compatible with new variants from the API, unless
         * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of
         * the SDK gracefully, consider overriding [Visitor.unknown]:
         * ```java
         * import com.openai.core.JsonValue;
         * import java.util.Optional;
         *
         * Optional<String> result = grader.accept(new Grader.Visitor<Optional<String>>() {
         *     @Override
         *     public Optional<String> visitStringCheck(StringCheckGrader stringCheck) {
         *         return Optional.of(stringCheck.toString());
         *     }
         *
         *     // ...
         *
         *     @Override
         *     public Optional<String> unknown(JsonValue json) {
         *         // Or inspect the `json`.
         *         return Optional.empty();
         *     }
         * });
         * ```
         *
         * @throws OpenAIInvalidDataException if [Visitor.unknown] is not overridden in [visitor]
         *   and the current variant is unknown.
         */
        fun <T> accept(visitor: Visitor<T>): T =
            when {
                stringCheck != null -> visitor.visitStringCheck(stringCheck)
                textSimilarity != null -> visitor.visitTextSimilarity(textSimilarity)
                python != null -> visitor.visitPython(python)
                scoreModel != null -> visitor.visitScoreModel(scoreModel)
                multi != null -> visitor.visitMulti(multi)
                else -> visitor.unknown(_json)
            }

        private var validated: Boolean = false

        /**
         * Validates that the types of all values in this object match their expected types
         * recursively.
         *
         * This method is _not_ forwards compatible with new types from the API for existing fields.
         *
         * @throws OpenAIInvalidDataException if any value type in this object doesn't match its
         *   expected type.
         */
        fun validate(): Grader = apply {
            if (validated) {
                return@apply
            }

            accept(
                object : Visitor<Unit> {
                    override fun visitStringCheck(stringCheck: StringCheckGrader) {
                        stringCheck.validate()
                    }

                    override fun visitTextSimilarity(textSimilarity: TextSimilarityGrader) {
                        textSimilarity.validate()
                    }

                    override fun visitPython(python: PythonGrader) {
                        python.validate()
                    }

                    override fun visitScoreModel(scoreModel: ScoreModelGrader) {
                        scoreModel.validate()
                    }

                    override fun visitMulti(multi: MultiGrader) {
                        multi.validate()
                    }
                }
            )
            validated = true
        }

        fun isValid(): Boolean =
            try {
                validate()
                true
            } catch (e: OpenAIInvalidDataException) {
                false
            }

        /**
         * Returns a score indicating how many valid values are contained in this object
         * recursively.
         *
         * Used for best match union deserialization.
         */
        @JvmSynthetic
        internal fun validity(): Int =
            accept(
                object : Visitor<Int> {
                    override fun visitStringCheck(stringCheck: StringCheckGrader) =
                        stringCheck.validity()

                    override fun visitTextSimilarity(textSimilarity: TextSimilarityGrader) =
                        textSimilarity.validity()

                    override fun visitPython(python: PythonGrader) = python.validity()

                    override fun visitScoreModel(scoreModel: ScoreModelGrader) =
                        scoreModel.validity()

                    override fun visitMulti(multi: MultiGrader) = multi.validity()

                    override fun unknown(json: JsonValue?) = 0
                }
            )

        override fun equals(other: Any?): Boolean {
            if (this === other) {
                return true
            }

            return other is Grader &&
                stringCheck == other.stringCheck &&
                textSimilarity == other.textSimilarity &&
                python == other.python &&
                scoreModel == other.scoreModel &&
                multi == other.multi
        }

        override fun hashCode(): Int =
            Objects.hash(stringCheck, textSimilarity, python, scoreModel, multi)

        override fun toString(): String =
            when {
                stringCheck != null -> "Grader{stringCheck=$stringCheck}"
                textSimilarity != null -> "Grader{textSimilarity=$textSimilarity}"
                python != null -> "Grader{python=$python}"
                scoreModel != null -> "Grader{scoreModel=$scoreModel}"
                multi != null -> "Grader{multi=$multi}"
                _json != null -> "Grader{_unknown=$_json}"
                else -> throw IllegalStateException("Invalid Grader")
            }

        companion object {

            /**
             * A StringCheckGrader object that performs a string comparison between input and
             * reference using a specified operation.
             */
            @JvmStatic
            fun ofStringCheck(stringCheck: StringCheckGrader) = Grader(stringCheck = stringCheck)

            /** A TextSimilarityGrader object which grades text based on similarity metrics. */
            @JvmStatic
            fun ofTextSimilarity(textSimilarity: TextSimilarityGrader) =
                Grader(textSimilarity = textSimilarity)

            /** A PythonGrader object that runs a python script on the input. */
            @JvmStatic fun ofPython(python: PythonGrader) = Grader(python = python)

            /** A ScoreModelGrader object that uses a model to assign a score to the input. */
            @JvmStatic
            fun ofScoreModel(scoreModel: ScoreModelGrader) = Grader(scoreModel = scoreModel)

            /**
             * A MultiGrader object combines the output of multiple graders to produce a single
             * score.
             */
            @JvmStatic fun ofMulti(multi: MultiGrader) = Grader(multi = multi)
        }

        /** An interface that defines how to map each variant of [Grader] to a value of type [T]. */
        interface Visitor<out T> {

            /**
             * A StringCheckGrader object that performs a string comparison between input and
             * reference using a specified operation.
             */
            fun visitStringCheck(stringCheck: StringCheckGrader): T

            /** A TextSimilarityGrader object which grades text based on similarity metrics. */
            fun visitTextSimilarity(textSimilarity: TextSimilarityGrader): T

            /** A PythonGrader object that runs a python script on the input. */
            fun visitPython(python: PythonGrader): T

            /** A ScoreModelGrader object that uses a model to assign a score to the input. */
            fun visitScoreModel(scoreModel: ScoreModelGrader): T

            /**
             * A MultiGrader object combines the output of multiple graders to produce a single
             * score.
             */
            fun visitMulti(multi: MultiGrader): T

            /**
             * Maps an unknown variant of [Grader] to a value of type [T].
             *
             * An instance of [Grader] can contain an unknown variant if it was deserialized from
             * data that doesn't match any known variant. For example, if the SDK is on an older
             * version than the API, then the API may respond with new variants that the SDK is
             * unaware of.
             *
             * @throws OpenAIInvalidDataException in the default implementation.
             */
            fun unknown(json: JsonValue?): T {
                throw OpenAIInvalidDataException("Unknown Grader: $json")
            }
        }

        internal class Deserializer : BaseDeserializer<Grader>(Grader::class) {

            override fun ObjectCodec.deserialize(node: JsonNode): Grader {
                val json = JsonValue.fromJsonNode(node)

                val bestMatches =
                    sequenceOf(
                            tryDeserialize(node, jacksonTypeRef<StringCheckGrader>())?.let {
                                Grader(stringCheck = it, _json = json)
                            },
                            tryDeserialize(node, jacksonTypeRef<TextSimilarityGrader>())?.let {
                                Grader(textSimilarity = it, _json = json)
                            },
                            tryDeserialize(node, jacksonTypeRef<PythonGrader>())?.let {
                                Grader(python = it, _json = json)
                            },
                            tryDeserialize(node, jacksonTypeRef<ScoreModelGrader>())?.let {
                                Grader(scoreModel = it, _json = json)
                            },
                            tryDeserialize(node, jacksonTypeRef<MultiGrader>())?.let {
                                Grader(multi = it, _json = json)
                            },
                        )
                        .filterNotNull()
                        .allMaxBy { it.validity() }
                        .toList()
                return when (bestMatches.size) {
                    // This can happen if what we're deserializing is completely incompatible with
                    // all the possible variants (e.g. deserializing from boolean).
                    0 -> Grader(_json = json)
                    1 -> bestMatches.single()
                    // If there's more than one match with the highest validity, then use the first
                    // completely valid match, or simply the first match if none are completely
                    // valid.
                    else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first()
                }
            }
        }

        internal class Serializer : BaseSerializer<Grader>(Grader::class) {

            override fun serialize(
                value: Grader,
                generator: JsonGenerator,
                provider: SerializerProvider,
            ) {
                when {
                    value.stringCheck != null -> generator.writeObject(value.stringCheck)
                    value.textSimilarity != null -> generator.writeObject(value.textSimilarity)
                    value.python != null -> generator.writeObject(value.python)
                    value.scoreModel != null -> generator.writeObject(value.scoreModel)
                    value.multi != null -> generator.writeObject(value.multi)
                    value._json != null -> generator.writeObject(value._json)
                    else -> throw IllegalStateException("Invalid Grader")
                }
            }
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) {
            return true
        }

        return other is GraderValidateResponse &&
            grader == other.grader &&
            additionalProperties == other.additionalProperties
    }

    private val hashCode: Int by lazy { Objects.hash(grader, additionalProperties) }

    override fun hashCode(): Int = hashCode

    override fun toString() =
        "GraderValidateResponse{grader=$grader, additionalProperties=$additionalProperties}"
}
