#pragma once

#include "error_info.h"

#include <infra/libs/outcome/result.h>

#include <any>

namespace NHttp {
/// Класс ошибки. Позволяет передавать данные об ошибке.
/// Может хранить, как просто текст, так и типизированные данные об ошибке.
/// Для типизированных ошибок нужно написать реализацию IErrorInfo.
/// За счет использовании std::any можно избежать выделения памяти,
/// если инстанс реализацию IErrorInfo будет занимать немного места в памяти.
/// TError наследуется от IErrorInfo, поэтому ошибки могут быть вложенными,
/// но в этом случае выделения памяти в куче не избежать.
class TError: public IErrorInfo {
  public:
    typedef IErrorInfo* (*TViewer)(std::any&);

    TError(const TError&) = default;
    TError(TError&&) = default;
    TError& operator=(const TError&) = default;
    TError& operator=(TError&&) = default;

    /// Конструктор создаёт TError, который хранит лишь текст.
    /// \param message текст ошибки
    explicit TError(TString message);

    /// Конструктор создаёт TError, который хранит лишь типизированную информацию об ошибке.
    /// \param errorInfo типизированная информация об ошибке
    template<typename T>
    requires std::is_base_of_v<IErrorInfo, T>
    TError(T errorInfo) {
        StoreInfo(std::move(errorInfo));
    }

    /// Конструктор создаёт TError, который хранит текст ошибки и типизированную информацию об ошибке.
    /// \param message текст ошибки
    /// \param errorInfo типизированная информация об ошибке
    template<typename T>
    requires std::is_base_of_v<IErrorInfo, T>
    TError(TString message, T errorInfo): TError{std::move(message)} {
        StoreInfo(std::move(errorInfo));
    }

    /// Метод добавляет в TError типизированную информацию об ошибке
    /// \param errorInfo типизированная информация об ошибке
    template<typename T>
    requires (std::is_base_of_v<IErrorInfo, T>)
    void StoreInfo(T errorInfo) {
        Info_ = std::move(errorInfo);
        InfoViewer_ = [](std::any& any) -> IErrorInfo* {
            return std::any_cast<T>(&any);
        };
    }

    /// Метод записывает в IOutputStream текст ошибки и текст типизорованной информации об ошибке
    /// \param out поток, куда производиться запись
    void Print(IOutputStream& out) const override;

    /// Метод позволяет получить типизированную информацию об ошибке.
    /// \return возвращает nullptr, если данных нет
    template<typename T>
    requires std::is_base_of_v<IErrorInfo, T>
    T* GetInfo() {
        if (!InfoViewer_) {
            return nullptr;
        }
        return static_cast<T*>(InfoViewer_(Info_));
    }

  private:
    TString Message_;
    std::any Info_;
    TViewer InfoViewer_ = nullptr;
};
}

template<>
class NPrivate::SuccessCastErrorMessageGetter<NHttp::TError> {
  public:
    static TString GetMessage(const NHttp::TError* const error);
};
