package de.effigenix.shared.common;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Result type for functional error handling.
 * Represents either a success value or a failure error.
 *
 * @param <E> Error type
 * @param <T> Success value type
 */
public sealed interface Result<E, T> {

    /**
     * Creates a successful result.
     */
    static <E, T> Result<E, T> success(T value) {
        return new Success<>(value);
    }

    /**
     * Creates a failed result.
     */
    static <E, T> Result<E, T> failure(E error) {
        return new Failure<>(error);
    }

    /**
     * Returns true if this is a success result.
     */
    boolean isSuccess();

    /**
     * Returns true if this is a failure result.
     */
    boolean isFailure();

    /**
     * Gets the success value, throws if failure.
     */
    T unsafeGetValue();

    /**
     * Gets the error, throws if success.
     */
    E unsafeGetError();

    /**
     * Gets the success value as Optional.
     */
    Optional<T> toOptional();

    /**
     * Maps the success value to another type.
     */
    <U> Result<E, U> map(Function<T, U> mapper);

    /**
     * Maps the error to another type.
     */
    <F> Result<F, T> mapError(Function<E, F> mapper);

    /**
     * FlatMaps the success value to another Result.
     */
    <U> Result<E, U> flatMap(Function<T, Result<E, U>> mapper);

    /**
     * Folds the result into a single value.
     */
    <R> R fold(Function<E, R> onFailure, Function<T, R> onSuccess);

    /**
     * Executes action if success.
     */
    Result<E, T> onSuccess(Consumer<T> action);

    /**
     * Executes action if failure.
     */
    Result<E, T> onFailure(Consumer<E> action);

    /**
     * Success implementation.
     */
    record Success<E, T>(T value) implements Result<E, T> {
        @Override
        public boolean isSuccess() {
            return true;
        }

        @Override
        public boolean isFailure() {
            return false;
        }

        @Override
        public T unsafeGetValue() {
            return value;
        }

        @Override
        public E unsafeGetError() {
            throw new IllegalStateException("Cannot get error from Success");
        }

        @Override
        public Optional<T> toOptional() {
            return Optional.ofNullable(value);
        }

        @Override
        public <U> Result<E, U> map(Function<T, U> mapper) {
            return new Success<>(mapper.apply(value));
        }

        @Override
        public <F> Result<F, T> mapError(Function<E, F> mapper) {
            return new Success<>(value);
        }

        @Override
        public <U> Result<E, U> flatMap(Function<T, Result<E, U>> mapper) {
            return mapper.apply(value);
        }

        @Override
        public <R> R fold(Function<E, R> onFailure, Function<T, R> onSuccess) {
            return onSuccess.apply(value);
        }

        @Override
        public Result<E, T> onSuccess(Consumer<T> action) {
            action.accept(value);
            return this;
        }

        @Override
        public Result<E, T> onFailure(Consumer<E> action) {
            return this;
        }
    }

    /**
     * Failure implementation.
     */
    record Failure<E, T>(E error) implements Result<E, T> {
        @Override
        public boolean isSuccess() {
            return false;
        }

        @Override
        public boolean isFailure() {
            return true;
        }

        @Override
        public T unsafeGetValue() {
            throw new IllegalStateException("Cannot get value from Failure: " + error);
        }

        @Override
        public E unsafeGetError() {
            return error;
        }

        @Override
        public Optional<T> toOptional() {
            return Optional.empty();
        }

        @Override
        public <U> Result<E, U> map(Function<T, U> mapper) {
            return new Failure<>(error);
        }

        @Override
        public <F> Result<F, T> mapError(Function<E, F> mapper) {
            return new Failure<>(mapper.apply(error));
        }

        @Override
        public <U> Result<E, U> flatMap(Function<T, Result<E, U>> mapper) {
            return new Failure<>(error);
        }

        @Override
        public <R> R fold(Function<E, R> onFailure, Function<T, R> onSuccess) {
            return onFailure.apply(error);
        }

        @Override
        public Result<E, T> onSuccess(Consumer<T> action) {
            return this;
        }

        @Override
        public Result<E, T> onFailure(Consumer<E> action) {
            action.accept(error);
            return this;
        }
    }
}
