///|
/// 操作语法与参数解析:CLI(apply-*-mbt-op-b64)、WASM SDK、Studio 共用的
/// 唯一实现。此前只在 cli 包,WASM 目标(只依赖 core/decl)无法触达。
///
/// 声明转义还原也移到这里:操作参数经 split_op_args 保留转义原文,由
/// unescape_value 统一还原,两半必须成对存在。

///|
/// CLI/engine 边界的公共值还原:把 `\" \\ \, \( \) \n \r \t` 还原为字面字符。
pub fn unescape_value(s : String) -> String {
  unescape_decl(s)
}

///|
/// 还原声明转义:\" \\ \, \( \) \n \r \t,以及 json_escape 的 C0 控制符
/// 编码段 \u00xx——decl_escape 经 json_escape 把控制符写成 \u00xx,解码端
/// 必须对称还原,否则控制符文本往返后变成字面 backslash-u 序列。
fn unescape_decl(s : String) -> String {
  let sb = StringBuilder::new()
  let it = s.iter()
  let mut running = true
  while running {
    match it.next() {
      None => running = false
      Some('\\') =>
        match it.next() {
          None => sb.write_char('\\')
          Some('"') => sb.write_char('"')
          Some('\\') => sb.write_char('\\')
          Some(',') => sb.write_char(',')
          Some('(') => sb.write_char('(')
          Some(')') => sb.write_char(')')
          Some('n') => sb.write_char('\n')
          Some('r') => sb.write_char('\r')
          Some('t') => sb.write_char('\t')
          Some('u') =>
            match hex4(it) {
              Some(code) => sb.write_char(Char::from_int(code))
              None => sb.write_string("\\u")
            }
          Some(other) => {
            sb.write_char('\\')
            sb.write_char(other)
          }
        }
      Some(c) => sb.write_char(c)
    }
  }
  sb.to_string()
}

///|
/// 从迭代器读 4 个十六进制字符(\uXXXX);任一非法即 None(调用方按原文回填)。
fn hex4(it : Iter[Char]) -> Int? {
  let mut code = 0
  for _ in 0..<4 {
    match it.next() {
      None => return None
      Some(h) => {
        let d = match h {
          '0'..='9' => h.to_int() - 48
          'a'..='f' => h.to_int() - 87
          'A'..='F' => h.to_int() - 55
          _ => return None
        }
        code = code * 16 + d
      }
    }
  }
  Some(code)
}

///|
pub fn split_cmd(line : String) -> Array[String] {
  // 支持双引号包裹带空格的参数:update ab n text="Hello World"
  let out : Array[String] = []
  let buf = StringBuilder::new()
  let mut in_q = false
  let mut esc = false
  for c in line.iter() {
    if esc {
      // 保留转义序列原文(含 \" \, 等),由 k=v 解析端统一还原。
      buf.write_char('\\')
      buf.write_char(c)
      esc = false
      continue
    }
    if c == '\\' && in_q {
      esc = true
    } else if c == '"' {
      in_q = !in_q
    } else if (c == ' ' || c == '\t' || c == '\r') && !in_q {
      if buf.to_string().length() > 0 {
        out.push(buf.to_string())
        buf.reset()
      }
    } else {
      buf.write_char(c)
    }
  }
  if buf.to_string().length() > 0 {
    out.push(buf.to_string())
  }
  out
}

///|
/// 查找子串首次出现位置;未找到返回 -1
pub fn index_of_str(s : String, sub : String) -> Int {
  let sl = s.length()
  let subl = sub.length()
  if subl == 0 || subl > sl {
    return -1
  }
  let mut i = 0
  while i + subl <= sl {
    if s.substring(start=i, end=i + subl) == sub {
      return i
    }
    i = i + 1
  }
  -1
}

