// J9 HTML 面(JSON-LD 1.1 API REC 9.5——HTML script 提取):
// Process HTML(9.5.1)+ Extract Script Content(9.5.2)两算法落码。
// 扫描器 = 最小良构 HTML 状态机:只认 )建模需求;EOF 截断按
// HTML 规则视作元素闭合。

///|
/// 一枚 JSON-LD script 元素(文档序):id 属性词面 + textContent 原文
priv struct HtmlScript {
  id_attr : String?
  content : String
}

///|
/// 扫描状态(最小良构形——注释/标注/标签/属性/script 原文)
priv enum HtmlScanMode {
  HtmlText // 标签外
  HtmlTagStart // '<' 后首字符分派
  HtmlBangDecl // 
  HtmlStartTag // 标签名段
  HtmlTagBody // 属性段
  HtmlAttrValueStart // '=' 后首字符(引号探测)
  HtmlAttrQuoted // 引号值内
  HtmlAttrBare // 裸值
  HtmlEndTag // 
}

///|
/// script 原文语境判定(EOF 收口用;平铺等值——避 is 或模式的 fmt 括号坑)
fn html_in_script(mode : HtmlScanMode) -> Bool {
  mode is HtmlScriptRaw || mode is HtmlScriptLt || mode is HtmlScriptEndProbe
}

///|
/// ASCII 大小写不敏感等值(标签名/type 属性比较口径)
fn html_ci_equals(first : String, second : String) -> Bool {
  if first.length() != second.length() {
    return false
  }
  for i in 0..= 65 && a <= 90 { a + 32 } else { a }
    let fb = if b >= 65 && b <= 90 { b + 32 } else { b }
    if fa != fb {
      return false
    }
  }
  true
}

///|
/// ` Bool {
  if text.length() > target.length() {
    return false
  }
  for i in 0..= 65 && a <= 90 { a + 32 } else { a }
    let fb = if b >= 65 && b <= 90 { b + 32 } else { b }
    if fa != fb {
      return false
    }
  }
  true
}

///|
const HTML_LD_JSON_TYPE : String = "application/ld+json"

///|
/// HTML 空白(标签/属性分隔口径)
fn html_is_space(ch : Char) -> Bool {
  ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}

///|
/// 扫描器状态束(单遍状态机;段切换即换新累计器)
priv struct HtmlScanner {
  mut mode : HtmlScanMode
  mut tag_name : StringBuilder
  mut attr_name : StringBuilder
  mut attr_value : StringBuilder
  mut script_type : String?
  mut script_id : String?
  mut capture : Bool
  mut content : StringBuilder
  mut candidate : StringBuilder
  mut quote : Char
  mut comment_dashes : Int
  mut prev1 : Char
  mut prev2 : Char
  mut base_attr : String? //  属性暂存(te020——HTML 文档基)
  mut base_href : String? // 首个  词面(HTML 规则首现生效)
  scripts : Array[HtmlScript]
}

///|
/// 扫描产物:JSON-LD script 集合 + 文档基 href 词面
priv struct HtmlScanResult {
  scripts : Array[HtmlScript]
  base_href : String?
}

///|
/// 收集文档序全部 JSON-LD script 元素(REC 9.5:JSON-LD script = type 属性
/// 为 application/ld+json 的 script 元素;textContent = 开闭标之间原文)
fn html_ld_scripts(html : String) -> HtmlScanResult {
  let scanner = HtmlScanner::{
    mode: HtmlText,
    tag_name: StringBuilder(),
    attr_name: StringBuilder(),
    attr_value: StringBuilder(),
    script_type: None,
    script_id: None,
    capture: false,
    content: StringBuilder(),
    candidate: StringBuilder(),
    quote: '"',
    comment_dashes: 0,
    prev1: ' ',
    prev2: ' ',
    base_attr: None,
    base_href: None,
    scripts: [],
  }
  for ch in html {
    scanner.step(ch)
  }
  // EOF 截断(HTML 规则:script 内 EOF 视作闭合)
  if html_in_script(scanner.mode) && scanner.capture {
    scanner.emit_script()
  }
  HtmlScanResult::{ scripts: scanner.scripts, base_href: scanner.base_href, }
}

