// 缺失元素一键补缺(missing → autofill 闭环)。
//
// generate/autofix 的报告(infer_missing)告诉 Agent "还缺什么"——本模块把
// 报告变成行动:按页面类型把缺失元素物化为真实节点(组件 + 语义文本)。
// 闭环性质:补缺物料的文本("忘记密码?""记住我""刷新")恰好满足检测器的
// 关键词判据——补完后再跑 infer_missing 归零,天然幂等。
//
// 放置策略(确定性):新节点堆叠在当前内容底缘之下(16px 步进、24px 边距),
// search_bar 锚定 app_bar 之下(语义位置优先);与既有内容的重叠交由
// auto_fix 的重叠/溢出清偿处理——布局归引擎,与本仓库生成式哲学一致。

///|
/// 缺失元素 → 物化配方。element 与 infer_missing 的 element 字段一一对应。
fn autofill_recipe(
  element : String,
  content_w : Double,
) -> (String, String, Double, Double, String, Bool)? {
  let cw = if content_w < 100.0 { 342.0 } else { content_w }
  match element {
    // auth:密码框下方的右对齐链接 + 底部注册链接 + 记住我
    "forgot_password" =>
      Some(("body_text", "忘记密码?", cw, 20.0, "#4B6BFB", true))
    "signup_link" =>
      Some(("body_text", "没有账号?立即注册", cw, 20.0, "#4B6BFB", false))
    "remember_me" =>
      Some(("checkbox", "记住我", 160.0, 44.0, "#111111", false))
    // dashboard / list / profile / settings 操作
    "refresh" => Some(("button", "刷新", 120.0, 44.0, "", false))
    "filter" => Some(("button", "筛选", 120.0, 44.0, "", false))
    "edit_profile" => Some(("button", "编辑资料", 160.0, 44.0, "", false))
    "logout" => Some(("button", "退出登录", cw, 48.0, "", false))
    // list 顶部搜索(语义位置:调用方锚定 app_bar 之下)
    "search_bar" => Some(("text_input", "搜索", cw, 44.0, "", true))
    // 通用加载状态(灰字,弱存在感)
    "loading_state" =>
      Some(("body_text", "加载中…", cw, 20.0, "#6B7280", false))
    // missing_validation(error_states)是状态语义,不是可放置节点——
    // 由组件状态多态(define_component_state)承载,本配方跳过
    _ => None
  }
}

