///| FFI exports for JS/WASM targets

///| Document handles are opaque integers that reference internal state

// =============================================================================
// Internal State Management
// =============================================================================

///|
/// Stored document with source for incremental parsing
priv struct StoredDocument {
  result : ParseResult
  source : String
}

///|
/// Global document storage (handle -> StoredDocument)
let documents : Map[Int, StoredDocument] = {}

///|
/// Next available handle ID (using Ref for mutability at top level)
let next_handle : Ref[Int] = Ref::new(1)

///|
/// Allocate a new handle for a document
fn alloc_handle(result : ParseResult, source : String) -> Int {
  let handle = next_handle.val
  next_handle.val = handle + 1
  documents[handle] = { result, source }
  handle
}

///|
/// Get stored document by handle
fn get_stored(handle : Int) -> StoredDocument? {
  documents.get(handle)
}

// =============================================================================
// Public FFI API
// =============================================================================

///| Parse markdown source and return a document handle

///|
/// Returns 0 on error
pub fn md_parse(source : String) -> Int {
  let result = parse(source)
  alloc_handle(result, source)
}

///| Render document to markdown string

///|
/// Returns empty string if handle is invalid
pub fn md_render_to_string(handle : Int) -> String {
  match get_stored(handle) {
    Some(stored) => serialize(stored.result.document)
    None => ""
  }
}

///| Parse incrementally with edit information

///|
/// Returns new document handle, or 0 on error
pub fn md_parse_incremental(
  handle : Int,
  new_source : String,
  change_start : Int,
  old_end : Int,
  new_end : Int,
) -> Int {
  match get_stored(handle) {
    Some(stored) => {
      let edit = EditInfo::replace(
        change_start,
        old_end - change_start,
        new_end - change_start,
      )
      let inc_result = parse_incremental(
        stored.result.document,
        stored.source,
        new_source,
        edit,
      )
      let new_parse_result : ParseResult = {
        document: inc_result.document,
        definitions: stored.result.definitions,
      }
      alloc_handle(new_parse_result, new_source)
    }
    None => 0
  }
}

///|
/// Free a document handle to release memory
pub fn md_free(handle : Int) -> Unit {
  documents.remove(handle)
}

///| Convenience: parse and immediately render (for roundtrip testing)

///|
/// When strict=true, uses full CommonMark compliance (slower)
pub fn md_parse_and_render(source : String, strict? : Bool = false) -> String {
  let result = parse(source, strict~)
  serialize(result.document)
}

///| Render document to HTML

///|
/// Returns empty string if handle is invalid
pub fn md_render_to_html(handle : Int) -> String {
  match get_stored(handle) {
    Some(stored) => render_html(stored.result.document)
    None => ""
  }
}