// Copyright (c) 2024 LinZeming
// Released under the MIT License
//
// Context — nested-scope variable storage for template rendering.
//
// Contexts form a linked list (via `parent`).  Variable lookup walks the
// chain from innermost to outermost scope.  `set` always writes to the
// current (innermost) scope.

// ---------------------------------------------------------------------------
// Macro definition type
// ---------------------------------------------------------------------------

///|
/// A macro definition — stored in the rendering context.
pub(all) struct MacroDef {
  name : String
  params : Array[String]
  body : Array[Node]
} derive(Debug)

// ---------------------------------------------------------------------------
// Context struct
// ---------------------------------------------------------------------------

///|
/// A rendering context — stores variables and macros in a nested-scope chain.
pub(all) struct Context {
  data : Map[String, Value]
  macros : Map[String, MacroDef]
  parent : Option[Context]
} derive(Debug)

// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------

///|
/// Create an empty root context.
pub fn Context::new() -> Context {
  {
    data: Map::new(),
    macros: Map::new(),
    parent: Option::None,
  }
}

///|
/// Create a child context that delegates lookups to *parent*.
pub fn Context::new_child(parent : Context) -> Context {
  {
    data: Map::new(),
    macros: Map::new(),
    parent: Option::Some(parent),
  }
}

// ---------------------------------------------------------------------------
// Variable access
// ---------------------------------------------------------------------------

///|
/// Check whether *name* is defined in **this** scope (does not recurse).
fn Context::has_own(self : Context, name : String) -> Bool {
  for key in self.data.keys() {
    if key == name {
      return true
    }
  }
  false
}

///|
/// Look up *name* in the scope chain (current → parent → …).
pub fn Context::get(self : Context, name : String) -> Option[Value] {
  if self.has_own(name) {
    Option::Some(self.data[name])
  } else {
    match self.parent {
      Option::Some(p) => p.get(name)
      Option::None => Option::None
    }
  }
}

///|
/// Set *name* = *value* in the **current** scope (does not affect parent).
pub fn Context::set(self : Context, name : String, value : Value) -> Context {
  let data = self.data
  data[name] = value
  { ..self, data }
}

// ---------------------------------------------------------------------------
// Macro storage
// ---------------------------------------------------------------------------

///|
/// Register a macro definition in the current scope.
pub fn Context::register_macro(self : Context, m : MacroDef) -> Context {
  let macros = self.macros
  macros[m.name] = m
  { ..self, macros }
}

///|
/// Look up a macro by name, walking the scope chain.
pub fn Context::get_macro(self : Context, name : String) -> MacroDef? {
  for key in self.macros.keys() {
    if key == name {
      return Some(self.macros[key])
    }
  }
  match self.parent {
    Option::Some(p) => p.get_macro(name)
    Option::None => None
  }
}