///|
/// MoonViz MCP Server:JSON-RPC 2.0 over stdio。
///
/// 完整实现:initialize / tools/list / tools/call(含参数解析)。
///
/// 测试:
/// ```bash
/// echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"apply_template","arguments":{"template_id":"login","name":"my_login"}}}' | moon run --target native mcp
/// ```
extern "c" fn getchar() -> Int = "getchar"

///|
fn read_line() -> String {
  let bytes : Array[Int] = []
  let mut on = true
  while on {
    let c = getchar()
    if c == 10 || c == -1 {
      on = false
    } else if c == 13 {
      // 跳过 \r
    } else {
      bytes.push(c)
    }
  }
  utf8_from_bytes(bytes)
}

///|
/// stdin 是 UTF-8 字节流:逐字节收集后解码(getchar 逐字节返回,
/// 直接用 Int::to_char 会按 Latin-1 拼坏非 ASCII 文本)
fn utf8_from_bytes(bytes : Array[Int]) -> String {
  let sb = StringBuilder::new()
  let mut i = 0
  let n = bytes.length()
  while i < n {
    let b0 = bytes[i]
    if b0 < 0x80 {
      sb.write_char(Int::to_char(b0).unwrap_or('?'))
      i = i + 1
    } else if b0 < 0xE0 && i + 1 < n {
      let cp = ((b0 - 0xC0) << 6) + (bytes[i + 1] - 0x80)
      sb.write_char(Int::to_char(cp).unwrap_or('?'))
      i = i + 2
    } else if b0 < 0xF0 && i + 2 < n {
      let cp = ((b0 - 0xE0) << 12) +
        ((bytes[i + 1] - 0x80) << 6) +
        (bytes[i + 2] - 0x80)
      sb.write_char(Int::to_char(cp).unwrap_or('?'))
      i = i + 3
    } else if i + 3 < n {
      let cp = ((b0 - 0xF0) << 18) +
        ((bytes[i + 1] - 0x80) << 12) +
        ((bytes[i + 2] - 0x80) << 6) +
        (bytes[i + 3] - 0x80)
      sb.write_char(Int::to_char(cp).unwrap_or('?'))
      i = i + 4
    } else {
      i = i + 1
    }
  }
  sb.to_string()
}

///|
/// 全局项目状态(跨请求保持)
let g_project : @core.Project = @core.Project::new()

///|
/// 设计版本历史(会话级):history_init 后随 commit/checkout/undo/redo 演进。
let g_history : @ref.Ref[@core.DesignHistory?] = @ref.Ref::new(None)

///|
let g_history_board : @ref.Ref[String] = @ref.Ref::new("")

///|
fn main {
  let mut running = true
  while running {
    let line = read_line()
    if line.length() == 0 {
      running = false
    } else {
      let response = handle_request(line)
      if response != "" {
        println(response)
      }
    }
  }
}

///|
fn handle_request(line : String) -> String {
  // JSON-RPC 2.0:无 id 成员的请求是通知(含未知通知),必须静默不响应;
  // extract_json_id 返回 None 即无 id(F54)
  let id = match extract_json_id(line) {
    Some(v) => v
    None => return ""
  }
  let method = extract_json_string(line, "method")
  match method {
    "initialize" =>
      "{\"jsonrpc\":\"2.0\",\"id\":\{id},\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"moonviz\",\"version\":\"\{@core.ENGINE_VERSION}\"}}}"
    "notifications/initialized" => ""
    "tools/list" => {
      let tools = tools_definition()
      "{\"jsonrpc\":\"2.0\",\"id\":\{id},\"result\":{\"tools\":\{tools}}}"
    }
    "tools/call" => {
      let tool_name = extract_json_string(line, "name")
      let result = execute_tool(line, tool_name)
      // MCP 要求 text 字段是 JSON 字符串(需要转义引号)
      let escaped = json_escape_text(result)
      "{\"jsonrpc\":\"2.0\",\"id\":\{id},\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"\{escaped}\"}]}}"
    }
    "ping" => "{\"jsonrpc\":\"2.0\",\"id\":\{id},\"result\":{}}"
    _ =>
      "{\"jsonrpc\":\"2.0\",\"id\":\{id},\"error\":{\"code\":-32601,\"message\":\"Method not found: \{method}\"}}"
  }
}

// ---------------------------------------------------------------------------
// 工具定义
// ---------------------------------------------------------------------------

///|
fn tools_definition() -> String {
  // 工具能力字典的单一事实源在 core/agent_api.mbt:name/description/
  // inputSchema(真实参数 schema)由此生成,CLI `list-tools` 与
  // 落地项目的 moonviz-tools.json 同源。
  @core.list_tools_json()
}

///|

// ---------------------------------------------------------------------------
// 工具执行(真正干活)
// ---------------------------------------------------------------------------

