#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <variant>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <memory>

namespace json {

class Value;
using Object = std::unordered_map<std::string, Value>;
using Array  = std::vector<Value>;

class Value {
public:
    enum Type { Null, Bool, Number, String, ArrayT, ObjectT };

    Value() : type_(Null) {}
    Value(bool b) : type_(Bool), bool_(b) {}
    Value(double n) : type_(Number), number_(n) {}
    Value(int n) : type_(Number), number_(n) {}
    Value(const std::string& s) : type_(String), string_(s) {}
    Value(const char* s) : type_(String), string_(s) {}
    Value(const Array& a) : type_(ArrayT), array_(a) {}
    Value(const Object& o) : type_(ObjectT), object_(o) {}

    Type type() const { return type_; }
    bool is_null()   const { return type_ == Null; }
    bool is_bool()   const { return type_ == Bool; }
    bool is_number() const { return type_ == Number; }
    bool is_string() const { return type_ == String; }
    bool is_array()  const { return type_ == ArrayT; }
    bool is_object() const { return type_ == ObjectT; }

    bool         as_bool()   const { return bool_; }
    double       as_number() const { return number_; }
    int          as_int()    const { return (int)number_; }
    float        as_float()  const { return (float)number_; }
    const std::string& as_string() const { return string_; }
    const Array&  as_array()  const { return array_; }
    const Object& as_object() const { return object_; }

    // Object access
    const Value& operator[](const std::string& key) const {
        static Value null_val;
        auto it = object_.find(key);
        return (it != object_.end()) ? it->second : null_val;
    }

    // Array access
    const Value& operator[](size_t index) const {
        static Value null_val;
        return (index < array_.size()) ? array_[index] : null_val;
    }

    size_t size() const {
        if (type_ == ArrayT)  return array_.size();
        if (type_ == ObjectT) return object_.size();
        return 0;
    }

    bool has(const std::string& key) const {
        return type_ == ObjectT && object_.count(key) > 0;
    }

    // Get with default
    std::string get_string(const std::string& key, const std::string& def = "") const {
        if (has(key) && object_.at(key).is_string()) return object_.at(key).as_string();
        return def;
    }
    double get_number(const std::string& key, double def = 0.0) const {
        if (has(key) && object_.at(key).is_number()) return object_.at(key).as_number();
        return def;
    }
    float get_float(const std::string& key, float def = 0.0f) const {
        return (float)get_number(key, def);
    }
    int get_int(const std::string& key, int def = 0) const {
        return (int)get_number(key, def);
    }
    bool get_bool(const std::string& key, bool def = false) const {
        if (has(key) && object_.at(key).is_bool()) return object_.at(key).as_bool();
        return def;
    }

private:
    Type type_ = Null;
    bool bool_ = false;
    double number_ = 0.0;
    std::string string_;
    Array array_;
    Object object_;
};

// ---- Parser ----
class Parser {
public:
    static Value parse(const std::string& input) {
        Parser p(input);
        Value v = p.parseValue();
        p.skipWhitespace();
        return v;
    }

    static Value parseFile(const std::string& path) {
        std::ifstream f(path);
        if (!f.is_open())
            throw std::runtime_error("JSON: cannot open file: " + path);
        std::stringstream ss;
        ss << f.rdbuf();
        return parse(ss.str());
    }

private:
    const std::string& src_;
    size_t pos_ = 0;

    Parser(const std::string& s) : src_(s) {}

    char peek() const { return pos_ < src_.size() ? src_[pos_] : '\0'; }
    char advance() { return pos_ < src_.size() ? src_[pos_++] : '\0'; }

    void skipWhitespace() {
        while (pos_ < src_.size()) {
            char c = src_[pos_];
            if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { ++pos_; continue; }
            // Skip // line comments
            if (c == '/' && pos_ + 1 < src_.size() && src_[pos_+1] == '/') {
                pos_ += 2;
                while (pos_ < src_.size() && src_[pos_] != '\n') ++pos_;
                continue;
            }
            // Skip /* block comments */
            if (c == '/' && pos_ + 1 < src_.size() && src_[pos_+1] == '*') {
                pos_ += 2;
                while (pos_ + 1 < src_.size() && !(src_[pos_] == '*' && src_[pos_+1] == '/')) ++pos_;
                pos_ += 2;
                continue;
            }
            break;
        }
    }

