#pragma once

#include <cstdint>
#include <string>
#include <utility>
#include <vector>

// Minimal JSON document model for the update-flow decision layer.
//
// Deliberately mirrors JavaScript object semantics where the ported decision
// logic depends on them, because the golden vectors are generated by the TS
// reference implementation and compared as serialized strings:
//  - object members keep insertion order, and overwriting an existing key
//    keeps its original position (JS spread/assignment semantics);
//  - Kind::Undefined is distinct from Kind::Null: undefined-valued members
//    are skipped by Stringify (like JSON.stringify), null is emitted;
//  - Truthy() implements JS truthiness (undefined/null/false/0/NaN/'' are
//    falsy; empty arrays and objects are truthy);
//  - StrictEquals() implements === for primitives only (never for
//    arrays/objects — reference equality cannot hold across a parse).
namespace flowjson {

class Value;
using Members = std::vector<std::pair<std::string, Value>>;
using Elements = std::vector<Value>;

class Value {
 public:
  enum class Kind { Undefined, Null, Bool, Number, String, Array, Object };

  Value() = default;
  static Value Undefined() { return Value(); }
  static Value Null() { return Value(Kind::Null); }
  static Value Bool(bool b) {
    Value v(Kind::Bool);
    v.bool_ = b;
    return v;
  }
  static Value Number(double n) {
    Value v(Kind::Number);
    v.number_ = n;
    return v;
  }
  static Value String(std::string s) {
    Value v(Kind::String);
    v.string_ = std::move(s);
    return v;
  }
  static Value Array() { return Value(Kind::Array); }
  static Value Object() { return Value(Kind::Object); }

  Kind kind() const { return kind_; }
  bool IsUndefined() const { return kind_ == Kind::Undefined; }
  bool IsArray() const { return kind_ == Kind::Array; }
  bool IsObject() const { return kind_ == Kind::Object; }
  bool IsBool() const { return kind_ == Kind::Bool; }
  bool IsNumber() const { return kind_ == Kind::Number; }
  bool IsString() const { return kind_ == Kind::String; }

  bool AsBool() const { return bool_; }
  double AsNumber() const { return number_; }
  const std::string& AsString() const { return string_; }

  bool Truthy() const;

  // Array access.
  const Elements& elements() const { return elements_; }
  void Push(Value v) { elements_.push_back(std::move(v)); }
  size_t Size() const { return elements_.size(); }
  const Value& At(size_t i) const;

  // Object access. Get returns Undefined for a missing key; Set overwrites
  // in place (keeping the key's position) or appends.
  const Members& members() const { return members_; }
  const Value& Get(const std::string& key) const;
  void Set(const std::string& key, Value v);
  void Remove(const std::string& key);

  static bool StrictEquals(const Value& a, const Value& b);

 private:
  explicit Value(Kind kind) : kind_(kind) {}

  Kind kind_ = Kind::Undefined;
  bool bool_ = false;
  double number_ = 0;
  std::string string_;
  Elements elements_;
  Members members_;
};

// JSON.stringify-compatible for the value shapes the decision layer produces
// (undefined members skipped, undefined array elements become null, integral
// numbers within the double-safe range print without a decimal point). A
// top-level Undefined prints as "undefined" so two undefined results compare
// equal.
std::string Stringify(const Value& v);

// Hard limits for Parse. The TS reference has none (JSON.parse is unbounded)
// and the divergence is deliberate: this parser runs inside the native
// orchestrators on the raw checkUpdate body, and with sizeof(Value) ~ 96 a
// 1 MB "[0,0,...]" balloons to ~100 MB of heap. A response over either cap is
// treated as malformed (HandleCheckResponse -> invalidResponse). Real
// responses are a few KB and a few hundred nodes.
constexpr size_t kMaxInputBytes = 1024 * 1024;
constexpr size_t kMaxNodes = 65536;

// Strict JSON parser (the vectors file and checkUpdate responses). Returns
// Undefined and sets *ok to false on malformed input; nesting beyond 64
// levels, more than kMaxInputBytes of text or more than kMaxNodes values are
// rejected so hostile server data cannot exhaust the stack or the heap.
// Number parsing/printing is locale-independent (never strtod/LC_NUMERIC).
// Lone surrogate escapes decode to U+FFFD so the output is always valid UTF-8.
Value Parse(const std::string& text, bool* ok);

}  // namespace flowjson
