///|
/// fromRDF 主体(役57——REC §RDF-to-Object-Conversion;spec §8.130/§8.131
/// 钉):N-Quads → 展开且展平形节点对象数组。三段式:①全量入账(结构化
/// term 存储)②形态集合判定(纯列表/复合字面)③统一渲染。核心判据
/// (t0008 破译):**@list 转换只发生在普通性质对象位**——rdf:first 位引
/// 用恒 {@id} 物化(lista/b1/c1 面);rest 位链续走吸收。named graph =
/// 图名节点 @graph 位;rdf:type → @type(useRdfType 例外);xsd:string
/// datatype 剥除;useNativeTypes → native 词形(boolean 词形空间含 1/0、
/// 溢出/INF/非词形回落 typed;decimal 不在 native 列);@json 字面 =
/// {@type:"@json",@value:解析值}(无效即负例错);rdfDirection
/// i18n-datatype/compound-literal 两族。

///|
/// rdf:type 全 IRI
let from_rdf_type_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"

///|
let from_rdf_first_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first"

///|
let from_rdf_rest_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"

///|
let from_rdf_value_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#value"

///|
let from_rdf_language_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#language"

///|
let from_rdf_direction_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#direction"

///|
let from_rdf_nil_iri : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"

///|
let from_rdf_list_type : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#List"

///|
let from_rdf_json_datatype : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON"

///|
let from_i18n_datatype_prefix : String = "https://www.w3.org/ns/i18n#"

///|
let from_xsd_string : String = "http://www.w3.org/2001/XMLSchema#string"

///|
/// 同图节点累积面(结构化 term 存储——渲染期统一转换)
priv struct FromRdfNode {
  props : Map[String, Array[NqTerm]]
  prop_order : Array[String]
  types : Array[String]
  /// first 位引用标记(吸收豁免判定——t0008/tli02 双面界定)
}

///|
/// 单图账(order = subject 首见序)
priv struct FromRdfGraph {
  nodes : Map[String, FromRdfNode]
  order : Array[String]
}

///|
fn FromRdfGraph::new() -> FromRdfGraph {
  { nodes: Map([]), order: [], }
}

///|
fn FromRdfGraph::node(self : FromRdfGraph, key : String) -> FromRdfNode {
  match self.nodes.get(key) {
    Some(node) => node
    None => {
      let fresh : FromRdfNode = { props: Map([]), prop_order: [], types: [], }
      self.nodes[key] = fresh
      self.order.push(key)
      fresh
    }
  }
}

///|
fn FromRdfGraph::append_prop(
  self : FromRdfGraph,
  key : String,
  pred : String,
  term : NqTerm,
) -> Unit {
  let node = self.node(key)
  match node.props.get(pred) {
    Some(values) =>
      // RDF 集合语义:同键同值去重(结构等值)
      if !from_rdf_terms_contains(values, term) {
        values.push(term)
      }
    None => {
      node.props[pred] = [term]
      node.prop_order.push(pred)
    }
  }
}

///|
fn from_rdf_terms_contains(values : Array[NqTerm], term : NqTerm) -> Bool {
  for existing in values {
    if from_rdf_term_eq(existing, term) {
      return true
    }
  }
  false
}

///|
fn from_rdf_term_eq(a : NqTerm, b : NqTerm) -> Bool {
  match (a, b) {
    (NqIriTerm(x), NqIriTerm(y)) => x == y
    (NqBnodeTerm(x), NqBnodeTerm(y)) => x == y
    (NqLitTerm(v1, d1, l1), NqLitTerm(v2, d2, l2)) =>
      v1 == v2 &&
      from_rdf_opt_string_eq(d1, d2) &&
      from_rdf_opt_string_eq(l1, l2)
    _ => false
  }
}

///|
fn from_rdf_opt_string_eq(a : String?, b : String?) -> Bool {
  match (a, b) {
    (Some(x), Some(y)) => x == y
    (None, None) => true
    _ => false
  }
}