///|
pub fn parse_op_double(s : String) -> Double {
  let mut result = 0.0
  let mut frac = 0.0
  let mut div = 10.0
  let mut seen_dot = false
  let mut neg = false
  for c in s.iter() {
    let code = c.to_int()
    if code == 45 {
      neg = true
    } else if code >= 48 && code <= 57 {
      if seen_dot {
        frac = frac + (code - 48).to_double() / div
        div = div * 10.0
      } else {
        result = result * 10.0 + (code - 48).to_double()
      }
    } else if code == 46 {
      seen_dot = true
    }
  }
  if neg {
    -(result + frac)
  } else {
    result + frac
  }
}

///|
/// 对由 MBT 重新构建的临时 Project 执行一个人类或 Agent 操作。
///
/// 这个函数不负责持久化。调用方随后必须经 commit_and_render() 把变更写成
/// canonical MBT,再从 MBT 重建画布,避免 Project/命令日志成为第二事实源。

///|
/// place 的 w/h 位置 token 是否数字(首字符 0-9;"-" 单独出现表示默认)。
fn place_token_is_number(t : String) -> Bool {
  t.length() > 0 &&
  ({
    let c0 = t.substring(start=0, end=1)
    c0 >= "0" && c0 <= "9"
  })
}

///|
/// place 参数解析结果(variant/x/y/w/h 槽 + 尾部 k=v 用户参数槽)。
pub(all) struct PlaceArgs {
  variant : String
  x : Double
  y : Double
  w : Double
  h : Double
  args : Map[String, String]
  error : String // 非空 = 解析失败(place_invalid_arg:)
}

///|
/// place 尾部参数解析(issue #18 w/h 槽):CLI(cli_exec 的 place 命令)
/// 与 apply_operation 的 place 臂共用此实现——「双路线解析必须一致」
/// 由单一实现结构性兑现,不再各持副本互相漂移。
/// parts 为 split_cmd 产物,布局:
/// [place, ab, comp, id, variant?, x?, y?, w?, h?, k=v...]。
pub fn parse_place_args(parts : Array[String]) -> PlaceArgs {
  let variant = if parts.length() >= 5 && parts[4] != "-" {
    parts[4]
  } else {
    ""
  }
  let x = if parts.length() >= 6 {
    parse_op_double(parts[5])
  } else {
    0.0
  }
  let y = if parts.length() >= 7 {
    parse_op_double(parts[6])
  } else {
    0.0
  }
  // 位置参数 w/h(issue #18):place 一步到位指定最终尺寸,
  // AgentGate 在最终 bbox 上评估,消除中间态拒绝
  let mut w = 0.0
  let mut h = 0.0
  let mut kv_error = ""
  let mut kv_start = 7
  if parts.length() >= 8 && parts[7].find("=") is None {
    if parts[7] == "-" {
      kv_start = 8
    } else if place_token_is_number(parts[7]) {
      w = parse_op_double(parts[7])
      kv_start = 8
      if parts.length() >= 9 && parts[8].find("=") is None {
        if parts[8] == "-" {
          kv_start = 9
        } else if place_token_is_number(parts[8]) {
          h = parse_op_double(parts[8])
          kv_start = 9
        } else {
          kv_error = "place_invalid_arg:" + parts[8]
        }
      }
    } else {
      kv_error = "place_invalid_arg:" + parts[7]
    }
  }
  // 尾部 k=v 对为用户组件参数槽
  let kv : Map[String, String] = Map::new()
  for i = kv_start; i < parts.length(); i = i + 1 {
    if kv_error != "" {
      break
    }
    match parts[i].find("=") {
      None =>
        if kv_error == "" {
          kv_error = "place_invalid_arg:" + parts[i]
        }
      Some(pos) => {
        let k = parts[i].substring(start=0, end=pos)
        // k=v 值反转义(与 update 路径一致,兑现 split_cmd 注释协议)
        let v = unescape_value(parts[i].substring(start=pos + 1))
        if k != "" {
          kv.set(k, v)
        }
      }
    }
  }
  {
    variant,
    x,
    y,
    w,
    h,
    args: kv,
    error: kv_error,
  }
}

