// models.mbt — data models (mirrors Python `models.py`)
//
// Translates MemoryType / MemoryStatus enums and the MemoryEntry dataclass
// into MoonBit. Timestamps are kept as epoch milliseconds (Int64) rather than
// ISO-8601 strings so the system stays dependency-free; the on-disk JSON
// shape (same field names, same semantics) is otherwise identical.

///|
/// Memory categories. Mirrors Python `MemoryType(str, Enum)`.
pub enum MemoryType {
  CommonSense
  Instruction
  Preference
  Fact
  Other
} derive(Debug, Eq)

///|
pub fn MemoryType::value(self : MemoryType) -> String {
  match self {
    CommonSense => "common_sense"
    Instruction => "instruction"
    Preference => "preference"
    Fact => "fact"
    Other => "other"
  }
}

///|
pub fn MemoryType::from_str(s : String) -> MemoryType {
  match s {
    "common_sense" => CommonSense
    "instruction" => Instruction
    "preference" => Preference
    "fact" => Fact
    _ => Other
  }
}

///|
/// Memory lifecycle status. Mirrors Python `MemoryStatus(str, Enum)`.
pub enum MemoryStatus {
  Active
  Deprecated
  Archived
} derive(Debug, Eq)

///|
pub fn MemoryStatus::value(self : MemoryStatus) -> String {
  match self {
    Active => "active"
    Deprecated => "deprecated"
    Archived => "archived"
  }
}

///|
pub fn MemoryStatus::from_str(s : String) -> MemoryStatus {
  match s {
    "deprecated" => Deprecated
    "archived" => Archived
    _ => Active
  }
}

///|
/// All memory types, used for storage directory layout and index categories.
pub fn all_types() -> Array[MemoryType] {
  [CommonSense, Instruction, Preference, Fact, Other]
}

///|
/// A single memory entry. Mirrors Python `MemoryEntry`.
///
/// Field `mtype` maps to JSON key `"type"` (the word `type` is a MoonBit
/// keyword, so the struct field is named `mtype` and serialization renames it).
pub struct MemoryEntry {
  id : String
  mtype : String
  content : String
  confidence : Double
  source : String
  timestamp : Int64
  access_count : Int
  priority : Double
  status : String
  last_accessed : Int64
  feedback_score : Double
  tags : Array[String]
} derive(Debug)

///|
let _id_counter : @ref.Ref[Int64] = @ref.Ref(0L)

///|
/// Current time in epoch milliseconds (UTC).
pub fn now_ms() -> Int64 {
  @env.now().reinterpret_as_int64()
}

///|
/// Generate a unique entry id.
///
/// Python's original uses `"mem_" + uuid4().hex[:12]` (random). We use a
/// time + monotonic-counter scheme that is equally unique and keeps the port
/// dependency-free (no UUID package needed). Tests only rely on uniqueness
/// and the `"mem_"` prefix, both of which this satisfies.
fn gen_id() -> String {
  let c = _id_counter.val
  _id_counter.val = c + 1L
  "mem_" + now_ms().to_string() + "_" + c.to_string()
}

///|
/// Create a new entry (mirrors `MemoryEntry.create`).
pub fn MemoryEntry::create(
  content : String,
  mtype : MemoryType,
  source? : String = "user_input",
  confidence? : Double = 0.8,
  tags? : Array[String] = [],
) -> MemoryEntry {
  let now = now_ms()
  {
    id: gen_id(),
    mtype: mtype.value(),
    content,
    confidence,
    source,
    timestamp: now,
    access_count: 0,
    priority: 0.5,
    status: MemoryStatus::Active.value(),
    last_accessed: now,
    feedback_score: 0.0,
    tags,
  }
}

///|
/// Serialize an entry to a JSON value (key `"type"` is emitted for `mtype`).
pub fn MemoryEntry::to_json(self : MemoryEntry) -> Json {
  let m : Map[String, Json] = Map([], capacity=16)
  m.set("id", Json::string(self.id))
  m.set("type", Json::string(self.mtype))
  m.set("content", Json::string(self.content))
  m.set("confidence", Json::number(self.confidence))
  m.set("source", Json::string(self.source))
  m.set("timestamp", Json::number(self.timestamp.to_double()))
  m.set("access_count", Json::number(self.access_count.to_double()))
  m.set("priority", Json::number(self.priority))
  m.set("status", Json::string(self.status))
  m.set("last_accessed", Json::number(self.last_accessed.to_double()))
  m.set("feedback_score", Json::number(self.feedback_score))
  let tags_json : Array[Json] = self.tags.map(fn(t) { Json::string(t) })
  m.set("tags", Json::array(tags_json))
  Json::object(m)
}

///|
/// Parse an entry from a JSON value. Returns `None` on malformed input.
pub fn MemoryEntry::from_json(j : Json) -> MemoryEntry? {
  match j {
    Object(m) => {
      let id = match m.get("id") {
        Some(String(s)) => s
        _ => return None
      }
      let mtype = match m.get("type") {
        Some(String(s)) => s
        _ => return None
      }
      let content = match m.get("content") {
        Some(String(s)) => s
        _ => return None
      }
      let source = match m.get("source") {
        Some(String(s)) => s
        _ => "user_input"
      }
      let confidence = match m.get("confidence") {
        Some(Number(n, ..)) => n
        _ => 0.8
      }
      let timestamp = match m.get("timestamp") {
        Some(Number(n, ..)) => n.to_int64()
        _ => 0L
      }
      let access_count = match m.get("access_count") {
        Some(Number(n, ..)) => n.to_int64().to_int()
        _ => 0
      }
      let priority = match m.get("priority") {
        Some(Number(n, ..)) => n
        _ => 0.5
      }
      let status = match m.get("status") {
        Some(String(s)) => s
        _ => "active"
      }
      let last_accessed = match m.get("last_accessed") {
        Some(Number(n, ..)) => n.to_int64()
        _ => 0L
      }
      let feedback_score = match m.get("feedback_score") {
        Some(Number(n, ..)) => n
        _ => 0.0
      }
      let tags : Array[String] = match m.get("tags") {
        Some(Array(a)) =>
          a.map(fn(x) {
            match x {
              String(s) => s
              _ => ""
            }
          })
        _ => []
      }
      Some({
        id,
        mtype,
        content,
        confidence,
        source,
        timestamp,
        access_count,
        priority,
        status,
        last_accessed,
        feedback_score,
        tags,
      })
    }
    _ => None
  }
}