///|
/// 配置对象的 JSON 序列化 / 反序列化,用于 CLI 与脚本持久化水印参数。
///
/// 注意:`seed`(Int64)与 `color`(Int)在 JSON 中表示为 Number;
/// Int64 超过 2^53 时会丢失精度(水印种子常规取值远小于该值,可忽略)。

///|
/// 将字段对序列化为 JSON 字符串(三个配置类型共用)。
fn stringify_cfg(pairs : Array[(String, Json)]) -> String {
  Json::stringify(Json::object(Map(pairs)))
}

///|
/// 将 `TextConfig` 序列化为 JSON 字符串。
pub fn text_config_to_json(cfg : TextConfig) -> String {
  stringify_cfg([
    ("text", Json::string(cfg.text)),
    ("x", Json::number(cfg.x.to_double())),
    ("y", Json::number(cfg.y.to_double())),
    ("font_size", Json::number(cfg.font_size.to_double())),
    ("opacity", Json::number(cfg.opacity)),
    ("angle", Json::number(cfg.angle)),
    ("color", Json::number(cfg.color.to_double())),
    ("font", Json::string(cfg.font)),
    ("auto_shrink", Json::boolean(cfg.auto_shrink)),
  ])
}

///|
/// 从 JSON 字符串解析 `TextConfig`;字段缺失或格式错误返回 `None`。
pub fn text_config_from_json(s : String) -> TextConfig? {
  text_cfg_from_obj(@json.parse(s)) catch {
    _ => None
  }
}

///|
fn text_cfg_from_obj(j : Json) -> TextConfig? {
  match j {
    Object(m) => {
      // 向后兼容:旧 JSON 缺 font → "ascii5x7";缺 angle(早期版本无该字段)→ 0.0
      let font = match j_str(m, "font") {
        Some(f) => f
        None => "ascii5x7"
      }
      let angle = match j_dbl(m, "angle") {
        Some(v) => v
        None => 0.0
      }
      let auto_shrink = match j_bool(m, "auto_shrink") {
        Some(v) => v
        None => false
      }
      match
        (
          j_str(m, "text"),
          j_int(m, "x"),
          j_int(m, "y"),
          j_int(m, "font_size"),
          j_dbl(m, "opacity"),
          j_int(m, "color"),
        ) {
        (
          Some(text),
          Some(x),
          Some(y),
          Some(font_size),
          Some(opacity),
          Some(color),
        ) =>
          Some({
            text,
            x,
            y,
            font_size,
            opacity,
            angle,
            color,
            font,
            auto_shrink,
          })
        _ => None
      }
    }
    _ => None
  }
}

///|
/// 将 `DotConfig` 序列化为 JSON 字符串。
pub fn dot_config_to_json(cfg : DotConfig) -> String {
  stringify_cfg([
    ("density", Json::number(cfg.density)),
    ("dot_size", Json::number(cfg.dot_size.to_double())),
    ("seed", Json::number(cfg.seed.to_double())),
    ("opacity", Json::number(cfg.opacity)),
    ("grid_offset_x", Json::number(cfg.grid_offset_x.to_double())),
    ("grid_offset_y", Json::number(cfg.grid_offset_y.to_double())),
  ])
}

///|
/// 从 JSON 字符串解析 `DotConfig`;字段缺失或格式错误返回 `None`。
pub fn dot_config_from_json(s : String) -> DotConfig? {
  dot_cfg_from_obj(@json.parse(s)) catch {
    _ => None
  }
}

///|
fn dot_cfg_from_obj(j : Json) -> DotConfig? {
  match j {
    Object(m) =>
      match
        (
          j_dbl(m, "density"),
          j_int(m, "dot_size"),
          j_i64(m, "seed"),
          j_dbl(m, "opacity"),
          j_int(m, "grid_offset_x"),
          j_int(m, "grid_offset_y"),
        ) {
        (
          Some(density),
          Some(dot_size),
          Some(seed),
          Some(opacity),
          Some(grid_offset_x),
          Some(grid_offset_y),
        ) =>
          Some({
            density,
            dot_size,
            seed,
            opacity,
            grid_offset_x,
            grid_offset_y,
          })
        _ => None
      }
    _ => None
  }
}

///|
/// 将 `TiledConfig` 序列化为 JSON 字符串。
pub fn tiled_config_to_json(cfg : TiledConfig) -> String {
  stringify_cfg([
    ("text", Json::string(cfg.text)),
    ("font_size", Json::number(cfg.font_size.to_double())),
    ("spacing_x", Json::number(cfg.spacing_x.to_double())),
    ("spacing_y", Json::number(cfg.spacing_y.to_double())),
    ("opacity", Json::number(cfg.opacity)),
    ("color", Json::number(cfg.color.to_double())),
    ("font", Json::string(cfg.font)),
  ])
}

///|
/// 从 JSON 字符串解析 `TiledConfig`;字段缺失或格式错误返回 `None`。
pub fn tiled_config_from_json(s : String) -> TiledConfig? {
  tiled_cfg_from_obj(@json.parse(s)) catch {
    _ => None
  }
}

///|
fn tiled_cfg_from_obj(j : Json) -> TiledConfig? {
  match j {
    Object(m) => {
      let font = match j_str(m, "font") {
        Some(f) => f
        None => "ascii5x7" // 向后兼容:旧 JSON 无 font 字段
      }
      match
        (
          j_str(m, "text"),
          j_int(m, "font_size"),
          j_int(m, "spacing_x"),
          j_int(m, "spacing_y"),
          j_dbl(m, "opacity"),
          j_int(m, "color"),
        ) {
        (
          Some(text),
          Some(font_size),
          Some(spacing_x),
          Some(spacing_y),
          Some(opacity),
          Some(color),
        ) =>
          Some({ text, font_size, spacing_x, spacing_y, opacity, color, font, })
        _ => None
      }
    }
    _ => None
  }
}

///|
fn j_str(m : Map[String, Json], k : String) -> String? {
  match m.get(k) {
    Some(j) =>
      match j {
        String(sv) => Some(sv)
        _ => None
      }
    _ => None
  }
}

///|
fn j_int(m : Map[String, Json], k : String) -> Int32? {
  match m.get(k) {
    Some(j) =>
      match j {
        Number(d, ..) => Some(d.to_int())
        _ => None
      }
    _ => None
  }
}

///|
fn j_bool(m : Map[String, Json], k : String) -> Bool? {
  match m.get(k) {
    Some(True) => Some(true)
    Some(False) => Some(false)
    _ => None
  }
}

///|
fn j_dbl(m : Map[String, Json], k : String) -> Double? {
  match m.get(k) {
    Some(j) =>
      match j {
        Number(d, ..) => Some(d)
        _ => None
      }
    _ => None
  }
}

///|
fn j_i64(m : Map[String, Json], k : String) -> Int64? {
  match m.get(k) {
    Some(j) =>
      match j {
        Number(d, ..) => Some(d.to_int64())
        _ => None
      }
    _ => None
  }
}