///|
/// term → subject/bnode 账键
fn nq2_term_key(term : NqTerm) -> String {
  match term {
    NqIriTerm(iri) => iri
    NqBnodeTerm(label) => "_:" + label
    NqLitTerm(value, _, _) => value
  }
}

///|
/// 图渲染上下文(账 + 纯列表集合)
priv struct FromRdfGraphCtx {
  graph : FromRdfGraph
  pure_lists : Map[String, Bool]
  is_10 : Bool
}

///|
fn from_rdf_graph_ctx(graph : FromRdfGraph, is_10 : Bool) -> FromRdfGraphCtx {
  let pure_lists : Map[String, Bool] = Map([])
  for key in graph.order {
    if from_rdf_is_pure_list(graph, key, Map([])) {
      pure_lists[key] = true
    }
  }
  { graph, pure_lists, is_10, }
}

///|
/// scoped 版:跨图出现(presence 多图)的键直接出局(t0020/21——同标签
/// 跨图 = 链污染),且污染沿 rest 链传播(z1 多图 ⇒ z0 链头同否)
fn from_rdf_graph_ctx_scoped(
  graph : FromRdfGraph,
  graph_key : String,
  first_refs : Map[String, Map[String, Bool]],
  presence : Map[String, Map[String, Bool]],
  is_10 : Bool,
) -> FromRdfGraphCtx {
  let pure_lists : Map[String, Bool] = Map([])
  for key in graph.order {
    let scoped_ok = from_rdf_is_pure_list_scoped(
      graph,
      key,
      Map([]),
      presence,
      graph_key,
    )
    // first 位物化豁免(1.0 模式面——t0008 lista/b1/c1):first 位引用且
    // rest 非直 nil 的纯链 = 物化(1.1 嵌套转换不豁免——tli03)
    let first_hit = match first_refs.get(key) {
      Some(graphs) => graphs.contains(graph_key)
      None => false
    }
    let rest_direct_nil = match graph.nodes.get(key) {
      Some(node) =>
        match node.props.get(from_rdf_rest_iri) {
          Some([rest_term]) =>
            match rest_term {
              NqIriTerm(iri) => iri == from_rdf_nil_iri
              _ => false
            }
          _ => false
        }
      None => false
    }
    if scoped_ok && !(is_10 && first_hit && !rest_direct_nil) {
      pure_lists[key] = true
    }
  }
  { graph, pure_lists, is_10, }
}

///|
/// scoped 纯列表判据:入口即查本键 presence 多图出局;rest 递归同规
fn from_rdf_is_pure_list_scoped(
  graph : FromRdfGraph,
  key : String,
  visiting : Map[String, Bool],
  presence : Map[String, Map[String, Bool]],
  graph_key : String,
) -> Bool {
  match presence.get(key) {
    Some(graphs) => if graphs.length() > 1 { return false }
    None => return false
  }
  if visiting.contains(key) {
    return false
  }
  if !from_rdf_is_pure_list(graph, key, visiting) {
    return false
  }
  // rest 链续查(链上污染传播)
  let node = graph.nodes[key]
  match node.props.get(from_rdf_rest_iri) {
    Some([rest_term]) =>
      match rest_term {
        NqBnodeTerm(label) => {
          let next_key = "_:" + label
          if next_key == key {
            return false
          }
          visiting[key] = true
          let result = from_rdf_is_pure_list_scoped(
            graph, next_key, visiting, presence, graph_key,
          )
          visiting.remove(key)
          result
        }
        NqIriTerm(iri) => iri == from_rdf_nil_iri
        NqLitTerm(_, _, _) => false
      }
    _ => false
  }
}