///|
fn HtmlScanner::step(self : HtmlScanner, ch : Char) -> Unit {
  match self.mode {
    HtmlText => if ch == '<' { self.mode = HtmlTagStart } else { () }
    HtmlTagStart => {
      // '<' 后首字符分派
      let code = ch.to_int()
      if (code >= 97 && code <= 122) || (code >= 65 && code <= 90) {
        self.tag_name = StringBuilder()
        self.tag_name.write_char(ch)
        self.mode = HtmlStartTag
      } else if ch == '!' || ch == '?' {
        self.comment_dashes = 0
        self.mode = HtmlBangDecl
      } else if ch == '/' {
        self.mode = HtmlEndTag
      } else {
        // 残字符(如 '< 5')回文本
        self.mode = HtmlText
      }
    }
    HtmlBangDecl =>
      if ch == '-' {
        self.comment_dashes = self.comment_dashes + 1
        if self.comment_dashes == 2 {
          self.mode = HtmlComment
        }
      } else {
        self.comment_dashes = 0
        if ch == '>' {
          self.mode = HtmlText
        }
      }
    HtmlComment => {
      if ch == '>' && self.prev1 == '-' && self.prev2 == '-' {
        self.mode = HtmlText
      }
      self.prev2 = self.prev1
      self.prev1 = ch
    }
    HtmlStartTag => {
      let code = ch.to_int()
      if ch == '>' {
        self.close_start_tag()
      } else if html_is_space(ch) || ch == '/' {
        self.mode = HtmlTagBody
      } else if (code >= 97 && code <= 122) || (code >= 65 && code <= 90) {
        self.tag_name.write_char(ch)
      } else {
        ()
      }
    }
    HtmlTagBody =>
      if ch == '>' {
        self.finalize_attr()
        self.close_start_tag()
      } else if html_is_space(ch) || ch == '/' {
        self.finalize_attr()
      } else if ch == '=' {
        self.mode = HtmlAttrValueStart
      } else {
        self.attr_name.write_char(ch)
      }
    HtmlAttrValueStart =>
      if ch == '"' || ch == '\'' {
        self.attr_value = StringBuilder()
        self.quote = ch
        self.mode = HtmlAttrQuoted
      } else if ch == '>' {
        // 空值属性(type= → 非法媒体类型,非 ld+json)
        self.finalize_attr()
        self.close_start_tag()
      } else if html_is_space(ch) {
        self.finalize_attr()
        self.mode = HtmlTagBody
      } else {
        // '/' 亦起裸值(HTML 规范 `=` 后 '/' 是值首字符)
        self.attr_value = StringBuilder()
        self.attr_value.write_char(ch)
        self.mode = HtmlAttrBare
      }
    HtmlAttrQuoted =>
      if ch == self.quote {
        self.finalize_attr()
        self.mode = HtmlTagBody
      } else {
        self.attr_value.write_char(ch)
      }
    HtmlAttrBare =>
      // 裸值只被空白/'>' 终结——'/' 属值内字符(HTML 规范裸值不含 '/'
      // 终结形;application/ld+json 含 '/',役65 探针实证断裂 bug)
      if ch == '>' {
        self.finalize_attr()
        self.close_start_tag()
      } else if html_is_space(ch) {
        self.finalize_attr()
        self.mode = HtmlTagBody
      } else {
        self.attr_value.write_char(ch)
      }
    HtmlEndTag => if ch == '>' { self.mode = HtmlText } else { () }
    HtmlScriptRaw =>
      if ch == '<' {
        self.candidate = StringBuilder()
        self.candidate.write_char('<')
        self.mode = HtmlScriptLt
      } else if self.capture {
        self.content.write_char(ch)
      } else {
        ()
      }
    HtmlScriptLt => {
      self.candidate.write_char(ch)
      let text = self.candidate.to_string()
      if html_ci_prefix_of("
      if ch == '>' {
        self.emit_script()
        self.mode = HtmlText
      } else if html_is_space(ch) || ch == '/' {
        self.emit_script()
        self.mode = HtmlEndSkip
      } else {
        // 非终止符(如  if ch == '>' { self.mode = HtmlText } else { () }
  }
}

///|
/// 属性收口:name=value 落位(script 的 type/id 判定)
fn HtmlScanner::finalize_attr(self : HtmlScanner) -> Unit {
  let name = self.attr_name.to_string()
  if name.length() == 0 {
    return
  }
  let value = self.attr_value.to_string()
  if html_ci_equals(name, "type") {
    self.script_type = Some(value)
  } else if html_ci_equals(name, "id") {
    self.script_id = Some(value)
  } else if html_ci_equals(self.tag_name.to_string(), "base") &&
    html_ci_equals(name, "href") {
    self.base_attr = Some(value)
  } else {
    ()
  }
  self.attr_name = StringBuilder()
  self.attr_value = StringBuilder()
}

///|
/// start tag 收口:script 判定字段落位,转入原文/文本态
fn HtmlScanner::close_start_tag(self : HtmlScanner) -> Unit {
  let name = self.tag_name.to_string()
  if html_ci_equals(name, "script") {
    match self.script_type {
      Some(media_type) =>
        self.capture = html_ci_equals(media_type, HTML_LD_JSON_TYPE)
      None => self.capture = false
    }
    if self.capture {
      self.content = StringBuilder()
    }
    self.mode = HtmlScriptRaw
    if !self.capture {
      // 非 ld+json script:判定字段即弃(capture 件留待 emit 重置——
      // id 属性要存活到落账)
      self.script_type = None
      self.script_id = None
    }
  } else {
    self.capture = false
    self.mode = HtmlText
    self.script_type = None
    self.script_id = None
    // (HTML 文档基——te020 oracle;首现生效)
    if html_ci_equals(name, "base") {
      match self.base_attr {
        Some(href) =>
          if self.base_href is None {
            self.base_href = Some(href)
          } else {
            ()
          }
        None => ()
      }
    }
    self.base_attr = None
  }
  self.tag_name = StringBuilder()
  self.attr_name = StringBuilder()
  self.attr_value = StringBuilder()
}

///|
/// script 元素落账(仅 capture 件)
fn HtmlScanner::emit_script(self : HtmlScanner) -> Unit {
  if self.capture {
    self.scripts.push(HtmlScript::{
      id_attr: self.script_id,
      content: self.content.to_string(),
    })
  }
  self.capture = false
  self.content = StringBuilder()
  self.script_type = None
  self.script_id = None
}

///|
/// percent 解码(REC 9.5.1 fragment 分支——id 匹配前解码 %XX 序列)
fn html_percent_decode(input : String) -> String {
  // 索引式(迭代器无回退——'%' 破形时两 consume 位会吞尾字符,wbtest 实证)
  let chars : Array[Char] = []
  for ch in input {
    chars.push(ch)
  }
  let bytes : Array[Byte] = []
  let mut i = 0
  while i < chars.length() {
    let code = chars[i].to_int()
    if code == 37 && i + 2 < chars.length() {
      match (html_hex_nibble(chars[i + 1]), html_hex_nibble(chars[i + 2])) {
        (Some(h), Some(l)) => {
          bytes.push((h * 16 + l).to_byte())
          i = i + 3
        }
        _ => {
          // 破形 → '%' 按字面
          bytes.push((37 : Int).to_byte())
          i = i + 1
        }
      }
    } else if code == 37 {
      bytes.push((37 : Int).to_byte())
      i = i + 1
    } else if code < 128 {
      bytes.push(code.to_byte())
      i = i + 1
    } else {
      // 非 ASCII 标量 → 按其 UTF-8 字节展开
      for unit_byte in @utf8.encode(chars[i].to_string()) {
        bytes.push(unit_byte)
      }
      i = i + 1
    }
  }
  @utf8.decode_lossy(Bytes::from_array(bytes))
}

///|
/// hex 位 → 0-15
fn html_hex_nibble(ch : Char) -> Int? {
  let code = ch.to_int()
  if code >= 48 && code <= 57 {
    Some(code - 48)
  } else if code >= 97 && code <= 102 {
    Some(code - 87)
  } else if code >= 65 && code <= 70 {
    Some(code - 55)
  } else {
    None
  }
}

///|
/// REC 9.5.2 Extract Script Content:source 非法 JSON → invalid script element
fn html_validate_script(
  script : HtmlScript,
  document_base : String?,
) -> Result[HtmlScriptSource, JsonLdError] {
  match parse_json(script.content) {
    Ok(_) =>
      Ok(HtmlScriptSource::{
        source: script.content,
        document_base,
        no_scripts: false,
      })
    Err(_) => Err(InvalidScriptElement("script content is not valid JSON"))
  }
}

///|
/// REC 9.5.1 Process HTML——script 源选择 + 逐 script JSON 校验。
/// fragment = 输入 URL `#` 后段(调用方剥离,percent 解码在此);
/// extract_all = extractAllScripts 选项。返回 = 喂下游文档函数的 JSON 源
/// 文本:单件/fragment 返回 textContent 原文(零变换保真);extract_all
/// 返回合并数组重文(数组并尾/标量追加——REC 9.5.1 末支;下游重解析)。
pub struct HtmlScriptSource {
  /// 喂下游文档函数的 JSON 源文本
  source : String
  /// HTML 文档基(首个  已对 document_url 相对解析的绝对形;
  /// 无  = None——documentUrl 本身即文档基)
  document_base : String?
  /// **无 JSON-LD script 且 extractAllScripts=false**(REC 9.5.1 无 script 臂)。
  /// 套件 oracle 在此**通道分叉**(同形异判,2026-10-03 勘定):ExpandTest #te006
  /// ⇒ loading document failed;**ToRDFTest #tr006 ⇒ 空数据集**。抽取器只报事实
  /// (本旗 + source="[]"),通道语义由套件 harness 裁定。
  no_scripts : Bool
}

///|
/// REC 9.5.1 Process HTML——script 源选择 + 逐 script JSON 校验 + 文档基。
/// document_url = REC url 参数(检索 URL:option.base ?? 文档位置,调用方
/// 剥 fragment);fragment = 输入 URL `#` 后段;extract_all = extractAllScripts
/// 选项。返回见 HtmlScriptSource:单件/fragment 返回 textContent 原文
/// (零变换保真);extract_all 返回合并数组重文(数组并尾/标量追加——
/// REC 9.5.1 末支;下游重解析)。
pub fn html_script_source(
  html : String,
  document_url : String?,
  fragment : String?,
  extract_all : Bool,
) -> Result[HtmlScriptSource, JsonLdError] {
  let scan = html_ld_scripts(html)
  let scripts = scan.scripts
  // REC 9.5.1 首步:documentUrl = HTML Document Base URL( 对
  // documentUrl 解析;无  = documentUrl 本身——返回 None 由调用方取
  // 检索 URL)
  let document_base : String? = match scan.base_href {
    Some(href) => Some(resolve_base_value(href, document_url))
    None => None
  }
  match fragment {
    Some(raw) => {
      // REC:按 id 定 script(percent 解码后);未命中 → loading document failed
      // (扫描器只收 JSON-LD script,故未命中兼涵「非 JSON-LD script 元素」臂)
      let wanted = html_percent_decode(raw)
      let mut found : HtmlScript? = None
      for script in scripts {
        match script.id_attr {
          Some(id) => if id == wanted { found = Some(script) } else { () }
          None => ()
        }
      }
      match found {
        None =>
          Err(
            LoadingDocumentFailed(
              "fragment #\{wanted}: no JSON-LD script element",
            ),
          )
        Some(script) => html_validate_script(script, document_base)
      }
    }
    None =>
      if extract_all {
        let parts : Array[JsonValue] = []
        for script in scripts {
          match parse_json(script.content) {
            Ok(JsonValue::Array(items)) =>
              for item in items {
                parts.push(item)
              }
            Ok(value) => parts.push(value)
            Err(_) =>
              return Err(
                InvalidScriptElement("script content is not valid JSON"),
              )
          }
        }
        Ok(HtmlScriptSource::{
          source: render_json(JsonValue::Array(parts)),
          document_base,
          // extractAllScripts=true 时空抽取是**正常空结果**(#te007 正例
          // "expands as empty")⇒ 不置无 script 旗(该旗专表 #te006 臂)
          no_scripts: false,
        })
      } else {
        match scripts.get(0) {
          None =>
            // 无 script 臂:报事实(旗 + 空数组源),通道语义交 harness 裁定
            // (#te006 expand ⇒ error / #tr006 toRdf ⇒ 空数据集;见字段注释)
            Ok(HtmlScriptSource::{
              source: "[]",
              document_base,
              no_scripts: true,
            })
          Some(script) => html_validate_script(script, document_base)
        }
      }
  }
}