///|
fn execute_tool(request_line : String, tool_name : String) -> String {
  let p = g_project
  // 从 params.arguments 中提取参数
  let artboard = extract_arg_string(request_line, "artboard")
  let template_id = extract_arg_string(request_line, "template_id")
  let name = extract_arg_string(request_line, "name")
  let component_id = extract_arg_string(request_line, "component_id")
  let instance_id = extract_arg_string(request_line, "instance_id")
  let variant = extract_arg_string(request_line, "variant")
  let theme = extract_arg_string(request_line, "theme")
  let token = extract_arg_string(request_line, "token")
  let value = extract_arg_string(request_line, "value")
  let source_b64 = extract_arg_string(request_line, "source_b64")
  let intent = extract_arg_string(request_line, "intent")
  let operations_b64 = extract_arg_string(request_line, "operations_b64")
  let x = extract_arg_num(request_line, "x")
  let y = extract_arg_num(request_line, "y")
  let w = extract_arg_num(request_line, "w")
  let h = extract_arg_num(request_line, "h")
  let node_id = extract_arg_string(request_line, "node_id")
  let new_id = extract_arg_string(request_line, "new_id")
  let mode = extract_arg_string(request_line, "mode")
  let nodes_arg = extract_arg_string(request_line, "nodes")
  let trigger = extract_arg_string(request_line, "trigger")
  let action_spec = extract_arg_string(request_line, "action_spec")
  let state_name = extract_arg_string(request_line, "state_name")
  let width = extract_arg_num(request_line, "width")
  let height = extract_arg_num(request_line, "height")

  match tool_name {
    "list_templates" => @core.list_templates_json()
    "list_components" => {
      let builtin = @core.list_components_json(p.components)
      let user_items : Array[String] = []
      for id in p.user_components.keys() {
        match p.user_components.get(id) {
          Some(def) => {
            let vs : Array[String] = []
            for v in def.variants.keys() {
              vs.push("\"\{@core.json_escape(v)}\"")
            }
            user_items.push(
              "{\"id\":\"\{@core.json_escape(id)}\",\"kind\":\"user-component\",\"category\":\"\{@core.json_escape(def.category)}\",\"variants\":[\{mcp_join(vs)}],\"source\":\"user\",\"description\":\"\{@core.json_escape(def.description)}\"}",
            )
          }
          None => ()
        }
      }
      if user_items.length() == 0 {
        builtin
      } else {
        let inner = builtin.substring(start=1, end=builtin.length() - 1)
        let sep = if inner == "" { "" } else { "," }
        "[\{inner}\{sep}\{mcp_join(user_items)}]"
      }
    }
    "component_compile" => {
      if source_b64 == "" {
        return err_json("missing source_b64")
      }
      match decode_b64_text(source_b64) {
        Err(e) => err_json(e)
        Ok(text) =>
          match @decl.parse_user_component(text) {
            Err(e) => err_json(e)
            Ok(def) => {
              p.user_components.set(def.id, def)
              "{\"ok\":true,\"component\":\"\{@core.json_escape(def.id)}\",\"params_count\":\{def.params.length()}}"
            }
          }
      }
    }
    "component_describe" => {
      if component_id == "" {
        return err_json("missing component_id")
      }
      match p.user_components.get(component_id) {
        None => err_json("unknown_component:" + component_id)
        Some(def) => {
          let ps : Array[String] = []
          for param in def.params.iter() {
            ps.push(
              "{\"name\":\"\{@core.json_escape(param.name)}\",\"kind\":\"\{@core.json_escape(param.kind)}\",\"default\":\"\{@core.json_escape(param.default_value)}\"}",
            )
          }
          let vs : Array[String] = []
          for v in def.variants.keys() {
            vs.push("\"\{@core.json_escape(v)}\"")
          }
          let kvs : Array[String] = []
          for param in def.params.iter() {
            kvs.push("\{param.name}=<\{param.kind}>")
          }
          let kv_sb = StringBuilder::new()
          let mut first_kv = true
          for kv in kvs.iter() {
            if first_kv == false {
              kv_sb.write_string(" ")
            }
            first_kv = false
            kv_sb.write_string(kv)
          }
          let kv_j = if kvs.length() > 0 { " " + kv_sb.to_string() } else { "" }
          let usage = "place  \{component_id}  [variant] [x] [y] [w] [h]\{kv_j}"
          "{\"ok\":true,\"id\":\"\{@core.json_escape(component_id)}\",\"description\":\"\{@core.json_escape(def.description)}\",\"source\":\"user\",\"size\":\"\{def.width}x\{def.height}\",\"params\":[\{mcp_join(ps)}],\"variants\":[\{mcp_join(vs)}],\"usage\":\"\{@core.json_escape(usage)}\"}"
        }
      }
    }
    "component_export" => {
      if component_id == "" {
        return err_json("missing component_id")
      }
      match p.user_components.get(component_id) {
        None => err_json("unknown_component:" + component_id)
        Some(def) =>
          // header 构造收敛到 core(与 CLI 同一 schema)
          match @core.mcf_pack(@core.mcf_header_json(def), def.source) {
            Err(e) => err_json(e)
            Ok(bytes) =>
              "{\"ok\":true,\"component\":\"\{@core.json_escape(component_id)}\",\"mcf_b64\":\"\{@base64.encode(bytes)}\"}"
          }
      }
    }
    "component_import" => {
      let mcf_b64 = extract_arg_string(request_line, "mcf_b64")
      if mcf_b64 == "" {
        return err_json("missing mcf_b64")
      }
      let bytes = @base64.decode(mcf_b64) catch {
        _ => return err_json("mcf_transport_invalid_base64")
      }
      match @core.mcf_unpack(bytes) {
        Err(e) => err_json(e)
        Ok((header, source)) =>
          match @decl.parse_user_component(source) {
            Err(e) => err_json(e)
            Ok(def) => {
              p.user_components.set(def.id, def)
              // header_json 由 mcf_header_json 产出,已是合法 JSON 对象文本:
              // 原样嵌入(再转义会破坏结构),收尾 } 是信封闭合(F51)
              "{\"ok\":true,\"component\":\"\{@core.json_escape(def.id)}\",\"header\":\{header}}"
            }
          }
      }
    }
    "library_snapshot" => {
      let items : Array[String] = []
      for id in p.user_components.keys() {
        match p.user_components.get(id) {
          Some(def) =>
            items.push(
              "{\"id\":\"\{@core.json_escape(id)}\",\"source_b64\":\"\{@base64.encode(@utf8.encode(def.source))}\"}",
            )
          None => ()
        }
      }
      "{\"ok\":true,\"op\":\"library-snapshot\",\"count\":\{items.length()},\"components\":[\{mcp_join(items)}]}"
    }
    "component_delete" => {
      if component_id == "" {
        return err_json("missing component_id")
      }
      if p.user_components.contains(component_id) == false {
        return err_json("unknown_component:" + component_id)
      }
      p.user_components.remove(component_id)
      "{\"ok\":true,\"op\":\"component-delete\",\"component\":\"\{@core.json_escape(component_id)}\"}"
    }
    "library_restore" => {
      let source_b64s = extract_arg_string(request_line, "source_b64s")
      if source_b64s == "" {
        return err_json("missing source_b64s")
      }
      let mut ok_count = 0
      let mut first_error = ""
      for part_view in source_b64s.split(" ") {
        let part = part_view.to_string()
        if part == "" {
          continue
        }
        match decode_b64_text(part) {
          Err(e) => if first_error == "" { first_error = e }
          Ok(text) =>
            match @decl.parse_user_component(text) {
              Err(e) => if first_error == "" { first_error = e }
              Ok(def) => {
                p.user_components.set(def.id, def)
                ok_count = ok_count + 1
              }
            }
        }
      }
      if first_error != "" {
        return err_json("library_restore_partial:\{ok_count}:" + first_error)
      }
      "{\"ok\":true,\"op\":\"library-restore\",\"count\":\{ok_count}}"
    }
    "list_tokens" => p.tokens.to_json()
    "list_themes" => {
      let themes = @core.builtin_themes()
      let items : Array[String] = []
      for t in themes.iter() {
        items.push(
          "{\"name\":\"\{t.name}\",\"display\":\"\{t.display_name}\",\"is_dark\":\{t.is_dark}}",
        )
      }
      let j = mcp_join(items)
      "[\{j}]"
    }
    "list_artboards" => p.list_artboards_json()
    "apply_template" => {
      if template_id == "" {
        return err_json("missing template_id")
      }
      let ab_name = if artboard != "" {
        artboard
      } else if name != "" {
        name
      } else {
        template_id
      }
      match p.apply_template(template_id~, name=ab_name) {
        Ok(id) => "{\"ok\":true,\"artboard\":\"\{id}\"}"
        Err(e) => err_json(e)
      }
    }
    "place_component" => {
      if artboard == "" || component_id == "" || instance_id == "" {
        return err_json("missing artboard/component_id/instance_id")
      }
      // 用户组件参数槽:arguments.args = "k=v k=v ..."(空格分隔)
      let kv : Map[String, String] = Map::new()
      let raw_args = extract_arg_string(request_line, "args")
      if raw_args != "" {
        for part_view in raw_args.split(" ") {
          let part = part_view.to_string()
          if part == "" {
            continue
          }
          match part.find("=") {
            None => return err_json("place_invalid_arg:" + part)
            Some(pos) => {
              let k = part.substring(start=0, end=pos)
              let v = @decl.unescape_value(part.substring(start=pos + 1))
              if k != "" {
                kv.set(k, v)
              }
            }
          }
        }
      }
      // x/y 是 schema required:缺参报错而非静默落 0(F55);
      // w/h 是可选(no() 注册):缺失保持 ≤0=默认尺寸语义
      let (xv, yv) = match (x, y) {
        (Some(a), Some(b)) => (a, b)
        _ => return err_json("missing x/y (required numbers)")
      }
      let wv = w.unwrap_or(0.0)
      let hv = h.unwrap_or(0.0)
      match
        p.place_component(
          artboard~,
          component_id~,
          instance_id~,
          variant~,
          x=xv,
          y=yv,
          w=wv,
          h=hv,
          args=kv,
        ) {
        Ok(msg) => "{\"ok\":true,\"result\":\"\{@core.json_escape(msg)}\"}"
        Err(e) => err_json(e)
      }
    }
    "query_nodes" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.query_nodes(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "lint_design" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.lint(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "generate" => {
      // 生成式 UI 入口(执行策略第一级):意图一句话,引擎生成 + 报告
      if intent == "" {
        return err_json("missing intent")
      }
      let spec = extract_arg_string(request_line, "spec")
      match p.generate(intent_text=intent, name=name, spec=spec) {
        Ok(json) => json
        Err(e) =>
          if e == "design_spec_required" {
            "{\"ok\":false,\"error\":\"design_spec_required\",\"design_spec_draft\":" +
            @core.design_spec_draft_json(intent) +
            "}"
          } else {
            err_json(e)
          }
      }
    }
    "batch_apply" => {
      // 执行策略第二级:多条确定精修一次提交(base64(换行分隔 ops))。
      // 严格解码(decode_b64_text 与 CLI decode_mbt_b64 同构:非法
      // base64/UTF-8 直接报错透传)——此前 try?+decode_lossy 把非法输入
      // 吞成空串再误报 ops_batch_empty,与 CLI 同输入的严格报错不同构,
      // 误导 Agent 诊断方向。
      if operations_b64 == "" {
        return err_json("missing operations_b64")
      }
      let block = match decode_b64_text(operations_b64) {
        Err(e) => return err_json(e)
        Ok(text) => text
      }
      let lines : Array[String] = []
      for line in block.split("\n").iter() {
        let trimmed = line.trim().to_string()
        if trimmed != "" {
          lines.push(trimmed)
        }
      }
      if lines.length() == 0 {
        return err_json("ops_batch_empty")
      }
      match @core.apply_operations(p, lines) {
        Ok(applied) =>
          "{\"ok\":true,\"op\":\"batch_apply\",\"applied\":\{applied}}"
        Err((index, error)) => {
          // applied 语义是已应用条数;超限拒绝时 index=-1 是哨兵而非
          // 条数,信封层与 index 解耦(不可能值会误导断点续批逻辑)
          let applied = if index < 0 { 0 } else { index }
          "{\"ok\":false,\"op\":\"batch_apply\",\"applied\":\{applied},\"index\":\{index},\"error\":\"\{@core.json_escape(error)}\"}"
        }
      }
    }
    "critique" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.critique(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "benchmark" => p.benchmark().to_json()
    "auto_fix" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.auto_fix(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "apply_theme" => {
      if theme == "" {
        return err_json("missing theme")
      }
      match p.apply_theme(theme) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "set_token" => {
      if token == "" || value == "" {
        return err_json("missing token or value")
      }
      match p.set_token(token, value) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "export_html" => {
      let html = p.export_html()
      let len = html.length().to_string()
      "{\"ok\":true,\"html_length\":\{len},\"preview\":\"\{html.substring(start=0, end=120)}...\"}"
    }
    "export_svg" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.export_svg(artboard~) {
        Ok(svg) => {
          let len = svg.length().to_string()
          "{\"ok\":true,\"svg_length\":\{len},\"preview\":\"\{svg.substring(start=0, end=100)}...\"}"
        }
        Err(e) => err_json(e)
      }
    }
    "read_mbt" =>
      if source_b64 == "" {
        "{\"error\":\"source_b64_required\"}"
      } else {
        match decode_b64_text(source_b64) {
          Err(e) => err_json(e)
          Ok(text) =>
            match @decl.load_mbt_project(text) {
              Ok(doc) =>
                "{\"ok\":true,\"mbt\":\"" +
                json_escape_text(doc.document.source) +
                "\"}"
              Err(e) => err_json(e)
            }
        }
      }
    "render_mbt" =>
      if source_b64 == "" {
        "{\"error\":\"source_b64_required\"}"
      } else {
        match decode_b64_text(source_b64) {
          Err(e) => err_json(e)
          Ok(text) =>
            match @decl.load_mbt_project(text) {
              Ok(doc) => doc.render_json()
              Err(e) => err_json(e)
            }
        }
      }
    "ddp_view" =>
      "{\"ok\":true,\"read_only\":true,\"mutations\":false,\"codec\":\"engine\"}"
    "export_artifact" => p.export_artifact(agent_id="mcp")
    "list_ops" => @core.list_ops_json()
    "animation_presets" => @core.animation_presets_json()
    "history" => {
      let cmd = extract_arg_string(request_line, "command")
      if cmd == "" {
        return err_json(
          "missing command (init|commit|log|undo|redo|checkout|diff)",
        )
      }
      match cmd {
        "init" =>
          match g_project.artboards.get(artboard) {
            None => err_json("unknown_artboard: " + artboard)
            Some(ab) => {
              // 幂等(与 CLI 语义对齐,issue #21):已注册同一目标板时
              // init 不重基线、保留已累积历史栈;换板才重置(此前每次
              // init 都清空撤销栈,注释与实现漂移——F57)
              let kept = match g_history.val {
                Some(_) => g_history_board.val == artboard
                None => false
              }
              if !kept {
                g_history.val = Some(@core.DesignHistory::new(ab.doc.clone()))
                g_history_board.val = artboard
              }
              "{\"ok\":true,\"op\":\"history-init\",\"artboard\":\"\{artboard}\",\"kept\":\{kept}}"
            }
          }
        "commit" =>
          match g_history.val {
            None => err_json("history_not_initialized")
            Some(h) => {
              // schema 中 artboard 可省略:缺省回落当前 history 板(与
              // CLI「history commit  [artboard]」对齐——F57)
              let target = if artboard != "" {
                artboard
              } else {
                g_history_board.val
              }
              match g_project.artboards.get(target) {
                None => err_json("unknown_artboard: " + target)
                Some(ab) => {
                  let msg = extract_arg_string(request_line, "message")
                  let m = if msg == "" { "commit" } else { msg }
                  let _ = h.commit("mcp", m, [], ab.doc.clone())
                  "{\"ok\":true,\"op\":\"history-commit\",\"rev\":\{h.rev()},\"message\":\"\{@core.json_escape(m)}\"}"
                }
              }
            }
          }
        "log" =>
          match g_history.val {
            None => err_json("history_not_initialized")
            Some(h) =>
              "{\"ok\":true,\"op\":\"history-log\",\"rev\":\{h.rev()},\"log\":\"\{@core.json_escape(h.log())}\"}"
          }
        "undo" | "redo" | "checkout" =>
          match g_history.val {
            None => err_json("history_not_initialized")
            Some(h) => {
              let want = extract_arg_string(request_line, "rev")
              let doc = if cmd == "undo" {
                h.undo()
              } else if cmd == "redo" {
                h.redo()
              } else {
                match mcp_parse_rev(want) {
                  Some(v) => h.checkout(v)
                  None => return err_json("bad_revision")
                }
              }
              match doc {
                Err(e) => err_json(e)
                Ok(d) => {
                  let target = g_history_board.val
                  match g_project.set_artboard_doc(artboard=target, d) {
                    Err(e) => err_json(e)
                    Ok(_) =>
                      "{\"ok\":true,\"op\":\"history-\{cmd}\",\"rev\":\{h.rev()},\"artboard\":\"\{target}\"}"
                  }
                }
              }
            }
          }
        "diff" =>
          match g_history.val {
            None => err_json("history_not_initialized")
            Some(h) => {
              let a = mcp_parse_rev(extract_arg_string(request_line, "from"))
              let b = mcp_parse_rev(extract_arg_string(request_line, "to"))
              match (a, b) {
                (Some(x), Some(y)) =>
                  match h.diff(x, y) {
                    Err(e) => err_json(e)
                    Ok(json) =>
                      "{\"ok\":true,\"op\":\"history-diff\",\"from\":\{x},\"to\":\{y},\"diff\":\{json}}"
                  }
                _ => err_json("usage: history diff  ")
              }
            }
          }
        _ => err_json("unknown history command: " + cmd)
      }
    }
    "protest" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      let script = extract_arg_string(request_line, "script")
      if script == "" {
        return err_json(
          "missing script (tap:x:y>board; back>board; noviol; render)",
        )
      }
      @core.run_protest_script(p, artboard, script)
    }
    "animation_css" => {
      if node_id == "" {
        return err_json("missing node_id")
      }
      let preset = extract_arg_string(request_line, "preset")
      if preset == "" {
        return err_json(
          "missing preset (press/fade_in/slide_in_right/modal_present/shake/pop)",
        )
      }
      @core.animation_css(node_id, preset)
    }
    "collab_merge" => {
      let spec = extract_arg_string(request_line, "spec")
      if spec == "" {
        return err_json("missing spec:  =[+op...] ...")
      }
      @core.merge_sessions_from_spec(spec)
    }
    "generate_spec" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.generate_spec(artboard~) {
        Ok(spec) => {
          let len = spec.length().to_string()
          "{\"ok\":true,\"spec_length\":\{len}}"
        }
        Err(e) => err_json(e)
      }
    }
    "infer_page_type" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.infer_page_type(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "infer_missing" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.infer_missing(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "suggest_alignment" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.suggest_alignment(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "extract_design_system" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.extract_design_system(artboard~) {
        Ok(ds) => ds.to_json()
        Err(e) => err_json(e)
      }
    }
    "export_design_system" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.export_design_system(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "generate_responsive" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.generate_responsive(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    // ---- 节点编辑与结构操作 ----
    "update_node" => {
      if artboard == "" || node_id == "" {
        return err_json("missing artboard/node_id")
      }
      let raw = extract_arg_string(request_line, "args")
      if raw == "" {
        return err_json("missing args (space-separated k=v)")
      }
      let kvs : Array[(String, String)] = []
      for part_view in raw.split(" ") {
        let part = part_view.to_string()
        if part == "" {
          continue
        }
        match part.find("=") {
          None => return err_json("update_invalid_arg:" + part)
          Some(pos) => {
            let k = part.substring(start=0, end=pos)
            let v = @decl.unescape_value(part.substring(start=pos + 1))
            if k != "" {
              kvs.push((k, v))
            }
          }
        }
      }
      if kvs.length() == 0 {
        return err_json("no k=v pairs")
      }
      match p.edit_node(artboard~, node_id~, kvs~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "move_node" => {
      if artboard == "" || node_id == "" {
        return err_json("missing artboard/node_id")
      }
      // x/y 是 schema required:缺参报错而非静默搬到 0(F55)
      let (xv, yv) = match (x, y) {
        (Some(a), Some(b)) => (a, b)
        _ => return err_json("missing x/y (required numbers)")
      }
      match p.move_node(artboard~, node_id~, x=xv, y=yv) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "group_nodes" => {
      if artboard == "" || new_id == "" || nodes_arg == "" {
        return err_json("missing artboard/new_id/nodes")
      }
      let ids : Array[String] = []
      for v in nodes_arg.split(",") {
        let t = v.to_string().trim().to_string()
        if t != "" {
          ids.push(t)
        }
      }
      if ids.length() < 2 {
        return err_json("need_two_nodes")
      }
      match p.group_nodes(artboard~, node_ids=ids, group_id=new_id) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "ungroup_node" => {
      if artboard == "" || node_id == "" {
        return err_json("missing artboard/node_id")
      }
      match p.ungroup_node(artboard~, group_id=node_id) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "align_nodes" => {
      if artboard == "" || mode == "" || nodes_arg == "" {
        return err_json("missing artboard/mode/nodes")
      }
      let ids : Array[String] = []
      for v in nodes_arg.split(",") {
        let t = v.to_string().trim().to_string()
        if t != "" {
          ids.push(t)
        }
      }
      if ids.length() < 2 {
        return err_json("need_two_nodes")
      }
      match p.align_nodes(artboard~, mode~, node_ids=ids) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "resize_canvas" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      // width/height 是 schema required:缺 height 报错而非静默重排成 0 高(F55)
      let (wv, hv) = match (width, height) {
        (Some(a), Some(b)) => (a, b)
        _ => return err_json("missing width/height (required numbers)")
      }
      match p.resize_canvas(artboard~, width=wv, height=hv) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "restyle_component" => {
      if artboard == "" || component_id == "" {
        return err_json("missing artboard/component_id")
      }
      let raw = extract_arg_string(request_line, "args")
      let kvs : Array[(String, String)] = []
      for part_view in raw.split(" ") {
        let part = part_view.to_string()
        if part == "" {
          continue
        }
        match part.find("=") {
          None => return err_json("restyle_invalid_arg:" + part)
          Some(pos) => {
            let k = part.substring(start=0, end=pos)
            let v = @decl.unescape_value(part.substring(start=pos + 1))
            if k != "" {
              kvs.push((k, v))
            }
          }
        }
      }
      if kvs.length() == 0 {
        return err_json("missing args (space-separated k=v)")
      }
      match p.restyle_component(artboard~, component_id~, kvs~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    // ---- 交互 ----
    "interact" => {
      if artboard == "" || node_id == "" || trigger == "" || action_spec == "" {
        return err_json("missing artboard/node_id/trigger/action_spec")
      }
      match p.attach_interaction(artboard~, node_id~, trigger~, action_spec~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "uninteract" => {
      if artboard == "" || node_id == "" {
        return err_json("missing artboard/node_id")
      }
      match p.detach_interaction(artboard~, node_id~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "interactions" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.interactions_json(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    // ---- 组件状态 ----
    "define_state" => {
      if artboard == "" || node_id == "" || state_name == "" {
        return err_json("missing artboard/node_id/state_name")
      }
      let raw = extract_arg_string(request_line, "args")
      let mut fill : String? = None
      let mut text_color : String? = None
      let mut stroke : String? = None
      let mut text : String? = None
      let mut radius : Double? = None
      let mut font_size : Double? = None
      for part_view in raw.split(" ") {
        let part = part_view.to_string()
        if part == "" {
          continue
        }
        match part.find("=") {
          None => ()
          Some(pos) => {
            let k = part.substring(start=0, end=pos)
            let v = @decl.unescape_value(part.substring(start=pos + 1))
            let d = @core.parse_double(v)
            match k {
              "fill" => fill = Some(v)
              "text_color" => text_color = Some(v)
              "stroke" => stroke = Some(v)
              "text" => text = Some(v)
              "radius" => radius = Some(d)
              "font_size" => font_size = Some(d)
              _ => ()
            }
          }
        }
      }
      let patch : @core.NodePatch = {
        ..@core.NodePatch::empty(),
        fill,
        text_color,
        stroke,
        text,
        corner_radius: radius,
        font_size,
      }
      match p.define_component_state(artboard~, node_id~, state_name~, patch~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "set_state" => {
      if node_id == "" || state_name == "" {
        return err_json("missing node_id/state_name")
      }
      let toggle = extract_arg_string(request_line, "toggle") == "true"
      match p.set_node_state(node_id~, state_name~, toggle~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    "list_states" => {
      if artboard == "" {
        return err_json("missing artboard")
      }
      match p.node_states_json(artboard~) {
        Ok(json) => json
        Err(e) => err_json(e)
      }
    }
    _ => err_json("unknown_tool: " + tool_name)
  }
}

///|
fn err_json(msg : String) -> String {
  let escaped = json_escape_text(msg)
  "{\"error\":\"\{escaped}\"}"
}

// ---------------------------------------------------------------------------
// JSON 参数解析
// ---------------------------------------------------------------------------

///|
/// 定位 params.arguments 对象体起点(容忍 "arguments":{ 与 "arguments": { 两种写法)。
/// 固定模式会让冒号后带空格的请求全部解析失败(表现为参数 missing)。
fn find_arguments_body(line : String) -> Int? {
  let key = "\"arguments\":"
  match line.find(key) {
    None => None
    Some(i) => {
      let mut j = i + key.length()
      while j < line.length() && line.get_char(j) == Some(' ') {
        j = j + 1
      }
      if line.get_char(j) == Some('{') {
        Some(j + 1)
      } else {
        None
      }
    }
  }
}

///|
/// 从 JSON-RPC params.arguments 中提取字符串参数(转义感知)。
/// 闭引号扫描必须跳过 \ 转义并还原常用序列(\" \\ \n \t \r \b \f /):
/// generate 是首个以完整自然语句入参的工具,intent 含 \" 时裸 find("\"")
/// 会在转义引号处截断、后半句静默丢失({"intent":"生成\"欢迎\"引导页"}
/// 实证只提取出 "生成\")。JSON 标准 \uXXXX 同样按标准解码(含
/// \uD83D\uDE00 代理对合成)——ensure_ascii 风格客户端发送的中文意图
/// 关键词(登录页 → \u767b\u5f55\u9875)只有解码才能命中模板;非法
/// 序列(hex 残缺/孤立代理项)与其余未知转义一样原样保留(不丢字符)。
/// 无闭合引号返回 ""(与既有行为一致)。
/// 输入行形如: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"apply_template","arguments":{"template_id":"login"}}}
fn extract_arg_string(line : String, key : String) -> String {
  // 先定位 "arguments":{ ... }
  let args_idx = match find_arguments_body(line) {
    Some(i) => i
    None => return ""
  }
  let args_body = line.substring(start=args_idx)
  // 在 arguments 对象内查找 key(同样容忍 "key": "value" 的空格写法)
  let pattern = "\"" + key + "\":"
  match args_body.find(pattern) {
    Some(idx) => {
      let mut start = idx + pattern.length()
      while start < args_body.length() && args_body.get_char(start) == Some(' ') {
        start = start + 1
      }
      if args_body.get_char(start) != Some('"') {
        return ""
      }
      // 扫描到第一个未转义的闭引号,途中还原 \X 转义序列
      let rest = args_body.substring(start=start + 1)
      let sb = StringBuilder::new()
      let mut i = 0
      let n = rest.length()
      let mut closed = false
      while i < n {
        match rest.get_char(i) {
          Some('"') => {
            closed = true
            i = n
          }
          Some('\\') => {
            if i + 1 < n && rest.get_char(i + 1) == Some('u') {
              // JSON 标准 \uXXXX:解析 4 位十六进制还原码点(含代理对合成)
              match decode_unicode_escape(rest, i, n) {
                Some((ch, next_i)) => {
                  sb.write_char(ch)
                  i = next_i
                }
                None => {
                  // 非法序列(hex 残缺/孤立代理项):原样保留,不做有损丢弃
                  sb.write_char('\\')
                  i = i + 1
                }
              }
            } else if i + 1 < n {
              match rest.get_char(i + 1) {
                Some('"') => sb.write_char('"')
                Some('\\') => sb.write_char('\\')
                Some('/') => sb.write_char('/')
                Some('n') => sb.write_char('\n')
                Some('t') => sb.write_char('\t')
                Some('r') => sb.write_char('\r')
                Some('b') => sb.write_char('\u{0008}')
                Some('f') => sb.write_char('\u{000C}')
                Some(other) => {
                  // 其余未知转义:原样保留,不做有损丢弃
                  // (\uXXXX 已在上方分支按 JSON 标准解码)
                  sb.write_char('\\')
                  sb.write_char(other)
                }
                None => sb.write_char('\\')
              }
              i = i + 2
            } else {
              // 行尾孤立反斜杠(截断的请求):原样保留
              sb.write_char('\\')
              i = i + 1
            }
          }
          Some(ch) => {
            sb.write_char(ch)
            i = i + 1
          }
          None => i = i + 1
        }
      }
      if closed {
        sb.to_string()
      } else {
        ""
      }
    }
    None => ""
  }
}

///|
/// 解析 rest 中自 i 起(rest[i]=='\\'、rest[i+1]=='u')的 \uXXXX 转义。
/// 成功返回(还原的字符, 消费后的下一游标):4 位十六进制直接映射码点;
/// 高代理项 \uD800-\uDBFF 后随低代理项转义 \uDC00-\uDFFF 时合成增补码点
/// (emoji 等 BMP 外字符)。序列非法(hex 残缺、孤立代理项)返回 None,
/// 由调用方原样保留整段文本(不做有损丢弃)。
fn decode_unicode_escape(rest : String, i : Int, n : Int) -> (Char, Int)? {
  // 至少要有 "\uXXXX" 6 个字符
  if i + 6 > n {
    return None
  }
  let code = match parse_hex4(rest, i + 2) {
    Some(c) => c
    None => return None
  }
  if code >= 0xD800 && code <= 0xDBFF {
    // 高代理项:紧随 \uXXXX 低代理项(0xDC00-0xDFFF)才合成,否则非法
    if
      i + 12 <= n &&
      rest.get_char(i + 6) == Some('\\') &&
      rest.get_char(i + 7) == Some('u') {
      match parse_hex4(rest, i + 8) {
        Some(low) =>
          if low >= 0xDC00 && low <= 0xDFFF {
            let cp = 0x10000 + (code - 0xD800) * 0x400 + (low - 0xDC00)
            return Some((Char::from_int(cp), i + 12))
          }
        None => ()
      }
    }
    return None
  }
  if code >= 0xDC00 && code <= 0xDFFF {
    // 孤立低代理项:非法序列
    return None
  }
  Some((Char::from_int(code), i + 6))
}

///|
/// 解析 s 自 start 起的 4 位十六进制(\uXXXX 的 XXXX);含非 hex 字符
/// 返回 None
fn parse_hex4(s : String, start : Int) -> Int? {
  let mut code = 0
  let mut k = 0
  while k < 4 {
    match hex_digit_value(s.get_char(start + k)) {
      Some(v) => code = code * 16 + v
      None => return None
    }
    k = k + 1
  }
  Some(code)
}

///|
/// 单个十六进制字符的数值(0-9 / a-f / A-F);其余返回 None
fn hex_digit_value(c : Char?) -> Int? {
  match c {
    Some(ch) => {
      let code = ch.to_int()
      if code >= '0'.to_int() && code <= '9'.to_int() {
        Some(code - '0'.to_int())
      } else if code >= 'a'.to_int() && code <= 'f'.to_int() {
        Some(code - 'a'.to_int() + 10)
      } else if code >= 'A'.to_int() && code <= 'F'.to_int() {
        Some(code - 'A'.to_int() + 10)
      } else {
        None
      }
    }
    None => None
  }
}

///|
/// 从 params.arguments 中提取数值参数:键缺失返回 None(schema 标注
/// required 的 x/y、width/height 缺参必须报错,静默取 0.0 会把节点搬到
/// 原点或把画板重排成 0 高——F55);键存在但非数值仍解析为 0.0(容忍
/// 字符串数字等历史形态,语义不变)。
fn extract_arg_num(line : String, key : String) -> Double? {
  let args_idx = match find_arguments_body(line) {
    Some(i) => i
    None => return None
  }
  let args_body = line.substring(start=args_idx)
  let pattern = "\"" + key + "\":"
  match args_body.find(pattern) {
    Some(idx) => {
      let mut start = idx + pattern.length()
      // 容忍 "key": 123 的空格写法(否则首个字符是空格,数字解析直接为 0)
      while start < args_body.length() && args_body.get_char(start) == Some(' ') {
        start = start + 1
      }
      let rest = args_body.substring(start~)
      let mut result = 0.0
      let mut frac = 0.1
      let mut has_dot = false
      for c in rest.iter() {
        let code = c.to_int()
        if code >= 48 && code <= 57 {
          let d = (code - 48).to_double()
          if has_dot {
            result = result + d * frac
            frac = frac * 0.1
          } else {
            result = result * 10.0 + d
          }
        } else if c == '.' && !has_dot {
          has_dot = true
        } else {
          break
        }
      }
      Some(result)
    }
    None => None
  }
}

// ---------------------------------------------------------------------------
// 通用 JSON 辅助
// ---------------------------------------------------------------------------

///|
fn extract_json_string(json : String, key : String) -> String {
  // 容忍 "key":"v" 与 "key": "v" 两种写法:多数 JSON 客户端会在冒号后加空格,
  // 固定无空格模式会让 method/name 解析为空(表现为 Method not found)。
  let pattern = "\"" + key + "\":"
  match json.find(pattern) {
    Some(idx) => {
      let mut start = idx + pattern.length()
      while start < json.length() && json.get_char(start) == Some(' ') {
        start = start + 1
      }
      if json.get_char(start) != Some('"') {
        return ""
      }
      let rest = json.substring(start=start + 1)
      match rest.find("\"") {
        Some(end) => rest.substring(start=0, end~)
        None => ""
      }
    }
    None => ""
  }
}

///|
/// 提取 JSON-RPC 请求 id(F54):
/// - 数字 id(含负号):原样回显;
/// - 字符串 id:回显含引号形态(嵌入响应即合法 JSON 字符串),修复
///   字符串 id 被回成 0 破坏请求-响应关联;
/// - id 存在但非串/数(null 等):以 null 回显;
/// - 无 id 成员:返回 None——JSON-RPC 2.0 通知,调用方不得响应。
fn extract_json_id(json : String) -> String? {
  match json.find("\"id\":") {
    None => None
    Some(idx) => {
      let mut start = idx + 5
      while start < json.length() && json.get_char(start) == Some(' ') {
        start = start + 1
      }
      match json.get_char(start) {
        Some('"') => {
          // 字符串 id:扫描到闭引号,保留引号形态回显
          let rest = json.substring(start=start + 1)
          match rest.find("\"") {
            Some(end) =>
              Some("\"" + rest.substring(start=0, end~) + "\"")
            None => None // 截断请求:无合法 id,按通知静默
          }
        }
        Some(_) => {
          let rest = json.substring(start~)
          let mut result = ""
          for c in rest.iter() {
            let code = c.to_int()
            if (code >= 48 && code <= 57) || c == '-' {
              result = result + c.to_string()
            } else {
              break
            }
          }
          if result == "" {
            Some("null")
          } else {
            Some(result)
          }
        }
        None => None
      }
    }
  }
}

///|
fn json_escape_text(s : String) -> String {
  let sb = StringBuilder::new()
  for c in s.iter() {
    match c {
      '"' => sb.write_string("\\\"")
      '\\' => sb.write_string("\\\\")
      '\n' => sb.write_string("\\n")
      _ => sb.write_char(c)
    }
  }
  sb.to_string()
}

///|
fn mcp_join(items : Array[String]) -> String {
  let sb = StringBuilder::new()
  let mut first = true
  for item in items.iter() {
    if first == false {
      sb.write_string(",")
    }
    first = false
    sb.write_string(item)
  }
  sb.to_string()
}

///|
fn decode_b64_text(payload : String) -> Result[String, String] {
  let bytes = @base64.decode(payload) catch {
    _ => return Err("mbt_transport_invalid_base64")
  }
  Ok(
    @utf8.decode(bytes) catch {
      _ => return Err("mbt_transport_invalid_utf8")
    },
  )
}

///|
fn mcp_parse_rev(text : String) -> Int? {
  let t = text.trim().to_string()
  if t.length() == 0 {
    return None
  }
  let mut v = 0
  let mut i = 0
  while i < t.length() {
    let c = t.substring(start=i, end=i + 1)
    let d = "0123456789".find(c)
    match d {
      None => return None
      Some(x) => v = v * 10 + x
    }
    i = i + 1
  }
  Some(v)
}