///|
/// fromRDF 文档入口(REC fromRdf;spec §8.130)
pub fn from_rdf_document(
  text : String,
  options : JsonLdOptions,
) -> Result[JsonValue, JsonLdError] {
  let quads = match nq2_parse(text) {
    Ok(quads) => quads
    Err(m) => return Err(JsonLdError::InvalidValue("invalid N-Quads: " + m))
  }
  let use_native = options.use_native_types
  let use_rdf_type = options.use_rdf_type
  let rdf_direction = options.rdf_direction
  let default_graph : FromRdfGraph = FromRdfGraph::new()
  let named_graphs : Map[String, FromRdfGraph] = Map([])
  let graph_order : Array[String] = []
  // ① 全量入账
  for quad in quads {
    let pred = match quad.predicate {
      NqIriTerm(iri) => iri
      _ => return Err(JsonLdError::InvalidValue("invalid predicate term"))
    }
    let subj_key = nq2_term_key(quad.subject)
    let graph = match quad.graph {
      None => default_graph
      Some(graph_term) => {
        let graph_key = nq2_term_key(graph_term)
        match named_graphs.get(graph_key) {
          Some(graph) => graph
          None => {
            let fresh : FromRdfGraph = FromRdfGraph::new()
            named_graphs[graph_key] = fresh
            graph_order.push(graph_key)
            fresh
          }
        }
      }
    }
    if pred == from_rdf_type_iri && !use_rdf_type {
      match quad.object {
        NqIriTerm(type_iri) => {
          let node = graph.node(subj_key)
          if !node.types.contains(type_iri) {
            node.types.push(type_iri)
          }
        }
        _ =>
          return Err(JsonLdError::InvalidValue("rdf:type object must be IRI"))
      }
    } else {
      graph.append_prop(subj_key, pred, quad.object)
    }
  }
  // ② 形态集合(各图独立):纯列表集合 + first 位引用集 + 跨图存在账
  let presence : Map[String, Map[String, Bool]] = Map([])
  for quad in quads {
    let gkey = match quad.graph {
      None => "@default"
      Some(graph_term) => nq2_term_key(graph_term)
    }
    match presence.get(nq2_term_key(quad.subject)) {
      Some(graphs) => graphs[gkey] = true
      None => {
        let fresh : Map[String, Bool] = Map([])
        fresh[gkey] = true
        presence[nq2_term_key(quad.subject)] = fresh
      }
    }
    match quad.object {
      NqIriTerm(iri) =>
        if iri != from_rdf_nil_iri {
          match presence.get(iri) {
            Some(graphs) => graphs[gkey] = true
            None => {
              let fresh : Map[String, Bool] = Map([])
              fresh[gkey] = true
              presence[iri] = fresh
            }
          }
        }
      NqBnodeTerm(label) =>
        match presence.get("_:" + label) {
          Some(graphs) => graphs[gkey] = true
          None => {
            let fresh : Map[String, Bool] = Map([])
            fresh[gkey] = true
            presence["_:" + label] = fresh
          }
        }
      NqLitTerm(_, _, _) => ()
    }
  }
  let first_refs : Map[String, Map[String, Bool]] = Map([])
  from_rdf_collect_first_refs(default_graph, "@default", first_refs)
  for graph_key in graph_order {
    match named_graphs.get(graph_key) {
      Some(graph) => from_rdf_collect_first_refs(graph, graph_key, first_refs)
      None => ()
    }
  }
  let is_10 = options.processing_mode is JsonLd10
  let default_ctx = from_rdf_graph_ctx_scoped(
    default_graph, "@default", first_refs, presence, is_10,
  )
  let named_ctx : Map[String, FromRdfGraphCtx] = Map([])
  for graph_key in graph_order {
    match named_graphs.get(graph_key) {
      Some(graph) =>
        named_ctx[graph_key] = from_rdf_graph_ctx_scoped(
          graph, graph_key, first_refs, presence, is_10,
        )
      None => ()
    }
  }
  // ③ 渲染:默认图节点(纯列表吸收不入成员)→ 具名图节点(@graph 位)
  let out : Array[JsonValue] = []
  let emitted : Map[String, Bool] = Map([])
  let compound_absorb : Map[String, Bool] = if rdf_direction
    is Some("compound-literal") {
    from_rdf_collect_compound(default_graph)
  } else {
    Map([])
  }
  let named_compound : Map[String, Map[String, Bool]] = Map([])
  if rdf_direction is Some("compound-literal") {
    for graph_key in graph_order {
      match named_graphs.get(graph_key) {
        Some(graph) =>
          named_compound[graph_key] = from_rdf_collect_compound(graph)
        None => ()
      }
    }
  }
  for key in default_ctx.graph.order {
    if default_ctx.pure_lists.contains(key) || compound_absorb.contains(key) {
      continue
    }
    out.push(
      match
        from_rdf_render_node(
          default_ctx, key, named_graphs, named_ctx, named_compound, use_native,
          rdf_direction,
        ) {
        Ok(jv) => jv
        Err(e) => return Err(e)
      },
    )
    emitted[key] = true
  }
  for graph_key in graph_order {
    if !emitted.contains(graph_key) {
      out.push(
        match
          from_rdf_render_node(
            default_ctx, graph_key, named_graphs, named_ctx, named_compound, use_native,
            rdf_direction,
          ) {
          Ok(jv) => jv
          Err(e) => return Err(e)
        },
      )
      emitted[graph_key] = true
    }
  }
  Ok(JsonValue::Array(out))
}

