///|
// 替换引擎(hutool `text.replacer` 三件:`StrReplacer` / `LookupReplacer` / `ReplacerChain`)。
//
// 为什么这一批要连着实体族一起做:`XmlEscape`/`Html4Escape`/`XmlUnescape`/`Html4Unescape`
// **全都 extends `ReplacerChain`**(javap -p 现读),实体转义就是"查表替换 + 链"拼出来的。
// 择路规则一旦定错,实体表与数字实体的读数会整片跟着错。四条判据全部来自
// `scripts/ReplacerLeg.java`:
//   * 长键优先,**与表序无关**(同一条表把 `{ab→X, abc→Y}` 倒过来放,两侧读数一样);
//   * 同键两条 ⇒ **后写的生效**(腿 set2 单字符档读数给 `2`);
//   * 串尾不够长就不匹配(不回绕、不部分命中);
//   * **一遍左到右扫,命中后从那一段之后继续**,产出不回头再扫(腿 C|overlap:`ab→1` 与 `1→X`
//     同链给 `1`;C|watch 那件的调用计数为 0,证第二件在同一位置根本没被调到);
//   * 空键 ⇒ 参照在构造器里 `charAt(0)` 就抛(腿 L|ctor),本库收成 `raise(EmptyKey)`。
//
// **公开的只有整串面**(`replace(s)`),参照那个 `protected int replace(CharSequence,int,StrBuilder)`
// 不进公开面。原因是一条读数而不是洁癖:参照的驱动循环把"返回负数"当成消费负数 ⇒ 游标倒退、
// **永不收敛**(腿 B|solo|retNeg 两秒超时守护下跑出 16.5 亿次调用;0 才是"不匹配")。
// 只要不开放逐步钩子,这一档就构造不出来;开放了就等于把"调用方可以写出不收敛"放进契约面。
//
// 形状:`kind` 0 = 查表(`LookupReplacer`)、1 = 链(`ReplacerChain`);字段全 `priv`,
// 公开面只有两条构造路,手工拼一条规则等于自己决定"命中后游标前进几位",那一档语义没冻
// (同 dfa 的 `WordTree` 口径)。

pub struct Replacer {
  priv kind : Int
  priv pairs : Array[(String, String)]
  priv chain : Array[Replacer]
}

///|
/// 内部构造件:跳过空键检查,供**表里根本不存在空键**的本包内部使用(实体表由反射读来,
/// 键最短就是 `<` 那 4 个字符;单字符键也在 1 以上)。公开面走 `lookup_replacer`。
fn make_lookup(pairs : Array[(String, String)]) -> Replacer {
  Replacer::{ kind: 0, pairs, chain: [], }
}

///|
/// 查表替换器(`LookupReplacer(String[]...)`)。表里每条是 `(键, 值)`;
/// 空键在本库是 `raise(EmptyKey)`(参照是同位置直接 `StringIndexOutOfBoundsException`)。
pub fn lookup_replacer(
  pairs : Array[(String, String)],
) -> Replacer raise TextError {
  for (key, _) in pairs {
    if key.is_empty() {
      raise EmptyKey
    }
  }
  make_lookup(pairs)
}

///|
/// 链式替换器(`ReplacerChain(StrReplacer...)` + `addChain`)。
/// 语义是"每个位置按链序问第一个命中的",**不是**"依次全文替换"(腿 C|overlap、B|chain_fs/sf)。
pub fn replacer_chain(rs : Array[Replacer]) -> Replacer {
  Replacer::{ kind: 1, pairs: [], chain: rs, }
}

///|
/// 从 `cs[at]` 起问一次:命中给 `(吃了几个码位, 要写出的串)`,否则 None。
/// 长键优先;等长时**后出现的生效**(腿 set2 读数如此,不是"先到先得")。
fn step_at(r : Replacer, cs : Array[Char], at : Int) -> (Int, String)? {
  if r.kind == 0 {
    longest_of(r.pairs, cs, at)
  } else {
    let mut found : (Int, String)? = None
    for child in r.chain {
      if found is None {
        found = step_at(child, cs, at)
      }
    }
    found
  }
}

///|
fn longest_of(
  pairs : Array[(String, String)],
  cs : Array[Char],
  at : Int,
) -> (Int, String)? {
  let mut best_len = 0
  let mut best_val = ""
  for (key, value) in pairs {
    let kc = key.iter().collect()
    let m = kc.length()
    // `== best_len` 也要走过去 ⇒ 同长度时表里**后**那条生效(腿 set2 给 `2`)
    if m < best_len || at + m > cs.length() || m == 0 {
      continue
    }
    let mut hit = true
    let mut k = 0
    while k < m {
      if cs[at + k] != kc[k] {
        hit = false
      }
      k += 1
    }
    if hit {
      best_len = m
      best_val = value
    }
  }
  if best_len == 0 {
    None
  } else {
    Some((best_len, best_val))
  }
}

///|
/// 整串替换(`StrReplacer.replace(CharSequence)` 的公开面)。
/// 参照返回 `CharSequence`,实为 `StrBuilder`;本库直接给 `String`,那条返回类型没有额外承诺。
pub fn Replacer::replace(self : Replacer, s : String) -> String {
  let cs = s.iter().collect()
  let buf = StringBuilder()
  let mut i = 0
  while i < cs.length() {
    match step_at(self, cs, i) {
      Some((consumed, text)) => {
        buf.write_string(text)
        i += consumed
      }
      None => {
        buf.write_char(cs[i])
        i += 1
      }
    }
  }
  buf.to_string()
}