///|
pub fn apply_operation(
  project : Project,
  operation : String,
) -> Result[Unit, String] {
  let parts = split_cmd(operation)
  if parts.length() == 0 {
    return Err("mbt_operation_empty")
  }
  match parts[0] {
    "move" =>
      if parts.length() < 5 {
        Err("mbt_operation_usage:move    ")
      } else {
        match
          project.move_node(
            artboard=parts[1],
            node_id=parts[2],
            x=parse_op_double(parts[3]),
            y=parse_op_double(parts[4]),
          ) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "update" =>
      if parts.length() < 4 {
        Err("mbt_operation_usage:update   k=v")
      } else {
        let kvs : Array[(String, String)] = []
        let mut bad_token = ""
        let mut i = 3
        while i < parts.length() {
          let kv = parts[i]
          let eq = index_of_str(kv, "=")
          if eq > 0 {
            kvs.push(
              (
                kv.substring(start=0, end=eq),
                unescape_value(kv.substring(start=eq + 1)),
              ),
            )
          } else if bad_token == "" {
            // 漏写 "=" 的 token 静默跳过 = Agent 收到成功却什么都没改
            //(与 restyle 的 no_kv_pairs 防御对称)
            bad_token = kv
          }
          i = i + 1
        }
        if bad_token != "" {
          Err("mbt_operation_invalid_kv:" + bad_token)
        } else {
          match project.edit_node(artboard=parts[1], node_id=parts[2], kvs~) {
            Ok(_) => Ok(())
            Err(error) => Err(error)
          }
        }
      }
    "delete" =>
      if parts.length() < 3 {
        Err("mbt_operation_usage:delete  ")
      } else {
        match project.delete_node(artboard=parts[1], node_id=parts[2]) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "copy" =>
      if parts.length() < 4 {
        Err("mbt_operation_usage:copy    [dx] [dy]")
      } else {
        let dx = if parts.length() >= 5 {
          parse_op_double(parts[4])
        } else {
          24.0
        }
        let dy = if parts.length() >= 6 {
          parse_op_double(parts[5])
        } else {
          24.0
        }
        match
          project.copy_node(
            artboard=parts[1],
            node_id=parts[2],
            new_id=parts[3],
            dx~,
            dy~,
          ) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "duplicate" =>
      if parts.length() < 3 {
        Err("mbt_operation_usage:duplicate  ")
      } else {
        match project.duplicate_artboard(src=parts[1], new_name=parts[2]) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "delete-artboard" =>
      if parts.length() < 2 {
        Err("mbt_operation_usage:delete-artboard ")
      } else {
        match project.delete_artboard(id=parts[1]) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "reorder" =>
      if parts.length() < 4 {
        Err("mbt_operation_usage:reorder   front|back|up|down")
      } else {
        match
          project.reorder_node(
            artboard=parts[1],
            node_id=parts[2],
            order=parts[3],
          ) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "flip" =>
      if parts.length() < 4 {
        Err("mbt_operation_usage:flip   h|v|both|none")
      } else {
        match
          project.flip_node(artboard=parts[1], node_id=parts[2], axis=parts[3]) {
          Ok(_) => Ok(())
          Err(error) => Err(error)
        }
      }
    "create" =>
      if parts.length() < 2 {
        Err("mbt_operation_usage:create  [width] [height]")
      } else {
        let width = if parts.length() >= 3 {
          parse_op_double(parts[2])
        } else {
          390.0
        }
        let height = if parts.length() >= 4 {
          parse_op_double(parts[3])
        } else {
          844.0
        }
        let _ = project.create_artboard(name=parts[1], width~, height~)
        Ok(())
      }
    "template" =>
      if parts.length() < 3 {
        Err("mbt_operation_usage:template