///|
/// JavaScript Runtime Module - Executes JS with DOM bindings
///
/// MVP Implementation using Node.js vm module (JS target only)
/// Will be replaced with QuickJS later

///|
/// Error types for JS execution
/// Note: Some variants are reserved for future use
pub suberror JsError {
  // Reserved for future use when unsupported operations are detected
  NotSupported
  ExecutionError(String)
  // Reserved for future use when parsing JS syntax fails
  SyntaxError(String)
  // Reserved for future use when undefined references are detected
  ReferenceError(String)
}

///|
pub impl Show for JsError with fn output(self, logger) {
  match self {
    NotSupported => logger.write_string("JsError::NotSupported")
    ExecutionError(msg) => {
      logger.write_string("JsError::ExecutionError(")
      logger.write_string(msg)
      logger.write_string(")")
    }
    SyntaxError(msg) => {
      logger.write_string("JsError::SyntaxError(")
      logger.write_string(msg)
      logger.write_string(")")
    }
    ReferenceError(msg) => {
      logger.write_string("JsError::ReferenceError(")
      logger.write_string(msg)
      logger.write_string(")")
    }
  }
}

///|
/// Result of JS execution
pub struct JsResult {
  /// Return value (JSON serialized)
  value : String
  /// Console logs captured during execution
  logs : Array[String]
  /// Whether execution succeeded
  success : Bool
}

///|
pub(all) enum AsyncExecutionMode {
  ImmediateFlush
  DeferredFlush
}

///|
/// Create a new JS result value
pub fn JsResult::new(
  value : String,
  logs : Array[String],
  success : Bool,
) -> JsResult {
  { value, logs, success }
}

///|
/// Create an ExecutionError (for use by external JsRuntime implementations).
pub fn JsError::execution_error(msg : String) -> JsError {
  ExecutionError(msg)
}

///|
/// JavaScript runtime abstraction
pub(open) trait JsRuntime {
  /// Initialize runtime if needed (idempotent)
  fn init(Self) -> Unit

  /// Execute JS against DomTree and return result
  fn execute(Self, Int, @dom.DomTree, String, AsyncExecutionMode) -> JsResult raise JsError

  /// Run one pending async step for an existing context
  fn tick(Self, Int, @dom.DomTree) -> JsResult raise JsError
}

///|
/// JavaScript execution context bound to a DomTree
pub struct JsContext {
  /// Stable runtime context id for persistent VM backends
  context_id : Int
  /// Async flush policy for execution
  async_mode : AsyncExecutionMode
  /// The DOM tree this context operates on
  dom : @dom.DomTree
  /// Console log buffer
  mut logs : Array[String]
  /// Runtime implementation
  runtime : &JsRuntime
}

///|
/// Create a new JS execution context with a DOM tree
pub fn JsContext::new(dom : @dom.DomTree) -> JsContext {
  {
    context_id: allocate_runtime_context_id(),
    async_mode: ImmediateFlush,
    dom,
    logs: [],
    runtime: default_js_runtime(),
  }
}

///|
pub fn JsContext::new_deferred(dom : @dom.DomTree) -> JsContext {
  {
    context_id: allocate_runtime_context_id(),
    async_mode: DeferredFlush,
    dom,
    logs: [],
    runtime: default_js_runtime(),
  }
}

///|
/// Create a new JS execution context with explicit runtime
pub fn JsContext::new_with_runtime(
  dom : @dom.DomTree,
  runtime : &JsRuntime,
) -> JsContext {
  {
    context_id: allocate_runtime_context_id(),
    async_mode: ImmediateFlush,
    dom,
    logs: [],
    runtime,
  }
}

///|
pub fn JsContext::new_with_runtime_and_mode(
  dom : @dom.DomTree,
  runtime : &JsRuntime,
  async_mode : AsyncExecutionMode,
) -> JsContext {
  {
    context_id: allocate_runtime_context_id(),
    async_mode,
    dom,
    logs: [],
    runtime,
  }
}

///|
/// Get the DOM tree from context
pub fn JsContext::get_dom(self : JsContext) -> @dom.DomTree {
  self.dom
}

///|
/// Clear console logs
pub fn JsContext::clear_logs(self : JsContext) -> Unit {
  self.logs = []
}

///|
/// Get captured console logs
pub fn JsContext::get_logs(self : JsContext) -> Array[String] {
  self.logs
}

///|
/// Execute JavaScript code with runtime abstraction
pub fn JsContext::execute(
  self : JsContext,
  code : String,
) -> JsResult raise JsError {
  self.runtime.init()
  let result = self.runtime.execute(
    self.context_id,
    self.dom,
    code,
    self.async_mode,
  )
  for log in result.logs {
    self.logs.push(log)
  }
  if !result.success {
    raise ExecutionError(result.value)
  }
  result
}

///|
pub fn JsContext::tick(self : JsContext) -> Bool raise JsError {
  self.runtime.init()
  let result = self.runtime.tick(self.context_id, self.dom)
  for log in result.logs {
    self.logs.push(log)
  }
  if !result.success {
    raise ExecutionError(result.value)
  }
  result.value == "true"
}