#pragma once

#include <Core/fig_string.hpp>

#include <exception>
#include <format>
#include <source_location>
#include <string>
#include <vector>

namespace Fig
{
    class AddressableError : public std::exception
    {
    public:
        explicit AddressableError() {}
        explicit AddressableError(FString _msg,
                                  size_t _line,
                                  size_t _column,
                                  FString _sourcePath,
                                  std::vector<FString> _sourceLines,
                                  std::source_location loc = std::source_location::current()) :
            src_loc(loc), line(_line), column(_column), sourcePath(std::move(_sourcePath)), sourceLines(std::move(_sourceLines))
        {
            message = _msg;
        }
        virtual FString toString() const
        {
            std::string msg = std::format("[AddressableError] {} at {}:{}, in [{}] {}", std::string(this->message.begin(), this->message.end()), this->line, this->column, this->src_loc.file_name(), this->src_loc.function_name());
            return FString(msg);
        }
        const char *what() const noexcept override
        {
            static std::string msg = toString().toBasicString();
            return msg.c_str();
        }
        std::source_location src_loc;

        virtual size_t getLine() const { return line; }
        virtual size_t getColumn() const { return column; }
        FString getMessage() const { return message; }
        FString getSourcePath() const { return sourcePath; }
        std::vector<FString> getSourceLines() const { return sourceLines; }

        virtual FString getErrorType() const
        {
            return FString(u8"AddressableError");
        }

    protected:
        size_t line, column;
        FString message;

        FString sourcePath;
        std::vector<FString> sourceLines;
    };

    class UnaddressableError : public std::exception
    {
    public:
        explicit UnaddressableError() {}
        explicit UnaddressableError(FString _msg,
                                    std::source_location loc = std::source_location::current()) :
            src_loc(loc)
        {
            message = _msg;
        }
        virtual FString toString() const
        {
            std::string msg = std::format("[UnaddressableError] {} in [{}] {}", this->message.toBasicString(),  this->src_loc.file_name(), this->src_loc.function_name());
            return FString(msg);
        }
        const char *what() const noexcept override
        {
            static std::string msg = toString().toBasicString();
            return msg.c_str();
        }
        std::source_location src_loc;
        FString getMessage() const { return message; }

        virtual FString getErrorType() const
        {
            return FString(u8"UnaddressableError");
        }

    protected:
        FString message;
    };

    class SyntaxError : public AddressableError
    {
    public:
        using AddressableError::AddressableError;

        virtual FString toString() const override
        {
            std::string msg = std::format("[SyntaxError] {} in [{}] {}", this->message.toBasicString(), this->src_loc.file_name(), this->src_loc.function_name());
            return FString(msg);
        }

        virtual FString getErrorType() const override
        {
            return FString(u8"SyntaxError");
        }
    };

    class RuntimeError final : public UnaddressableError
    {
    public:
        using UnaddressableError::UnaddressableError;
        explicit RuntimeError(FString _msg,
                             std::source_location loc = std::source_location::current()) :
            UnaddressableError(_msg, loc)
        {
        }
        virtual FString toString() const override
        {
            std::string msg = std::format("[RuntimeError] {} in [{}] {}", this->message.toBasicString(), this->src_loc.file_name(), this->src_loc.function_name());
            return FString(msg);
        }

        virtual FString getErrorType() const override
        {
            return FString(u8"RuntimeError");
        }
    };
    
} // namespace Fig