// string_helpers.hako - StringHelpers (common, pure helpers)
// Responsibility: numeric/string conversions and JSON quoting for selfhost tools.
// Non-responsibility: JSON scanning beyond local character processing; use CfgNavigatorBox/StringScanBox for navigation.

static box StringHelpers {
  // Convert a numeric or numeric-like string to its decimal representation.
  int_to_str(n) {
    local v = me.to_i64(n)
    if v == 0 { return "0" }
    if v < 0 { return "-" + me.int_to_str(0 - v) }
    local out = ""
    local digits = "0123456789"
    loop (v > 0) {
      local d = v % 10
      local ch = digits.substring(d, d+1)
      out = ch + out
      v = v / 10
    }
    return out
  }

  // Parse integer from number or numeric-like string (leading '-' allowed; stops at first non-digit).
  to_i64(x) {
    // Optional debug hook: observe incoming values/boxes.
    // Enable with NYASH_TO_I64_DEBUG=1 when diagnosing numeric coercions.
    if env.get("NYASH_TO_I64_DEBUG") == "1" {
      __mir__.log("[string_helpers/to_i64] x", x)
    }
    // Fail-close for unexpected boxes (e.g., Void) when explicitly requested.
    // This avoids String + Void type errors during Stage-1 CLI bring-up.
    if env.get("NYASH_TO_I64_FORCE_ZERO") == "1" { return 0 }
    if x == null { return 0 }
    local s = "" + x
    local i = 0
    local neg = 0
    if s.substring(0,1) == "-" { neg = 1  i = 1 }
    local n = s.length()
    if i >= n { return 0 }
    local acc = 0
    loop (i < n) {
      local ch = s.substring(i, i+1)
      if ch < "0" || ch > "9" { break }
      local ds = "0123456789"
      local dpos = ds.indexOf(ch)
      if dpos < 0 { break }
      acc = acc * 10 + dpos
      i = i + 1
    }
    if neg == 1 { return 0 - acc }
    return acc
  }

  // Quote a string for JSON (escape backslash, quote, and control chars) and wrap with quotes
  json_quote(s) {
    if s == null { return "\"\"" }
    local out = ""
    local i = 0
    local n = s.length()
    loop (i < n) {
      local ch = s.substring(i, i+1)
      if ch == "\\" { out = out + "\\\\" }
      else { if ch == "\"" { out = out + "\\\"" } else {
        if ch == "\n" { out = out + "\\n" } else {
          if ch == "\r" { out = out + "\\r" } else {
            if ch == "\t" { out = out + "\\t" } else { out = out + ch }
          }
        }
      }}
      i = i + 1
    }
    return "\"" + out + "\""
  }

  // Check if string is numeric-like (optional leading '-', then digits).
  is_numeric_str(s) {
    if s == null { return 0 }
    local n = s.length()
    if n == 0 { return 0 }
    local i = 0
    if s.substring(0,1) == "-" { if n == 1 { return 0 } i = 1 }
    loop(i < n) { local ch = s.substring(i, i+1) if ch < "0" || ch > "9" { return 0 } i = i + 1 }
    return 1
  }

  // Read consecutive digits starting at pos (no sign handling here; keep semantics simple)
  read_digits(text, pos) {
    local out = ""
    loop (true) {
      local ch = text.substring(pos, pos+1)
      if ch == "" { break }
      if ch >= "0" && ch <= "9" { out = out + ch  pos = pos + 1 } else { break }
    }
    return out
  }

  // ------------------------------------
  // NEW: Extended String Utilities (added for parser/compiler unification)
  // ------------------------------------

  // Character predicates
  is_digit(ch) { return ch >= "0" && ch <= "9" }
  is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" }
  is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }

  // Pattern matching
  starts_with(src, i, pat) {
    local n = src.length()
    local m = pat.length()
    {
      // Dev-only trace for Stage‑B / parser調査用
      local dbg = env.get("HAKO_STAGEB_DEBUG")
      if dbg != null && ("" + dbg) == "1" {
        print("[string_helpers/starts_with] src=\"" + src + "\"")
        print("[string_helpers/starts_with] i=" + ("" + i) + " m=" + ("" + m) + " n=" + ("" + n))
      }
    }
    if i + m > n { return 0 }
    local k = 0
    loop(k < m) {
      if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 }
      k = k + 1
    }
    return 1
  }

  // Keyword match with word boundary (next char not [A-Za-z0-9_])
  starts_with_kw(src, i, kw) {
    {
      local dbg = env.get("HAKO_STAGEB_DEBUG")
      if dbg != null && ("" + dbg) == "1" {
        print("[string_helpers/starts_with_kw] src=\"" + src + "\" i=" + ("" + i) + " kw=\"" + kw + "\"")
      }
    }
    // 同一箱内ヘルパーとして、receiver 経由ではなく
    // 明示的な箱名付きの静的呼び出しにする（引数順のずれ防止）。
    if StringHelpers.starts_with(src, i, kw) == 0 { return 0 }
    local n = src.length()
    local j = i + kw.length()
    if j >= n { return 1 }
    local ch = src.substring(j, j+1)
    if me.is_alpha(ch) || me.is_digit(ch) { return 0 }
    return 1
  }

  // String search
  index_of(src, i, pat) {
    local n = src.length()
    local m = pat.length()
    if m == 0 { return i }
    local j = i
    loop(j + m <= n) {
      if me.starts_with(src, j, pat) { return j }
      j = j + 1
    }
    return -1
  }

  // Trim spaces and tabs (with optional semicolon at end)
  trim(s) {
    local i = 0
    local n = s.length()
    loop(i < n && (s.substring(i,i+1) == " " || s.substring(i,i+1) == "\t")) { i = i + 1 }
    local j = n
    loop(j > i && (s.substring(j-1,j) == " " || s.substring(j-1,j) == "\t" || s.substring(j-1,j) == ";")) { j = j - 1 }
    return s.substring(i, j)
  }

  // Skip whitespace from position i
  skip_ws(src, i) {
    if src == null { return i }
    // Canonicalize to string to avoid mis-typed receivers in static boxes
    local s = "" + src
    local n = s.length()
    local j = i
    // Simplified loop structure for MirBuilder compatibility
    loop(j < n) {
      if me.is_space(s.substring(j, j+1)) {
        j = j + 1
        continue
      }
      break
    }
    return j
  }

  // Find last occurrence of pattern in string (backward search)
  last_index_of(src, pat) {
    if src == null { return -1 }
    if pat == null { return -1 }
    local n = src.length()
    local m = pat.length()
    if m == 0 { return n }
    if m > n { return -1 }
    local i = n - m
    loop(i >= 0) {
      if me.starts_with(src, i, pat) { return i }
      i = i - 1
    }
    return -1
  }

  // Split string by newline into ArrayBox (without relying on StringBox.split)
  split_lines(s) {
    local arr = new ArrayBox()
    if s == null { return arr }
    local n = s.length()
    local last = 0
    local i = 0
    loop (i < n) {
      local ch = s.substring(i, i+1)
      if ch == "\n" {
        arr.push(s.substring(last, i))
        last = i + 1
      }
      i = i + 1
    }
    // push tail
    if last <= n { arr.push(s.substring(last)) }
    return arr
  }
}
