// facade 出口层 —— 移植自 rust jeeflow-facade/src/lib.rs L20-402(C1/C5/C7/C22 强制层)

// ⚠️ builtin Json Number 是 Double:>2^53 的雪花 id 在构造期就丢精度,

//    故行 VO 构造时 id 即以十进制串落键(stringify_ids 留作递归安全网,覆盖嵌套/复数数组)。



///|

pub const CODE_SUCCESS : Int64 = 0L



///|

pub const CODE_ERROR : Int64 = 99999999L



///|

pub fn ok(data : Json?) -> Json {

  let obj = @json.empty_object()

  @json.obj_set(obj, "code", @json.number_of(CODE_SUCCESS))

  @json.obj_set(obj, "msg", @json.string_of("成功"))

  @json.obj_set(obj, "data", match data {

    Some(d) => d

    None => @json.null_json()

  })

  obj

}



///|

pub fn ok_data(data : Json) -> Json {

  ok(Some(data))

}



///|

pub fn err(msg : String) -> Json {

  let obj = @json.empty_object()

  @json.obj_set(obj, "code", @json.number_of(CODE_ERROR))

  @json.obj_set(obj, "msg", @json.string_of(msg))

  obj

}



///|

pub fn is_ok(env : Json) -> Bool {

  match @json.get_i64(env, "code") {

    Some(c) => c == 0L

    None => false

  }

}



///|

pub fn envelope_data(env : Json) -> Json? {

  @json.get(env, "data")

}



///|

fn to_camel(s : String) -> String {

  let b = StringBuilder()

  let mut upper_next = false

  for ch in s {

    if ch == '_' {

      upper_next = true

    } else if upper_next {

      let code = ch.to_int()

      if code >= 97 && code <= 122 {

        b.write_char((code - 32).unsafe_to_char())

      } else {

        b.write_char(ch)

      }

      upper_next = false

    } else {

      b.write_char(ch)

    }

  }

  b.to_string()

}



///|

/// 自由形态嵌套 map 键(VO 顶层 camelCase,内部保留原键)

fn is_opaque_value_key(k : String) -> Bool {

  k == "jsonObject" ||

  k == "json_object" ||

  k == "ext" ||

  k == "instanceExt" ||

  k == "instance_ext" ||

  k == "variable" ||

  k == "taskFormData" ||

  k == "task_form_data" ||

  k == "formData" ||

  k == "form_data" ||

  k == "nodeProgress" ||

  k == "node_progress"

}



///|

fn to_camel_json_inner(val : Json, convert_keys : Bool) -> Json {

  match val {

    Json::Object(map) => {

      let out = @json.empty_object()

      for k, v in map {

        let new_key = if convert_keys { to_camel(k) } else { k }

        let child_convert = convert_keys && !is_opaque_value_key(k) && !is_opaque_value_key(new_key)

        @json.obj_set(out, new_key, to_camel_json_inner(v, child_convert))

      }

      out

    }

    Json::Array(arr) => {

      let out = @json.empty_array()

      for v in arr {

        @json.arr_push(out, to_camel_json_inner(v, convert_keys))

      }

      out

    }

    other => other

  }

}



///|

pub fn to_camel_json(val : Json) -> Json {

  to_camel_json_inner(val, true)

}



///|

/// C1:id/*Id/*_id 单数键值字符串化(递归);ids/*Ids/*_ids 复数键逐元素

pub fn stringify_ids(val : Json) -> Json {

  match val {

    Json::Object(map) => {

      let out = @json.empty_object()

      for k, v in map {

        if k == "id" || k.has_suffix("Id") || k.has_suffix("_id") {

          @json.obj_set(out, k, stringify_id_value(v))

        } else if k == "ids" || k.has_suffix("Ids") || k.has_suffix("_ids") {

          @json.obj_set(out, k, stringify_id_array(v))

        } else {

          @json.obj_set(out, k, stringify_ids(v))

        }

      }

      out

    }

    Json::Array(arr) => {

      let out = @json.empty_array()

      for v in arr {

        @json.arr_push(out, stringify_ids(v))

      }

      out

    }

    other => other

  }

}



///|

fn stringify_id_value(v : Json) -> Json {

  match v {

    // id 字符串化走 repr 原文优先(>2^53 防 double 取整,D-M5-3)

    Json::Number(n, repr~) =>

      match repr {

        Some(s) => @json.string_of(s)

        None => @json.string_of(n.to_int64().to_string())

      }

    other => other

  }

}



///|

fn stringify_id_array(v : Json) -> Json {

  match v {

    Json::Array(arr) => {

      let out = @json.empty_array()

      for item in arr {

        @json.arr_push(out, stringify_id_value(item))

      }

      out

    }

    other => stringify_id_value(other)

  }

}



///|

/// C7:时间键格式化(NOW() 占位 / ISO T → 空格)