    void expect(char c) {
        skipWhitespace();
        if (advance() != c)
            throw std::runtime_error(std::string("JSON: expected '") + c + "' at pos " + std::to_string(pos_-1));
    }

    Value parseValue() {
        skipWhitespace();
        char c = peek();
        if (c == '"') return parseString();
        if (c == '{') return parseObject();
        if (c == '[') return parseArray();
        if (c == 't' || c == 'f') return parseBool();
        if (c == 'n') return parseNull();
        if (c == '-' || (c >= '0' && c <= '9')) return parseNumber();
        throw std::runtime_error(std::string("JSON: unexpected char '") + c + "' at pos " + std::to_string(pos_));
    }

    Value parseString() {
        expect('"');
        std::string result;
        while (pos_ < src_.size()) {
            char c = advance();
            if (c == '"') return Value(result);
            if (c == '\\') {
                char esc = advance();
                switch (esc) {
                    case '"':  result += '"';  break;
                    case '\\': result += '\\'; break;
                    case '/':  result += '/';  break;
                    case 'n':  result += '\n'; break;
                    case 't':  result += '\t'; break;
                    case 'r':  result += '\r'; break;
                    case 'b':  result += '\b'; break;
                    case 'f':  result += '\f'; break;
                    case 'u': {
                        // Basic \uXXXX — ASCII range only
                        std::string hex;
                        for (int i = 0; i < 4; ++i) hex += advance();
                        int cp = std::stoi(hex, nullptr, 16);
                        if (cp < 128) result += (char)cp;
                        break;
                    }
                    default: result += esc; break;
                }
            } else {
                result += c;
            }
        }
        throw std::runtime_error("JSON: unterminated string");
    }

    Value parseNumber() {
        size_t start = pos_;
        if (peek() == '-') advance();
        while (peek() >= '0' && peek() <= '9') advance();
        if (peek() == '.') { advance(); while (peek() >= '0' && peek() <= '9') advance(); }
        if (peek() == 'e' || peek() == 'E') {
            advance();
            if (peek() == '+' || peek() == '-') advance();
            while (peek() >= '0' && peek() <= '9') advance();
        }
        return Value(std::stod(src_.substr(start, pos_ - start)));
    }

    Value parseBool() {
        if (src_.compare(pos_, 4, "true") == 0) { pos_ += 4; return Value(true); }
        if (src_.compare(pos_, 5, "false") == 0) { pos_ += 5; return Value(false); }
        throw std::runtime_error("JSON: invalid bool at pos " + std::to_string(pos_));
    }

    Value parseNull() {
        if (src_.compare(pos_, 4, "null") == 0) { pos_ += 4; return Value(); }
        throw std::runtime_error("JSON: invalid null at pos " + std::to_string(pos_));
    }

    Value parseArray() {
        expect('[');
        Array arr;
        skipWhitespace();
        if (peek() == ']') { advance(); return Value(arr); }
        while (true) {
            arr.push_back(parseValue());
            skipWhitespace();
            if (peek() == ',') { advance(); continue; }
            break;
        }
        expect(']');
        return Value(arr);
    }

    Value parseObject() {
        expect('{');
        Object obj;
        skipWhitespace();
        if (peek() == '}') { advance(); return Value(obj); }
        while (true) {
            skipWhitespace();
            Value key = parseString();
            expect(':');
            obj[key.as_string()] = parseValue();
            skipWhitespace();
            if (peek() == ',') { advance(); continue; }
            break;
        }
        expect('}');
        return Value(obj);
    }
};

// Convenience
inline Value parse(const std::string& input) { return Parser::parse(input); }
inline Value parseFile(const std::string& path) { return Parser::parseFile(path); }

} // namespace json
