/// 渲染桥接层:从文档模型生成 SVG。
/// 渲染层不承担正确性责任——能走到这里的文档都已通过(或被人类路线明确
/// 接受为)验证层的谓词检查。
///
/// v2 现代视觉:elevation 阴影令牌、系统字体栈、多行文本、浅底衬托白卡。

///|
/// 文档 → SVG 字符串(根矩形即画布尺寸,浅底)
///
/// surface 为画布底色令牌值:主题切换后由 Project 侧传入当前主题的
/// surface(如 nord 的 #2E3440);默认浅色 #F6F7F9 保持既有文档兼容。
pub fn render_svg(
  doc : Document,
  surface? : String = "#F6F7F9",
  frame? : String = "#C9CFDA",
) -> String {
  let layout = solve(doc)
  let root_rect : Rect = match layout.rect_of(doc.root_id) {
    Some(r) => r
    None => Rect::new(0.0, 0.0, 390.0, 844.0)
  }
  let sb = StringBuilder::new()
  sb.write_string(
    "",
  )
  // 现代视觉基座:字体栈 + elevation 阴影令牌
  sb.write_string(
    "",
  )
  // Paint v2:渐变 defs(linear:#a:#b@deg / radial:#a:#b)
  let defs = collect_gradients(doc)
  if defs != "" {
    sb.write_string(defs)
  }
  sb.write_string(
    "",
  )
  sb.write_string(
    "",
  )
  // SVG viewBox 自带裁剪(root rect 与 viewBox 同尺寸),不再用 clipPath 包裹:
  // WebKit 对 clip-path 组内元素的 hit-test 会整体失效,原型交互点击会穿透。
  render_node(sb, doc, doc.root_id, layout, surface~, frame~)
  sb.write_string("")
  sb.to_string()
}

