#pragma once

#include <cstddef>
#include <map>
#include <stdexcept>
#include <string>
#include <variant>
#include <vector>

namespace dps {

class Json {
public:
    using array_t = std::vector<Json>;
    using object_t = std::map<std::string, Json>;
    using value_t = std::variant<std::nullptr_t, bool, double, std::string, array_t, object_t>;

    Json();
    Json(std::nullptr_t);
    Json(bool value);
    Json(int value);
    Json(long long value);
    Json(double value);
    Json(const char *value);
    Json(const std::string &value);
    Json(std::string &&value);
    Json(const array_t &value);
    Json(array_t &&value);
    Json(const object_t &value);
    Json(object_t &&value);

    static Json parse(const std::string &text);
    static Json object();
    static Json array();

    bool is_null() const;
    bool is_bool() const;
    bool is_number() const;
    bool is_string() const;
    bool is_array() const;
    bool is_object() const;

    bool as_bool() const;
    double as_number() const;
    int as_int() const;
    long long as_int64() const;
    const std::string &as_string() const;
    const array_t &as_array() const;
    const object_t &as_object() const;
    array_t &as_array();
    object_t &as_object();

    bool contains(const std::string &key) const;
    const Json &at(const std::string &key) const;
    Json &operator[](const std::string &key);
    const Json &operator[](const std::string &key) const;

    const Json &at(std::size_t index) const;
    Json &at(std::size_t index);
    void push_back(const Json &value);
    std::size_t size() const;

    std::string dump() const;
    std::string to_string_or(const std::string &fallback) const;
    int to_int_or(int fallback) const;
    bool to_bool_or(bool fallback) const;

private:
    value_t value_;
};

}  // namespace dps