///|
/// 节点渲染(默认图性质 + @graph 位若该键为图名)
fn from_rdf_render_node(
  ctx : FromRdfGraphCtx,
  key : String,
  named_graphs : Map[String, FromRdfGraph],
  named_ctx : Map[String, FromRdfGraphCtx],
  named_compound : Map[String, Map[String, Bool]],
  use_native : Bool,
  rdf_direction : String?,
) -> Result[JsonValue, JsonLdError] {
  let node = ctx.graph.node(key)
  let pairs : Array[(String, JsonValue)] = [("@id", JsonValue::String(key))]
  if node.types.length() > 0 {
    let type_values : Array[JsonValue] = []
    for t in node.types {
      type_values.push(JsonValue::String(t))
    }
    pairs.push(("@type", JsonValue::Array(type_values)))
  }
  for pred in node.prop_order {
    match node.props.get(pred) {
      Some(terms) =>
        pairs.push(
          (
            pred,
            match
              from_rdf_render_terms(
                ctx.graph,
                ctx.pure_lists,
                terms,
                use_native,
                rdf_direction,
              ) {
              Ok(jv) => jv
              Err(e) => return Err(e)
            },
          ),
        )
      None => ()
    }
  }
  match named_graphs.get(key) {
    Some(graph) => {
      let member_ctx = match named_ctx.get(key) {
        Some(member_ctx) => member_ctx
        None => from_rdf_graph_ctx(graph, ctx.is_10)
      }
      let graph_arr : Array[JsonValue] = []
      let member_compound = match named_compound.get(key) {
        Some(compound) => compound
        None => Map([])
      }
      for member_key in member_ctx.graph.order {
        if member_ctx.pure_lists.contains(member_key) ||
          member_compound.contains(member_key) {
          continue
        }
        graph_arr.push(
          match
            from_rdf_render_member(
              member_ctx, member_key, use_native, rdf_direction,
            ) {
            Ok(jv) => jv
            Err(e) => return Err(e)
          },
        )
      }
      pairs.push(("@graph", JsonValue::Array(graph_arr)))
    }
    None => ()
  }
  Ok(JsonValue::Object(pairs))
}