///|
fn render_node(
  sb : StringBuilder,
  doc : Document,
  id : String,
  layout : SolvedLayout,
  surface~ : String,
  frame~ : String,
) -> Unit {
  match doc.nodes.get(id) {
    None => ()
    // 隐藏节点:整棵子树不参与渲染(文档中仍保留,可随时 visible=true 恢复)
    Some(n) if !n.style.visible => ()
    Some(n) =>
      match layout.rect_of(id) {
        None => ()
        Some(r) => {
          // 每个节点包一层 :编辑器据此把 DOM 元素与场景图节点关联
          sb.write_string("")
          // issue #22:radius=999 只在方形节点上是圆;非方形必须钳制为
          // min(r, min(w,h)/2) 并同时输出 rx/ry,否则 SVG 各自半边钳制
          // 产出极端椭圆(420×40 搜索框 rx=210/ry=20)。
          let rr = if n.style.corner_radius > r.width / 2.0 ||
            n.style.corner_radius > r.height / 2.0 {
            if r.width < r.height { r.width / 2.0 } else { r.height / 2.0 }
          } else {
            n.style.corner_radius
          }
          match n.kind {
            Text => render_text_node(sb, n, r)
            Image => {
              // text 承载 src:http(s)/data URL → 真 (圆角裁剪);
              // 空/非 URL → 占位渲染(浅灰底 + 山形/太阳图标)
              let src = xml_escape(n.text)
              if has_url_prefix(src) {
                if n.style.corner_radius > 0.0 {
                  sb.write_string(
                    "",
                  )
                  sb.write_string(
                    "",
                  )
                } else {
                  sb.write_string(
                    "",
                  )
                }
              } else {
                // 图片占位:浅灰底 + 山形/太阳图标 + 细边框,任何尺寸下可辨识
                sb.write_string(
                  "",
                )
                let ic = r.width / 6.0
                if ic > 2.0 {
                  let cx = r.x + r.width / 2.0
                  let cy = r.y + r.height / 2.0
                  let iw = ic * 2.4
                  let ih = ic * 1.5
                  sb.write_string(
                    "",
                  )
                  sb.write_string(
                    "",
                  )
                }
              }
            }
            _ => {
              // Frame / Group / Rect
              sb.write_string(
                " 0.0 {
                  n.style.stroke_width
                } else {
                  1.0
                }
                sb.write_string(
                  " stroke=\"\{n.style.stroke}\" stroke-width=\"\{sw}\"",
                )
                match dash_array(n.style.stroke_dash) {
                  Some(da) => sb.write_string(" stroke-dasharray=\"\{da}\"")
                  None => ()
                }
              } else if n.style.fill == "none" && n.text == "" {
                // 无填充无文字的容器:细虚线边界,避免"幽灵组件"融进背景
                sb.write_string(
                  " stroke=\"\{frame}\" stroke-width=\"1\" stroke-dasharray=\"4 3\"",
                )
              }
              if n.style.opacity != 1.0 {
                sb.write_string(" opacity=\"\{n.style.opacity}\"")
              }
              // 单一 filter 属性:shadow+blur 同设时引用合并滤镜 def(fx_,
              // collect_gradients 生成)——输出两个同名属性是 XML 非良构,
              // 浏览器取首个导致 blur 静默丢失,严格解析器直接拒绝整个 SVG
              if n.style.shadow != "none" && n.style.blur > 0.0 {
                sb.write_string(" filter=\"url(#fx_\{svg_id_tag(n.id)})\"")
              } else if n.style.shadow != "none" {
                sb.write_string(" filter=\"url(#\{n.style.shadow})\"")
              } else if n.style.blur > 0.0 {
                sb.write_string(" filter=\"url(#blur_\{svg_id_tag(n.id)})\"")
              }
              // 单一 transform 属性:变换列表从右向左作用,
              // flip 矩阵写在左侧即左乘 rotate(先转后翻)
              let transforms : Array[String] = []
              if n.style.flip != "none" {
                let cx = r.x + r.width / 2.0
                let cy = r.y + r.height / 2.0
                let m = match n.style.flip {
                  "h" => "-1,1"
                  "v" => "1,-1"
                  _ => "-1,-1"
                }
                transforms.push(
                  "translate(\{cx} \{cy}) scale(\{m}) translate(\{-cx} \{-cy})",
                )
              }
              if n.style.rotate != 0.0 {
                let cx = r.x + r.width / 2.0
                let cy = r.y + r.height / 2.0
                transforms.push("rotate(\{n.style.rotate} \{cx} \{cy})")
              }
              if transforms.length() > 0 {
                sb.write_string(" transform=\"\{jstr8(transforms, " ")}\"")
              }
              sb.write_string("/>")
              // 带文字的形状(按钮 / 输入框占位符):默认水平垂直居中,
              // align=left/right 时改为左/右对齐到内边距
              if n.text != "" {
                let baseline = r.y + (r.height + n.style.font_size * 0.7) / 2.0
                let pad = 12.0
                let (tx, anchor) = match n.style.text_align {
                  "left" => (r.x + pad, " start")
                  "right" => (r.x + r.width - pad, " end")
                  _ => (r.x + r.width / 2.0, " middle")
                }
                sb.write_string(
                  "\{xml_escape(n.text)}",
                )
              }
            }
          }
          for cid in n.children.iter() {
            render_node(sb, doc, cid, layout, surface~, frame~)
          }
          sb.write_string("")
        }
      }
  }
}

///|
/// 文本节点:支持 \n 多行、自定义行高、字距
fn render_text_node(sb : StringBuilder, n : Node, r : Rect) -> Unit {
  let lines = split_lines(n.text)
  let lh = n.style.font_size * n.style.line_height
  let first_baseline = r.y + n.style.font_size * 1.0
  // 水平对齐:left(默认,x 起点)/ center / right(x 终点 + text-anchor)
  let (tx, anchor) = match n.style.text_align {
    "center" => (r.x + r.width / 2.0, " text-anchor=\"middle\"")
    "right" => (r.x + r.width, " text-anchor=\"end\"")
    _ => (r.x, "")
  }
  sb.write_string(
    "\{xml_escape(lines[0])}")
  } else {
    sb.write_string(">")
    for i in 0..\{xml_escape(lines[i])}",
      )
    }
    sb.write_string("")
  }
}

