// Cedar runtime types — the type system and value representations.
// References:
//   Rust: cedar-policy-core/src/ast/ (Type, ValueKind, EntityUID)
//   Go:   cedar-go/types/ (Value interface, concrete types)

// ---------------------------------------------------------------------------
// Identifiers & Names
// ---------------------------------------------------------------------------

///|
/// A unique entity identifier, e.g. `User::"alice"`.
pub(all) struct EntityUID {
  type_ : String
  id : String
} derive(Debug, Eq, Hash)

///|
/// An entity type name, e.g. `User`, `Photo`.
pub(all) struct EntityType(String) derive(Debug, Eq, ToJson, FromJson)

///|
/// A namespaced path for extension functions, e.g. `ip`, `decimal`.
pub(all) struct Name {
  ns : Array[String]
  name : String
} derive(Debug, Eq, ToJson, FromJson)

///|
/// Source position for diagnostics / error messages.
pub(all) struct Position {
  filename : String // optional source file name, "" if unknown
  offset : Int // byte offset, starting at 0
  line : Int // line number, starting at 1
  column : Int // column number, starting at 1
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Pattern (for the `like` operator)
// ---------------------------------------------------------------------------

///|
/// Pattern for the `like` operator (IAM-style `StringLike`).
/// The wildcard `*` matches any string; `\*` matches a literal `*`.
pub(all) struct Pattern {
  elements : Array[PatternElem]
} derive(Debug, Eq)

///|
pub(all) enum PatternElem {
  Char(Char)
  Wildcard
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Type — the Cedar type system (Rust approach)
// ---------------------------------------------------------------------------

///|
/// The runtime type of a Cedar value.
/// Two entity types are equal iff they have the same Name.
/// Two extension types are equal iff they have the same Name.
pub(all) enum Type {
  Bool
  Long
  String
  Set
  Record
  Entity(EntityType)
  Extension(Name)
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Value — all runtime values (Rust ValueKind, flattened)
// ---------------------------------------------------------------------------

///|
/// All values that can be the dynamic result of evaluating an Expr.
/// Includes extension values (ip, decimal, datetime, duration, etc.)
/// as opaque Extension(Name, String) entries for MVP.
pub(all) enum Value {
  Bool(Bool)
  Long(Int64)
  String(String)
  EntityUID(EntityUID)
  Set(Array[Value])
  Record(Map[String, Value])
  Extension(Name, String) // opaque: (fn_name, serialized_arg)
} derive(Debug, Eq, ToJson, FromJson)

// ---------------------------------------------------------------------------
// PartialValue — unified partial evaluation value
// ---------------------------------------------------------------------------

///|
/// Result of evaluating an expression — either a concrete value or a residual
/// expression that could not be reduced (partial evaluation).
///
/// Also used as the value type in Entity attrs/tags, because entity attributes
/// and tags can be unknown during partial evaluation (source #5 of Unknown).
pub(all) enum PartialValue {
  Value(Value)
  Residual(Expr)
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Entity — application data for evaluation (attrs + tags + parents)
// ---------------------------------------------------------------------------

///|
/// An entity as provided by the application. Contains attributes (schema-defined),
/// tags (free-form), and parent relationships for hierarchy traversal (`in` operator).
pub(all) struct Entity {
  uid : EntityUID
  attrs : Map[String, Value]
  tags : Map[String, Value]
  parents : Array[EntityUID]
} derive(Debug, Eq)

///|
/// Serialize an Entity to JSON.
pub impl ToJson for Entity with fn to_json(self) -> Json {
  let attrs_obj : Map[String, Json] = Map([])
  for key, v in self.attrs {
    attrs_obj.set(key, v.to_json())
  }
  let tags_obj : Map[String, Json] = Map([])
  for key, v in self.tags {
    tags_obj.set(key, v.to_json())
  }
  Json::object(
    Map([
      ("uid", self.uid.to_json()),
      ("attrs", Json::object(attrs_obj)),
      ("tags", Json::object(tags_obj)),
      ("parents", Json::array(self.parents.map(fn(p) { p.to_json() }))),
    ]),
  )
}

///|
/// Deserialize an Entity from JSON.
pub impl FromJson for Entity with fn from_json(
  json : Json,
  path : @json.JsonPath,
) -> Entity raise @json.JsonDecodeError {
  match json {
    Json::Object(obj) => {
      let uid = match obj.get("uid") {
        Some(uid_json) => EntityUID::from_json(uid_json, path.add_key("uid"))
        None => raise @json.JsonDecodeError((path, "missing 'uid' in entity"))
      }
      let attrs = match obj.get("attrs") {
        Some(Json::Object(m)) => {
          let attrs : Map[String, Value] = Map([])
          for key, val in m {
            attrs.set(
              key,
              Value::from_json(val, path.add_key("attrs").add_key(key)),
            )
          }
          attrs
        }
        None => Map([])
        Some(_) =>
          raise @json.JsonDecodeError(
            (path.add_key("attrs"), "expected object"),
          )
      }
      let tags = match obj.get("tags") {
        Some(Json::Object(m)) => {
          let tags : Map[String, Value] = Map([])
          for key, val in m {
            tags.set(
              key,
              Value::from_json(val, path.add_key("tags").add_key(key)),
            )
          }
          tags
        }
        None => Map([])
        Some(_) =>
          raise @json.JsonDecodeError((path.add_key("tags"), "expected object"))
      }
      let parents = match obj.get("parents") {
        Some(Json::Array(parents_arr)) => {
          let parents : Array[EntityUID] = []
          for j = 0; j < parents_arr.length(); j = j + 1 {
            parents.push(
              EntityUID::from_json(
                parents_arr[j],
                path.add_key("parents").add_index(j),
              ),
            )
          }
          parents
        }
        Some(_) =>
          raise @json.JsonDecodeError(
            (path.add_key("parents"), "expected array"),
          )
        None => []
      }
      Entity::{ uid, attrs, tags, parents }
    }
    _ => raise @json.JsonDecodeError((path, "expected JSON object for entity"))
  }
}

// (Request, Decision, Diagnostic* types moved to evaluator/request.mbt)

// ---------------------------------------------------------------------------
// EntityUID JSON: map type_ (MoonBit keyword workaround) ↔ "type" (Cedar JSON)
// ---------------------------------------------------------------------------

///|
/// Serialize EntityUID as Cedar-compatible JSON: { "type": "...", "id": "..." }.
pub impl ToJson for EntityUID with fn to_json(self) -> Json {
  Json::object(
    Map([("type", Json::string(self.type_)), ("id", Json::string(self.id))]),
  )
}

///|
/// Deserialize from Cedar JSON entity UID format.
pub impl FromJson for EntityUID with fn from_json(
  json : Json,
  path : @json.JsonPath,
) -> EntityUID raise @json.JsonDecodeError {
  match json {
    Json::Object(fields) =>
      match (fields.get("type"), fields.get("id")) {
        (Some(Json::String(type_s)), Some(Json::String(id_s))) =>
          EntityUID::{ type_: type_s, id: id_s }
        _ => {
          let payload : (@json.JsonPath, String) = (
            path, "expected {type, id} for EntityUID",
          )
          raise @json.JsonDecodeError(payload)
        }
      }
    _ => {
      let payload : (@json.JsonPath, String) = (
        path, "expected object for EntityUID",
      )
      raise @json.JsonDecodeError(payload)
    }
  }
}