///|
/// 具名图内容节点渲染
fn from_rdf_render_member(
  ctx : FromRdfGraphCtx,
  key : String,
  use_native : Bool,
  rdf_direction : String?,
) -> Result[JsonValue, JsonLdError] {
  let node = ctx.graph.node(key)
  let pairs : Array[(String, JsonValue)] = [("@id", JsonValue::String(key))]
  if node.types.length() > 0 {
    let type_values : Array[JsonValue] = []
    for t in node.types {
      type_values.push(JsonValue::String(t))
    }
    pairs.push(("@type", JsonValue::Array(type_values)))
  }
  for pred in node.prop_order {
    match node.props.get(pred) {
      Some(terms) =>
        pairs.push(
          (
            pred,
            match
              from_rdf_render_terms(
                ctx.graph,
                ctx.pure_lists,
                terms,
                use_native,
                rdf_direction,
              ) {
              Ok(jv) => jv
              Err(e) => return Err(e)
            },
          ),
        )
      None => ()
    }
  }
  Ok(JsonValue::Object(pairs))
}

///|
/// 值列渲染(term → JSON 值形;普通性质对象位纯列表头 → @list)
fn from_rdf_render_terms(
  graph : FromRdfGraph,
  pure : Map[String, Bool],
  terms : Array[NqTerm],
  use_native : Bool,
  rdf_direction : String?,
) -> Result[JsonValue, JsonLdError] {
  let values : Array[JsonValue] = []
  for term in terms {
    values.push(
      match
        from_rdf_render_term(
          graph,
          pure,
          term,
          use_native,
          rdf_direction,
          Map([]),
        ) {
        Ok(jv) => jv
        Err(e) => return Err(e)
      },
    )
  }
  Ok(JsonValue::Array(values))
}

///|
/// 单 term 渲染(引用形/字面形;nil = 空列表;复合字面吸收)
fn from_rdf_render_term(
  graph : FromRdfGraph,
  pure : Map[String, Bool],
  term : NqTerm,
  use_native : Bool,
  rdf_direction : String?,
  visiting : Map[String, Bool],
) -> Result[JsonValue, JsonLdError] {
  match term {
    NqIriTerm(iri) =>
      if iri == from_rdf_nil_iri {
        Ok(JsonValue::Object([("@list", JsonValue::Array([]))]))
      } else {
        Ok(JsonValue::Object([("@id", JsonValue::String(iri))]))
      }
    NqBnodeTerm(label) => {
      let ref_key = "_:" + label
      // 复合字面吸收(rdf_direction=compound-literal——di11/12)
      if rdf_direction is Some("compound-literal") {
        match from_rdf_compound_literal(graph, ref_key) {
          Some(value_object) => return Ok(value_object)
          None => ()
        }
      }
      if pure.contains(ref_key) {
        let items : Array[JsonValue] = []
        match
          from_rdf_walk_list(
            graph, ref_key, pure, use_native, rdf_direction, items, visiting,
          ) {
          Ok(_) => ()
          Err(e) => return Err(e)
        }
        Ok(JsonValue::Object([("@list", JsonValue::Array(items))]))
      } else {
        Ok(JsonValue::Object([("@id", JsonValue::String(ref_key))]))
      }
    }
    NqLitTerm(value, datatype, language) =>
      from_rdf_render_literal(
        value, datatype, language, use_native, rdf_direction,
      )
  }
}