///|
/// 描边线型 → SVG stroke-dasharray 值;solid 返回 None(不输出该属性)
fn dash_array(dash : String) -> String? {
  match dash {
    "dashed" => Some("6 4")
    "dotted" => Some("1.5 3")
    _ => None
  }
}

///|
/// elevation 阴影令牌参数 (dx, dy, stdDeviation, flood-opacity)——
/// 与 render_svg 顶部 defs 的 e1/e2/e3 定义一一对应;未知令牌取 e2 档兜底
fn shadow_token_params(tok : String) -> (Double, Double, Double, Double) {
  match tok {
    "e1" => (0.0, 1.0, 2.0, 0.07)
    "e2" => (0.0, 4.0, 10.0, 0.10)
    "e3" => (0.0, 14.0, 28.0, 0.16)
    _ => (0.0, 4.0, 10.0, 0.10)
  }
}

///|
/// 按 \n 拆行(无换行返回单元素数组)
fn split_lines(s : String) -> Array[String] {
  let out : Array[String] = []
  let cur = StringBuilder::new()
  for c in s.iter() {
    if c == '\n' {
      out.push(cur.to_string())
      cur.reset()
    } else {
      cur.write_char(c)
    }
  }
  out.push(cur.to_string())
  out
}

///|
fn svg_kind_of(n : Node) -> String {
  match n.kind {
    Frame => "frame"
    Group => "group"
    Rect => "rect"
    Text => "text"
    Image => "image"
  }
}

///|
/// Paint DSL 引用:"#hex" 原样;"linear:#a:#b@deg"/"radial:#a:#b" → url(#g_tag)
fn paint_ref(fill : String, node_id : String) -> String {
  if has_prefix(fill, "linear:") || has_prefix(fill, "radial:") {
    return "url(#g_\{svg_id_tag(node_id)})"
  }
  fill
}

///|
/// 文字色渐变引用(def 以 _t 后缀注册)
fn paint_ref_t(color : String, node_id : String) -> String {
  if has_prefix(color, "linear:") || has_prefix(color, "radial:") {
    return "url(#g_\{svg_id_tag(node_id)}_t)"
  }
  color
}

///|
/// 节点 id → SVG id 安全 tag

///|
/// 图片 src 判定:http(s)/data URL 才渲染真 ,其余(含空)走占位
fn has_url_prefix(s : String) -> Bool {
  let http = "http://"
  let https = "https://"
  let data = "data:"
  if s.length() >= http.length() &&
    s.substring(start=0, end=http.length()) == http {
    true
  } else if s.length() >= https.length() &&
    s.substring(start=0, end=https.length()) == https {
    true
  } else {
    s.length() >= data.length() &&
    s.substring(start=0, end=data.length()) == data
  }
}

///|
fn svg_id_tag(id : String) -> String {
  let sb = StringBuilder::new()
  for c in id.iter() {
    let code = c.to_int()
    if (code >= 48 && code <= 57) ||
      (code >= 65 && code <= 90) ||
      (code >= 97 && code <= 122) {
      sb.write_char(c)
    } else {
      sb.write_char('_')
    }
  }
  sb.to_string()
}

///|
/// 遍历文档收集全部渐变,生成  段
fn collect_gradients(doc : Document) -> String {
  let sb = StringBuilder::new()
  for id in doc.nodes.keys() {
    match doc.get(id) {
      None => ()
      Some(n) => {
        let g = gradient_def(n.style.fill, id)
        if g != "" {
          sb.write_string(g)
        }
        // 文字色也可能是渐变
        let tg = gradient_def(n.style.text_color, id + "_t")
        if tg != "" {
          sb.write_string(tg)
        }
      }
    }
  }
  // blur 滤镜 defs;shadow+blur 同设时生成合并滤镜(先模糊、再对模糊结果投阴影)
  for id in doc.nodes.keys() {
    match doc.get(id) {
      None => ()
      Some(n) => {
        let has_blur = n.style.blur > 0.0
        let has_shadow = n.style.shadow != "none"
        if has_blur && has_shadow {
          let (dx, dy, std, op) = shadow_token_params(n.style.shadow)
          sb.write_string(
            "",
          )
        } else if has_blur {
          sb.write_string(
            "",
          )
        }
      }
    }
  }
  let inner = sb.to_string()
  if inner == "" {
    ""
  } else {
    "\{inner}"
  }
}