///|
/// 一键补缺:infer_missing → 逐元素物化 → auto_fix 清偿重叠/溢出 →
/// 报告(placed/skipped/补后缺失数)。报告驱动、可重入:报告里的每个
/// 可物化元素都落地后,后续 infer_missing 对该画板归零。
pub fn Project::autofill_missing(
  self : Project,
  artboard~ : String,
) -> Result[String, String] {
  let ab = match self.artboards.get(artboard) {
    None => return Err("unknown_artboard")
    Some(meta) => meta
  }
  let missing_json = match self.infer_missing(artboard=artboard) {
    Ok(j) => j
    Err(e) => return Err(e)
  }
  let page_type = extract_field(missing_json, "page_type")
  // 解析缺失元素清单(element 字段;与 infer_missing 的 JSON 形状耦合,
  // 单一事实源在 infer_missing——这里只消费)。扫描限定在 "missing":[ … ]
  // 数组片段内:裸扫整个信封的话,信封其他字段一旦也带 element 键就会
  // 静默重复收集,放大放置冲突。
  let elements = parse_missing_envelope(missing_json)
  // 内容底缘(确定性放置光标)+ 现有内容矩形表(锚定占用检查用)
  let layout = solve(ab.doc)
  let mut bottom = 0.0
  let mut app_bar_bottom = -1.0
  let occupied : Array[Rect] = []
  for id in ab.doc.nodes.keys() {
    match ab.doc.nodes.get(id) {
      Some(n) => {
        // root 是全画布 wrapper——计入会把放置光标顶到画布外(844+16),
        // 物化节点全部越界;底缘只看真实内容节点
        if id == "root" {
          continue
        }
        match layout.rect_of(id) {
          Some(r) => {
            if r.y + r.height > bottom {
              bottom = r.y + r.height
            }
            if n.component == "app_bar" && r.y + r.height > app_bar_bottom {
              app_bar_bottom = r.y + r.height
            }
            occupied.push(r)
          }
          None => ()
        }
      }
      None => ()
    }
  }
  let w = ab.width
  let content_w = w - 48.0
  let placed : Array[String] = []
  let skipped : Array[String] = []
  let mut y_cursor = bottom + 16.0
  for element in elements.iter() {
    match autofill_recipe(element, content_w) {
      None => skipped.push(af_skip_json(element, "state_semantics_not_placeable"))
      Some(recipe) => {
        let (component, text, node_w, node_h, color, anchor_top) = recipe
        let node_id = element
        // 同 id 预检:place_component 的根路径会丢弃 insert_node 的重复 id
        // 失败(返回值未消费),既有节点随后被 edit_node 静默改写——用户
        // 数据破坏 + 假成功报告。命中即跳过,绝不覆盖既有内容。
        if ab.doc.has(node_id) {
          skipped.push(af_skip_json(element, "id_taken"))
          continue
        }
        // search_bar 等锚定元素语义位置在 app_bar 之下;锚定前先查带区
        // 占用——候选矩形与既有内容(或本轮已放置节点)相交即退回底缘
        // 光标。锚定是语义偏好,不是插队许可:硬塞进占用带区会触发
        // auto_fix 的下移级联,把既有内容推挤改写。
        let mut y = y_cursor
        let mut anchored_rect : Rect? = None
        if anchor_top && app_bar_bottom >= 0.0 {
          let cand = Rect::new(24.0, app_bar_bottom + 12.0, node_w, node_h)
          let mut taken = false
          for r in occupied.iter() {
            if cand.intersects(r) {
              taken = true
              break
            }
          }
          if !taken {
            y = cand.y
            anchored_rect = Some(cand)
          }
        }
        // 画布高度钳制:底缘光标越过画布(近满页)时节点会落到画布外,
        // 而 fix_overflow 对负剩余空间保持原尺寸、清偿不动——如实跳过,
        // 不制造永久视觉债。
        if y + node_h > ab.height {
          skipped.push(af_skip_json(element, "no_room"))
          continue
        }
        let placed_ok = match
          self.place_component(
            artboard=artboard,
            component_id=component,
            instance_id=node_id,
            x=24.0,
            y=y,
            w=node_w,
            h=node_h,
          ) {
          Err(_) => false
          Ok(_) => true
        }
        if placed_ok {
          if text != "" {
            let _ = self.edit_node(
              artboard=artboard,
              node_id=node_id,
              kvs=[("text", text)],
            )
          }
          if color != "" {
            let kv = if component == "body_text" {
              [("text_color", color)]
            } else {
              [("fill", color)]
            }
            let _ = self.edit_node(artboard=artboard, node_id=node_id, kvs=kv)
          }
          placed.push(
            "{\"element\":\"" +
            json_escape(element) +
            "\",\"node\":\"" +
            json_escape(node_id) +
            "\",\"component\":\"" +
            component +
            "\",\"y\":" +
            y.to_string() +
            "}",
          )
          match anchored_rect {
            // 锚定放置注册占用(防后续锚定元素同带区叠放),但**不推进**
            // 底缘光标——锚定 y 在内容带中部,推进会让后续物化节点落进
            // 既有内容里
            Some(r) => occupied.push(r)
            // 只有底缘光标放置才推进光标
            None => y_cursor = y + node_h + 16.0
          }
        } else {
          skipped.push(af_skip_json(element, "place_failed"))
        }
      }
    }
  }
  // issue #25-B4(规则化):mock 数据缺口只报 id,内容由 Agent 填充
  let mock_gap_ids = self.mock_data_gaps(artboard=artboard)
  let mock_gaps_json = json_string_array(mock_gap_ids)
  // 清偿:新节点与既有内容的重叠/溢出/触控规范归 auto_fix
  let autofix_json = match self.auto_fix(artboard=artboard) {
    Ok(json) => json
    Err(_) => "{\"fixes\":0}"
  }
  // 顶层 ok 如实采纳 autofix 结果:清偿不动(remaining > 0)时不再谎报成功
  let autofix_ok = !autofix_json.contains("\"ok\":false")
  let after = match self.infer_missing(artboard=artboard) {
    Ok(j) => j
    Err(e) => return Err(e)
  }
  let after_count = match after.find("\"missing_count\":") {
    Some(i) => {
      let rest = after.substring(start=i + 16)
      let mut j = 0
      while j < rest.length() {
        match rest.get_char(j) {
          Some(c) => if c >= '0' && c <= '9' { j = j + 1 } else { break }
          None => break
        }
      }
      rest.substring(start=0, end=j).to_string()
    }
    None => "?"
  }
  let pj = placed.join(",")
  let sj = skipped.join(",")
  Ok(
    "{\"ok\":\{autofix_ok},\"op\":\"autofill\",\"artboard\":\"\{json_escape(artboard)}\",\"page_type\":\"\{page_type}\",\"placed_count\":\{placed.length()},\"placed\":[\{pj}],\"skipped\":[\{sj}],\"autofix\":\{autofix_json},\"missing_count_after\":\{after_count},\"mock_missing\":[\{mock_gaps_json}]}",
  )
}

///|
/// 从 infer_missing 信封提取缺失元素清单:先截取 "missing":[ … ] 数组
/// 片段再扫描 element——信封其他字段里的同名键不参与(CORE-4)
fn parse_missing_envelope(json : String) -> Array[String] {
  parse_missing_elements(missing_array_scope(json))
}

///|
/// 截取信封里 "missing":[ … ] 的数组内容片段(不含方括号);数组缺席或
/// 为空返回 ""。闭合按当前 infer_missing 的扁平条目形状取首个 `]`——
/// 条目的 suggestion 文案不含括号。
fn missing_array_scope(json : String) -> String {
  match json.find("\"missing\":[") {
    Some(i) => {
      let rest = json.substring(start=i + "\"missing\":[".length())
      match rest.find("]") {
        Some(j) => rest.substring(start=0, end=j).to_string()
        None => ""
      }
    }
    None => ""
  }
}

///|
/// 从缺失数组片段提取 element 清单(顺序保留;只消费不校验——单一
/// 事实源在 infer_missing 的 JSON 形状)
fn parse_missing_elements(scope : String) -> Array[String] {
  let elements : Array[String] = []
  let mut from = 0
  let key = "\"element\":\""
  while from + key.length() <= scope.length() {
    match scope.substring(start=from).find(key) {
      Some(rel) => {
        let i = from + rel + key.length()
        let rest = scope.substring(start=i)
        match rest.find("\"") {
          Some(j) => {
            elements.push(rest.substring(start=0, end=j).to_string())
            from = i + j
          }
          None => from = scope.length()
        }
      }
      None => from = scope.length()
    }
  }
  elements
}

///|
/// skipped 数组元素 JSON 片段:{"element":"…","reason":"…"}(element 经
/// 转义;reason 为字面枚举,不需转义)
fn af_skip_json(element : String, reason : String) -> String {
  "{\"element\":\"\{json_escape(element)}\",\"reason\":\"\{reason}\"}"
}