///|
/// 复合字面吸收(rdf_direction=compound-literal——di11/12):节点恰
/// rdf:value + 可选 rdf:direction/rdf:language(各一、字面形、无型)⇒
/// 值对象折叠,bnode 不物化。非复合形 = None
fn from_rdf_compound_literal(graph : FromRdfGraph, key : String) -> JsonValue? {
  let node = match graph.nodes.get(key) {
    Some(node) => node
    None => return None
  }
  if node.types.length() > 0 {
    return None
  }
  let allowed : Map[String, Bool] = Map([])
  allowed[from_rdf_value_iri] = true
  allowed[from_rdf_direction_iri] = true
  allowed[from_rdf_language_iri] = true
  for pred in node.prop_order {
    if !allowed.contains(pred) {
      return None
    }
  }
  let value_terms = match node.props.get(from_rdf_value_iri) {
    Some(terms) => if terms.length() == 1 { terms } else { return None }
    None => return None
  }
  let mut value_text : String? = None
  let mut language : String? = None
  let mut direction : String? = None
  match value_terms[0] {
    NqLitTerm(value, None, None) => value_text = Some(value)
    _ => return None
  }
  match node.props.get(from_rdf_language_iri) {
    Some([NqLitTerm(lang, None, None)]) => language = Some(lang)
    _ => ()
  }
  match node.props.get(from_rdf_direction_iri) {
    Some([NqLitTerm(dir, None, None)]) => direction = Some(dir)
    _ => ()
  }
  let pairs : Array[(String, JsonValue)] = [
    ("@value", JsonValue::String(value_text.unwrap())),
  ]
  match language {
    Some(lang) => pairs.push(("@language", JsonValue::String(lang)))
    None => ()
  }
  match direction {
    Some(dir) => pairs.push(("@direction", JsonValue::String(dir)))
    None => ()
  }
  Some(JsonValue::Object(pairs))
}

///|
/// 字面形装配(@json = {@type:@json,@value:解析值}——解析失败 Err;native
/// 词形;langtag;非 xsd:string datatype;纯值)
fn from_rdf_render_literal(
  value : String,
  datatype : String?,
  language : String?,
  use_native : Bool,
  rdf_direction : String?,
) -> Result[JsonValue, JsonLdError] {
  match datatype {
    Some(dt) =>
      if dt == from_rdf_json_datatype {
        return match parse_json(value) {
          Ok(parsed) =>
            Ok(
              JsonValue::Object([
                ("@type", JsonValue::String("@json")),
                ("@value", parsed),
              ]),
            )
          Err(_) => Err(JsonLdError::InvalidValue("invalid JSON literal"))
        }
      }
    None => ()
  }
  // i18n 方向 datatype(rdf_direction=i18n-datatype——di05/06):
  // https://www.w3.org/ns/i18n#_ ⇒ @direction (+@language),
  // datatype 剥除
  if rdf_direction is Some("i18n-datatype") {
    match datatype {
      Some(dt) =>
        if dt.length() > from_i18n_datatype_prefix.length() &&
          index_of_str_prefix(dt, from_i18n_datatype_prefix) {
          let suffix = dt[from_i18n_datatype_prefix.length():].to_owned()
          // 尾 '_' 切 lang/dir(dir = ltr/rtl;lang 可空可含 '-')
          let mut split_at : Int? = None
          for i in 0.. {
              let lang = suffix[:at].to_owned()
              let dir = suffix[at + 1:].to_owned()
              if dir == "ltr" || dir == "rtl" {
                let pairs : Array[(String, JsonValue)] = []
                pairs.push(("@value", JsonValue::String(value)))
                if lang != "" {
                  pairs.push(("@language", JsonValue::String(lang)))
                }
                pairs.push(("@direction", JsonValue::String(dir)))
                return Ok(JsonValue::Object(pairs))
              }
            }
            None => ()
          }
        }
      None => ()
    }
  }
  if use_native {
    match datatype {
      Some(dt) => {
        // xsd:boolean 词形空间 = true/false/1/0(t0027 boolean-number 面)
        if dt == "http://www.w3.org/2001/XMLSchema#boolean" {
          if value == "true" || value == "1" {
            return Ok(JsonValue::Object([("@value", JsonValue::Bool(true))]))
          }
          if value == "false" || value == "0" {
            return Ok(JsonValue::Object([("@value", JsonValue::Bool(false))]))
          }
          // 词形外(True/False/notnative)→ typed 字面回落
        }
        // native 数值面 = integer/double(decimal 不在 REC useNativeTypes
        // 列——t0018);词形非法(溢出/INF/notnative)→ typed 回落
        if dt == "http://www.w3.org/2001/XMLSchema#integer" ||
          dt == "http://www.w3.org/2001/XMLSchema#double" {
          match parse_json(value) {
            Ok(JsonValue::Number(num)) =>
              // 溢出词形(0.1e999…→Infinity)= 非词形回落(t0027);
              // 判定 = |num| != Infinity(num - num 对 NaN/Inf 恒非零)
              if !num.is_nan() && !num.is_inf() {
                return Ok(
                  JsonValue::Object([("@value", JsonValue::Number(num))]),
                )
              }
            _ => ()
          }
        }
      }
      None => ()
    }
  }
  match language {
    Some(lang) =>
      return Ok(
        JsonValue::Object([
          ("@value", JsonValue::String(value)),
          ("@language", JsonValue::String(lang)),
        ]),
      )
    None => ()
  }
  match datatype {
    Some(dt) =>
      if dt != from_xsd_string {
        return Ok(
          JsonValue::Object([
            ("@value", JsonValue::String(value)),
            ("@type", JsonValue::String(dt)),
          ]),
        )
      }
    None => ()
  }
  Ok(JsonValue::Object([("@value", JsonValue::String(value))]))
}

