{{ header }}package {{ package }};

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

{%- if doc %}
/**
 * {{ doc }}
 */
{%- endif %}
@SuppressWarnings("PMD")
public final class {{ class_name }} {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private final JsonNode value;

    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public {{ class_name }}(final JsonNode value) {
        this.value = value;
    }

    @JsonValue
    public JsonNode value() {
        return value;
    }

    public static {{ class_name }} of(final String s) {
        return new {{ class_name }}(JsonNodeFactory.instance.textNode(s));
    }

    public static {{ class_name }} of(final List<String> items) {
        var arr = JsonNodeFactory.instance.arrayNode();
        for (var s : items) { arr.add(s); }
        return new {{ class_name }}(arr);
    }

    public static {{ class_name }} ofObject(final Object o) {
        return new {{ class_name }}(MAPPER.valueToTree(o));
    }

    public static {{ class_name }} fromJson(final String json) throws {{ exception_class }} {
        try {
            return new {{ class_name }}(MAPPER.readTree(json));
        } catch (Exception e) {
            throw new {{ exception_class }}("Failed to parse {{ class_name }} from JSON: " + e.getMessage(), e);
        }
    }

    // CPD-OFF — wrapper boilerplate is intentionally identical across all
    // untagged-union classes; CPD's duplicate-block detector flags the asString /
    // asList / asObject / equals / hashCode / toString block (~170 tokens) which
    // is the price of avoiding a generic base class. See alef gen_java_untagged_wrapper.
    public Optional<String> asString() {
        return value != null && value.isTextual() ? Optional.of(value.asText()) : Optional.empty();
    }

    public Optional<List<String>> asList() {
        if (value == null || !value.isArray()) { return Optional.empty(); }
        var result = new ArrayList<String>(value.size());
        for (var node : value) { result.add(node.asText()); }
        return Optional.of(result);
    }

    public Optional<JsonNode> asObject() {
        return value != null && value.isObject() ? Optional.of(value) : Optional.empty();
    }
{% if emit_text %}
    /**
     * Returns the plain-text display value of this content.
     *
     * <ul>
     *   <li>If the value is a JSON string, it is returned verbatim.</li>
     *   <li>If the value is a JSON array, the {@code "text"} field of every element
     *       whose {@code "type"} equals {@code "text"} is concatenated in order;
     *       non-text parts (images, audio, refusals, etc.) are skipped.</li>
     *   <li>Otherwise (null, object, empty) returns {@code ""}.</li>
     * </ul>
     */
    public String text() {
        if (value == null) { return ""; }
        if (value.isTextual()) { return value.asText(); }
        if (value.isArray()) {
            var sb = new StringBuilder();
            for (var part : value) {
                var typeNode = part.get("type");
                if (typeNode != null && "text".equals(typeNode.asText())) {
                    var textNode = part.get("text");
                    if (textNode != null) { sb.append(textNode.asText()); }
                }
            }
            return sb.toString();
        }
        return "";
    }
{% endif %}
    @Override
    public boolean equals(final Object o) {
        return this == o
            || (o instanceof {{ class_name }} other && Objects.equals(value, other.value));
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    @Override
    public String toString() {
        return value == null ? "null" : value.toString();
    }
    // CPD-ON
}
