#pragma once

#include "fesa/core/diagnostic.hpp"

#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>

namespace fesa {

// Maps a failure to the stable command-line exit-code classes defined by V0.
enum class FailureCategory {
    input,
    model,
    solver,
    output
};

// Transports success or structured diagnostics without exposing backend errors.
class Status {
public:
    static Status ok();
    static Status failure(std::vector<Diagnostic> diagnostics);
    static Status failure(
        FailureCategory category, std::vector<Diagnostic> diagnostics);

    bool isOk() const noexcept;
    std::optional<FailureCategory> failureCategory() const noexcept;
    const std::vector<Diagnostic>& diagnostics() const noexcept;

private:
    Status(
        bool isOk,
        std::optional<FailureCategory> category,
        std::vector<Diagnostic> diagnostics);

    bool isOk_;
    std::optional<FailureCategory> category_;
    std::vector<Diagnostic> diagnostics_;
};

// Owns exactly one successful value or one failed Status.
template<class T>
class Result {
public:
    static Result success(T value) {
        return Result{SuccessTag{}, std::move(value)};
    }

    static Result failure(Status status) {
        if (status.isOk()) {
            throw std::invalid_argument{"A failed Result requires a failed Status."};
        }
        return Result{FailureTag{}, std::move(status)};
    }

    bool hasValue() const noexcept {
        return value_.has_value();
    }

    T& value() {
        if (!value_) {
            throw std::logic_error{"Result has no value."};
        }
        return *value_;
    }

    const T& value() const {
        if (!value_) {
            throw std::logic_error{"Result has no value."};
        }
        return *value_;
    }

    const Status& status() const noexcept {
        return status_;
    }

private:
    struct SuccessTag {};
    struct FailureTag {};

    Result(SuccessTag, T value)
        : value_{std::move(value)}, status_{Status::ok()} {}

    Result(FailureTag, Status status)
        : value_{std::nullopt}, status_{std::move(status)} {}

    std::optional<T> value_;
    Status status_;
};

} // namespace fesa
