Msg.java

/*
 * SPDX-FileCopyrightText: 2025 kaumei.io
 * SPDX-License-Identifier: Apache-2.0
 */
package io.kaumei.jdbc.anno.msg;

import org.jspecify.annotations.Nullable;

import java.util.Collections;
import java.util.TreeSet;
import java.util.function.Supplier;

public interface Msg {

    static Msg.Builder builder() {
        return new MsgBuilderImpl();
    }

    static Msg.Message of(String text) {
        return new MessageImpl(text);
    }

    static Messages empty() {
        return MessagesEmptyImpl.EMPTY_LIST;
    }

    static <T> Msg.Result<T> result(T value) {
        return new MessageResultImpl<>(value);
    }

    static <T> Msg.Result<T> result(Messages messages) {
        return new MessageResultImpl<>(messages);
    }

    static <T> Msg.NullableResult<T> nullableResult(@Nullable T value) {
        return new MessageResultNullableImpl<T>(value);
    }

    static <T> Msg.NullableResult<T> nullableResult(Messages messages) {
        return new MessageResultNullableImpl<>(messages);
    }

    // ------------------------------------------------------------------------

    static Messages merge(Msg.Message msg1, Msg.Message msg2) {
        var s = new TreeSet<Msg.Message>();
        s.add(msg1);
        s.add(msg2);
        return new MessagesImpl(s);
    }

    static Messages merge(Msg.Message... messages) {
        var s = new TreeSet<Msg.Message>();
        Collections.addAll(s, messages);
        return new MessagesImpl(s);
    }

    static Messages merge(Messages msgSet1, Messages msgSet2, Msg.Message msg) {
        var s = new TreeSet<Msg.Message>();
        for (var item : msgSet1) {
            s.add(item);
        }
        for (var item : msgSet2) {
            s.add(item);
        }
        s.add(msg);
        return new MessagesImpl(s);
    }

    static Messages merge(Messages msgSet1, Messages msgSet2) {
        var s = new TreeSet<Msg.Message>();
        for (var item : msgSet1) {
            s.add(item);
        }
        for (var item : msgSet2) {
            s.add(item);
        }
        return s.isEmpty() ? Msg.empty() : new MessagesImpl(s);
    }

    // ------------------------------------------------------------------------

    sealed interface Builder permits MsgBuilderImpl {

        boolean hasMessages();

        boolean hasNoMessages();

        String join(String sep);

        void requireEmpty();

        Messages build();

        /**
         * Will fail if the value is null, regardless of whether messages are
         * present or not.
         * For a lazy null check, use build(Supplier<T> func).
         */
        <T> Result<T> build(T value);

        <T> Result<T> build(Supplier<T> func);

        <T> NullableResult<T> buildOpt(@Nullable T value);

        Builder add(Message msg);

        Builder add(boolean test, Message msg);

        Builder add(Supplier<Boolean> test, Message msg);

        Builder add(Messages msg);

        Builder add(@Nullable HasMessages value);
    }

    sealed interface Messages extends Iterable<Msg.Message> permits MessagesEmptyImpl, MessagesImpl, Message {

        boolean hasMessages();

        boolean hasNoMessages();

        String join(String sep);

        void join(StringBuilder sb, String sep);

        void requireEmpty();

        Messages requireNoneEmpty();

        /**
         * Will fail if the value is null, regardless of whether messages are
         * present or not.
         * For a lazy null check, use build(Supplier<T> func).
         */
        <T> Result<T> build(T value);

        <T> Result<T> build(Supplier<T> func);

        <T> NullableResult<T> buildOpt(@Nullable T value);
    }

    sealed interface Message extends Messages, Comparable<Msg.Message> permits MessageImpl {
        String text();
    }

    interface HasMessages {

        boolean hasMessages();

        Messages messages();
    }

    sealed interface Result<T> extends HasMessages permits MessageResultImpl {

        T value();

        /**
         * @return the value or in case the result has messages null
         */
        @Nullable T valueOrNull();
    }

    sealed interface NullableResult<T> extends HasMessages permits MessageResultNullableImpl {

        /**
         * @return if value is null and no messages
         */
        boolean isNull();

        /**
         * @return the value and ensures to be always not null
         * @throws IllegalStateException if the result has messages or the value is null
         */
        T value();

        @Nullable T valueOpt();
    }
}