///|
/// 单个 Paint DSL → 渐变 def;非渐变返回 ""
fn has_prefix(s : String, pre : String) -> Bool {
  if s.length() < pre.length() {
    return false
  }
  s.substring(start=0, end=pre.length()) == pre
}

///|
fn gradient_def(paint : String, id : String) -> String {
  if has_prefix(paint, "linear:") {
    let body = paint.substring(start=7, end=paint.length())
    let (c1, c2, deg) = parse_two_colors_angle(body)
    let tag = "g_\{svg_id_tag(id)}"
    // 角度 → (x1,y1,x2,y2):0°=→ 90°=↓
    let rad = deg * 3.14159265 / 180.0
    let x2 = (0.5 + 0.5 * rad_cos(rad)).to_string()
    let y2 = (0.5 + 0.5 * rad_sin(rad)).to_string()
    let x1 = (0.5 - 0.5 * rad_cos(rad)).to_string()
    let y1 = (0.5 - 0.5 * rad_sin(rad)).to_string()
    return ""
  }
  if has_prefix(paint, "radial:") {
    let body = paint.substring(start=7, end=paint.length())
    let (c1, c2, _deg) = parse_two_colors_angle(body)
    let tag = "g_\{svg_id_tag(id)}"
    return ""
  }
  ""
}

///|
/// "a@45" / "a" / "a:b@45" → (c1, c2, deg)
fn parse_two_colors_angle(body : String) -> (String, String, Double) {
  // 先拆角度
  let mut color_part = body
  let mut deg = 90.0
  let at = index_of_at(body)
  if at >= 0 {
    color_part = body.substring(start=0, end=at)
    deg = parse_deg(body.substring(start=at + 1, end=body.length()))
  }
  let parts = split_char(color_part, ':')
  let c1 = if parts.length() > 0 { parts[0] } else { "#cccccc" }
  let c2 = if parts.length() > 1 { parts[1] } else { c1 }
  (c1, c2, deg)
}

///|
/// 找 @ 首次出现位置,未找到 -1
fn index_of_at(s : String) -> Int {
  let mut i = 0
  for c in s.iter() {
    if c == '@' {
      return i
    }
    i = i + 1
  }
  -1
}

///|
fn parse_deg(s : String) -> Double {
  let mut v = 0.0
  for c in s.iter() {
    let code = c.to_int()
    if code >= 48 && code <= 57 {
      v = v * 10.0 + (code - 48).to_double()
    }
  }
  v
}

///|
fn split_char(s : String, sep : Char) -> Array[String] {
  let out : Array[String] = []
  let cur = StringBuilder::new()
  for c in s.iter() {
    if c == sep {
      out.push(cur.to_string())
      cur.reset()
    } else {
      cur.write_char(c)
    }
  }
  out.push(cur.to_string())
  out
}

///|
fn rad_cos(r : Double) -> Double {
  // cos 的泰勒近似(-π..π 精度足够渲染视觉)
  let x = wrap_angle(r)
  let x2 = x * x
  1.0 - x2 / 2.0 + x2 * x2 / 24.0 - x2 * x2 * x2 / 720.0
}

///|
fn rad_sin(r : Double) -> Double {
  let x = wrap_angle(r)
  let x2 = x * x
  x - x * x2 / 6.0 + x * x2 * x2 / 120.0
}

///|
fn wrap_angle(r : Double) -> Double {
  // 归一到 -π..π
  let pi = 3.14159265
  let mut x = r
  while x > pi {
    x = x - 2.0 * pi
  }
  while x < -pi {
    x = x + 2.0 * pi
  }
  x
}

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