///|
/// 前缀判定(dt 头部 == prefix)
fn index_of_str_prefix(dt : String, prefix : String) -> Bool {
  if dt.length() < prefix.length() {
    return false
  }
  for i in 0.. Bool {
  if visiting.contains(key) {
    return false // 环(t0012)
  }
  // 纯列表头限 bnode 键(t0009——IRI 键列表节点 = 常规节点物化)
  if !(key.length() > 2 && key[0] == 95 && key[1] == 58) {
    return false
  }
  let node = match graph.nodes.get(key) {
    Some(node) => node
    None => return false
  }
  // types ⊆ {rdf:List}(t0016——rdf:List 型链照转;他型 = 否)
  for t in node.types {
    if t != from_rdf_list_type {
      return false
    }
  }
  if node.prop_order.length() != 2 {
    return false // 有他性质(t0011)或缺 first/rest 位
  }
  match
    (node.props.get(from_rdf_first_iri), node.props.get(from_rdf_rest_iri)) {
    (Some(first_values), Some(rest_values)) =>
      if first_values.length() != 1 || rest_values.length() != 1 {
        return false // 多 rdf:first(t0013)等
      }
    _ => return false
  }
  // rest 对象判定:nil = 链终;bnode/IRI = 递归(须同图纯列表)
  match node.props.get(from_rdf_rest_iri) {
    Some([rest_term]) =>
      match rest_term {
        NqIriTerm(iri) => iri == from_rdf_nil_iri
        NqBnodeTerm(label) => {
          let next_key = "_:" + label
          if next_key == key {
            false
          } else {
            visiting[key] = true
            let result = from_rdf_is_pure_list(graph, next_key, visiting)
            visiting.remove(key)
            result
          }
        }
        NqLitTerm(_, _, _) => false
      }
    _ => false
  }
}

///|
/// 链行走收值(纯列表头已判——first 位 {@id}/@value 形 + first 位列表节
/// 点物化递归、rest 续走;nil 止)
fn from_rdf_walk_list(
  graph : FromRdfGraph,
  key : String,
  pure : Map[String, Bool],
  use_native : Bool,
  rdf_direction : String?,
  items : Array[JsonValue],
  visiting : Map[String, Bool],
) -> Result[Unit, JsonLdError] {
  if visiting.contains(key) {
    return Err(JsonLdError::InvalidValue("list cycle"))
  }
  visiting[key] = true
  let node = match graph.nodes.get(key) {
    Some(node) => node
    None => return Err(JsonLdError::InvalidValue("list node missing"))
  }
  match node.props.get(from_rdf_first_iri) {
    Some([first_term]) =>
      match first_term {
        // nil = 空列表项(tli01);纯列表头引用 = 嵌套 @list(tli02——
        // _:a/_:b 单节链嵌套转);first 位多节链/非纯 = {@id} 物化(t0008
        // ——lista/b1/c1 面吸收豁免+物化)
        NqIriTerm(iri) =>
          if iri == from_rdf_nil_iri {
            items.push(JsonValue::Object([("@list", JsonValue::Array([]))]))
          } else if pure.contains(iri) {
            let nested : Array[JsonValue] = []
            match
              from_rdf_walk_list(
                graph, iri, pure, use_native, rdf_direction, nested, visiting,
              ) {
              Ok(_) => ()
              Err(e) => return Err(e)
            }
            items.push(JsonValue::Object([("@list", JsonValue::Array(nested))]))
          } else {
            items.push(JsonValue::Object([("@id", JsonValue::String(iri))]))
          }
        NqBnodeTerm(label) => {
          let ref_key = "_:" + label
          if pure.contains(ref_key) {
            let nested : Array[JsonValue] = []
            match
              from_rdf_walk_list(
                graph, ref_key, pure, use_native, rdf_direction, nested, visiting,
              ) {
              Ok(_) => ()
              Err(e) => return Err(e)
            }
            items.push(JsonValue::Object([("@list", JsonValue::Array(nested))]))
          } else {
            items.push(JsonValue::Object([("@id", JsonValue::String(ref_key))]))
          }
        }
        NqLitTerm(value, datatype, language) =>
          match
            from_rdf_render_literal(
              value, datatype, language, use_native, rdf_direction,
            ) {
            Ok(jv) => items.push(jv)
            Err(e) => return Err(e)
          }
      }
    _ => return Err(JsonLdError::InvalidValue("list node missing rdf:first"))
  }
  match node.props.get(from_rdf_rest_iri) {
    Some([rest_term]) =>
      match rest_term {
        NqIriTerm(iri) =>
          if iri == from_rdf_nil_iri {
            Ok(())
          } else {
            // rest 指 IRI——纯列链内已判 IRI 链节;防御直落
            Ok(())
          }
        NqBnodeTerm(label) => {
          let next_key = "_:" + label
          if next_key == key {
            Err(JsonLdError::InvalidValue("list cycle"))
          } else {
            from_rdf_walk_list(
              graph, next_key, pure, use_native, rdf_direction, items, visiting,
            )
          }
        }
        NqLitTerm(_, _, _) =>
          Err(JsonLdError::InvalidValue("list rest must be bnode or nil"))
      }
    _ => Err(JsonLdError::InvalidValue("list node missing rdf:rest"))
  }
}

///|
/// first 位引用收集(rdf:first 值位的节点键集——吸收豁免判定面)
fn from_rdf_collect_first_refs(
  graph : FromRdfGraph,
  graph_key : String,
  first_refs : Map[String, Map[String, Bool]],
) -> Unit {
  for key in graph.order {
    let node = match graph.nodes.get(key) {
      Some(node) => node
      None => continue
    }
    let first_terms = match node.props.get(from_rdf_first_iri) {
      Some(terms) => terms
      None => continue
    }
    for term in first_terms {
      let ref_key = match term {
        NqIriTerm(iri) => if iri != from_rdf_nil_iri { iri } else { continue }
        NqBnodeTerm(label) => "_:" + label
        NqLitTerm(_, _, _) => continue
      }
      match first_refs.get(ref_key) {
        Some(graphs) => graphs[graph_key] = true
        None => {
          let fresh : Map[String, Bool] = Map([])
          fresh[graph_key] = true
          first_refs[ref_key] = fresh
        }
      }
    }
  }
}

///|
/// 复合字面节点集合(rdf_direction=compound-literal——渲染循环吸收面)
fn from_rdf_collect_compound(graph : FromRdfGraph) -> Map[String, Bool] {
  let compound : Map[String, Bool] = Map([])
  for key in graph.order {
    if from_rdf_compound_literal(graph, key) is Some(_) {
      compound[key] = true
    }
  }
  compound
}