pub fn format_time_fields(val : Json) -> Json {

  match val {

    Json::Object(map) => {

      let out = @json.empty_object()

      for k, v in map {

        if k.has_suffix("Time") || k.has_suffix("_time") {

          @json.obj_set(out, k, format_time_value(v))

        } else {

          @json.obj_set(out, k, format_time_fields(v))

        }

      }

      out

    }

    Json::Array(arr) => {

      let out = @json.empty_array()

      for v in arr {

        @json.arr_push(out, format_time_fields(v))

      }

      out

    }

    other => other

  }

}



///|

fn format_time_value(v : Json) -> Json {

  match v {

    Json::String(s) => {

      if s == "NOW()" || s == "NOW" {

        return @json.string_of(@model.current_time_str())

      }

      // ISO 'T' → 空格(19 位近似可直接换)

      if s.length() >= 19 {

        let bytes = s.to_array()

        if bytes[10] == 'T' {

          let approx = s[0:10].to_owned() + " " + s[11:19].to_owned()

          return @json.string_of(approx)

        }

        // 毫秒尾巴 ".fff" 截断

        if s.length() > 19 && s.to_array()[10] == ' ' {

          return @json.string_of(s[0:19].to_owned())

        }

      }

      @json.string_of(s)

    }

    other => other

  }

}



///|

/// 出口统一变换:camelCase → id 字符串化 → 时间格式化

pub fn transform_output(val : Json) -> Json {

  format_time_fields(stringify_ids(to_camel_json(val)))

}



///|

/// 分页五键(C22)

pub fn[T] page_to_json(page : @model.PageResult[T], row_json : (T) -> Json) -> Json {

  let obj = @json.empty_object()

  @json.obj_set(obj, "pageNum", @json.number_of(page.page_num()))

  @json.obj_set(obj, "pageSize", @json.number_of(page.page_size()))

  @json.obj_set(obj, "recordCount", @json.number_of(page.record_count()))

  @json.obj_set(obj, "totalPage", @json.number_of(page.total_page()))

  let rows = @json.empty_array()

  for r in page.rows() {

    @json.arr_push(rows, row_json(r))

  }

  @json.obj_set(obj, "rows", rows)

  obj

}



///|

pub fn parse_graph(content : String) -> Json? {

  let t = content.trim(chars=" \t\r\n").to_owned()

  if t.is_empty() {

    return None

  }

  // parse_json 是 `-> Json raise JeeflowError`:坏 JSON ⇒ None(旧 Err 臂)
  try {
    Some(@json.parse_json(t))
  } catch {
    _ => None
  }
}
///|

pub fn parse_json_map(s : String?) -> Map[String, Json] {

  let out : Map[String, Json] = Map([])

  match s {

    Some(raw) => {

      let t = raw.trim(chars=" \t\r\n").to_owned()

      if !t.is_empty() {

        // 坏 JSON 或非对象 ⇒ 空 map(旧 `_ => ()` / `Err ⇒ ()` 两臂合成一条)
        try {
          match @json.parse_json(t) {
            Json::Object(m) =>
              for k, v in m {
                out[k] = v
              }
            _ => ()
          }
        } catch {
          _ => ()
        }
      }

    }

    None => ()

  }

  out

}



///|

/// 表单数据派生(issues/15):prefix 键带前缀 + 去前缀双份

pub fn form_data_of_map(vars : Map[String, Json], prefix : String) -> Json {

  let out = @json.empty_object()

  for k, v in vars {

    if k.has_prefix(prefix) {

      @json.obj_set(out, k, v)

      @json.obj_set(out, k[prefix.length():].to_owned(), v)

    }

  }

  out

}



///|

pub fn form_data_of_flow(vars : @json.FlowData, prefix : String) -> Json {

  form_data_of_map(vars.inner(), prefix)

}



///|

pub fn flow_data_to_object(vars : @json.FlowData) -> Json {

  vars.to_object_json()

}



///|

pub fn first_task_node_id(json_object : Json?) -> String? {

  match json_object {

    Some(obj) =>

      match @json.get(obj, "nodes") {

        Some(Json::Array(nodes)) => {

          for n in nodes {

            match @json.get(n, "type") {

              Some(Json::String(t)) =>

                if t == "snaker:task" {

                  return @json.as_str(@json.get(n, "id").unwrap_or(@json.null_json()))

                }

              _ => ()

            }

          }

          None

        }

        _ => None

      }

    None => None

  }

}



///|

fn opt_json(s : String?) -> Json {

  match s {

    Some(v) => @json.string_of(v)

    None => @json.null_json()

  }

}



///|

fn num_json(n : Int) -> Json {

  @json.number_of(n.to_int64())

}



///|

fn opt_num_json(n : Int?) -> Json {

  match n {

    Some(v) => num_json(v)

    None => @json.null_json()

  }

}



///|

fn i64_json(n : Int64) -> Json {

  @json.string_of(n.to_string())

}



///|

fn opt_i64_json(n : Int64?) -> Json {

  match n {

    Some(v) => i64_json(v)

    None => @json.null_json()

  }

}