// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
pub impl Show for Unit with fn output(_self, logger) {
  logger.write_string("()")
}

///|
pub impl Show for Unit with fn to_string(_self) {
  "()"
}

///|
pub impl Show for Bool with fn to_string(self) {
  if self {
    "true"
  } else {
    "false"
  }
}

///|
pub impl Show for Int with fn to_string(self) {
  Int::to_string(self)
}

///|
pub impl Show for Int64 with fn to_string(self) {
  Int64::to_string(self)
}

///|
pub impl Show for UInt with fn to_string(self) {
  UInt::to_string(self)
}

///|
pub impl Show for UInt64 with fn to_string(self) {
  UInt64::to_string(self)
}

///|
pub impl Show for Byte with fn to_string(self) {
  Byte::to_string(self)
}

///|
pub impl Show for UInt16 with fn to_string(self) {
  UInt16::to_string(self)
}

///|
/// Convert a byte to a two-digit lowercase hexadecimal string.
///
/// Example:
///
/// ```mbt check
/// test {
///   inspect(Byte::to_hex(b'\x0f'), content="0f")
/// }
/// ```
pub fn Byte::to_hex(b : Byte) -> String {
  fn to_hex_digit(i : Byte) -> Char {
    if i < 10 {
      (i + b'0').to_char()
    } else {
      (i + b'a' - 10).to_char()
    }
  }

  [to_hex_digit(b / 16), to_hex_digit(b % 16)]
}

///|
test "to_hex_digit" {
  inspect(Byte::to_hex(b'\xee'), content="ee")
  inspect(Byte::to_hex(b'\xf3'), content="f3")
}

///|
/// Returns the escaped representation of a string.
///
/// When `quote` is true (default), the result is wrapped in double quotes
/// like a MoonBit string literal.
///
/// Escape rules:
/// - Double quote and backslash are backslash-escaped: `\"`, `\\`
/// - Common control characters use named escapes: `\n`, `\r`, `\b`, `\t`
/// - Other control characters (< U+0020) use `\u{hex}` format
/// - All other characters are displayed as-is
///
/// ```mbt check
/// test {
///   inspect("Hello \n".escape(), content="\"Hello \\n\"")
///   inspect("Hello \n".escape(quote=false), content="Hello \\n")
/// }
/// ```
pub fn String::escape(self : String, quote? : Bool = true) -> String {
  let buf = StringBuilder()
  self[:].escape_to(buf, quote~)
  buf.to_string()
}

///|
/// Returns the escaped representation of a string view.
///
/// When `quote` is true (default), the result is wrapped in double quotes
/// like a MoonBit string literal.
///
/// Escape rules:
/// - Double quote and backslash are backslash-escaped: `\"`, `\\`
/// - Common control characters use named escapes: `\n`, `\r`, `\b`, `\t`
/// - Other control characters (< U+0020) use `\u{hex}` format
/// - All other characters are displayed as-is
///
/// ```mbt check
/// test {
///   inspect("Hello\nWorld"[:6].escape(), content="\"Hello\\n\"")
///   inspect("Hello\nWorld"[:6].escape(quote=false), content="Hello\\n")
/// }
/// ```
pub fn StringView::escape(
  self : StringView,
  quote? : Bool = true,
) -> StringView {
  let buf = StringBuilder()
  self.escape_to(buf, quote~)
  buf.to_string()
}

///|
// js and wasm-gc have no vector loads, so they keep the scalar loop.
#cfg(not(any(target="native", target="wasm")))
fn StringView::escape_to(
  self : StringView,
  logger : &Logger,
  quote? : Bool = true,
) -> Unit {
  if quote {
    logger.write_char('"')
  }
  let len = self.length()
  fn flush_segment(seg : Int, i : Int) {
    if i > seg {
      logger.write_view(self[seg:i])
    }
  }
  for i = 0, seg = 0 {
    if i >= len {
      flush_segment(seg, i)
      break
    }
    let code = self.unsafe_get(i)
    match code {
      '"' | '\\' as c => {
        flush_segment(seg, i)
        logger.write_char('\\')
        logger.write_char(c.unsafe_to_char())
        continue i + 1, i + 1
      }
      '\n' => {
        flush_segment(seg, i)
        logger.write_string("\\n")
        continue i + 1, i + 1
      }
      '\r' => {
        flush_segment(seg, i)
        logger.write_string("\\r")
        continue i + 1, i + 1
      }
      '\b' => {
        flush_segment(seg, i)
        logger.write_string("\\b")
        continue i + 1, i + 1
      }
      '\t' => {
        flush_segment(seg, i)
        logger.write_string("\\t")
        continue i + 1, i + 1
      }
      code =>
        if code < ' ' {
          flush_segment(seg, i)
          logger <+ "\\u{\{cb => cb.write_string(code.to_byte().to_hex())}}"
          continue i + 1, i + 1
        } else {
          continue i + 1, seg
        }
    }
  }
  if quote {
    logger.write_char('"')
  }
}

///|
// Same output and `Logger` call sequence as the scalar loop above, but the
// unescaped runs are found with `find_escape_code_unit`, eight UTF-16 code
// units at a time for longer runs.
//
// This relies on `String`/`StringView` being well-formed UTF-16 (see
// `StringView::make_view`). Every run boundary is either an end of the view or
// next to an escaped code unit, which is ASCII, so no boundary splits a
// surrogate pair: the scalar loop's `self[seg:i]` never snaps a boundary, and
// the unchecked `make_view` below yields the same well-formed run.
#cfg(any(target="native", target="wasm"))
fn StringView::escape_to(
  self : StringView,
  logger : &Logger,
  quote? : Bool = true,
) -> Unit {
  if quote {
    logger.write_char('"')
  }
  let data = self.str()
  let end = self.start() + self.length()
  for i = self.start(), seg = self.start() {
    let i = find_escape_code_unit(data, i, end)
    if i > seg {
      logger.write_view(StringView::make_view(data, seg, i))
    }
    guard i < end else { break }
    let code = data.unsafe_get(i)
    match code {
      '"' | '\\' as c => {
        logger.write_char('\\')
        logger.write_char(c.unsafe_to_char())
      }
      '\n' => logger.write_string("\\n")
      '\r' => logger.write_string("\\r")
      '\b' => logger.write_string("\\b")
      '\t' => logger.write_string("\\t")
      code => logger <+ "\\u{\{cb => cb.write_string(code.to_byte().to_hex())}}"
    }
    continue i + 1, i + 1
  }
  if quote {
    logger.write_char('"')
  }
}

///|
// Returns the index of the first code unit in `data[pos:end]` that
// `String::escape` rewrites (a double quote, a backslash, or a control
// character below U+0020), or `end` when there is none. The caller must
// ensure `0 <= pos <= end <= data.length()`.
//
// Ranges shorter than two SIMD blocks, which include most short strings,
// are scanned one code unit at a time. Longer ones check `pos` first, so a
// run of consecutive escapes does not pay for a vector scan per escape.
#cfg(any(target="native", target="wasm"))
#inline
fn find_escape_code_unit(data : String, pos : Int, end : Int) -> Int {
  if end - pos < 16 {
    find_escape_code_unit_scalar(data, pos, end)
  } else if code_unit_needs_escape(data.unsafe_get(pos)) {
    pos
  } else {
    find_escape_code_unit_simd(data, pos + 1, end)
  }
}

///|
#cfg(any(target="native", target="wasm"))
#inline
fn code_unit_needs_escape(code : UInt16) -> Bool {
  code == '"' || code == '\\' || code < ' '
}

///|
#cfg(any(target="native", target="wasm"))
fn find_escape_code_unit_scalar(data : String, pos : Int, end : Int) -> Int {
  for i in pos.. Int {
  let quote = i16x8_splat('"')
  let backslash = i16x8_splat('\\')
  let control_max = i16x8_splat(0x1F)
  // `end - p` cannot overflow, unlike `p + 8` for offsets near `Int` max.
  let tail_start = for p = pos; end - p >= 8; {
    let block = v128_load_i16x8(data, p)
    let escaped = v128_or(
      i16x8_le_u(block, control_max),
      v128_or(i16x8_eq(block, quote), i16x8_eq(block, backslash)),
    )
    if v128_any_true(escaped) {
      // Lane k sets bit k, so the lowest set bit is the first match.
      return p + i16x8_bitmask(escaped).ctz()
    }
    continue p + 8
  } nobreak {
    p
  }
  find_escape_code_unit_scalar(data, tail_start, end)
}

///|
/// Returns the original string without escaping.
/// `Show::output` for `String` also writes the raw string through this
/// `Show::to_string` implementation.
/// Use `String::escape` when a quoted and escaped representation is needed.
/// # Examples
///
/// ```mbt check
/// test {
///   let str = "Hello \n"
///   inspect(str.to_string(), content="Hello \n")
///   inspect(str.escape(quote=true), content="\"Hello \\n\"")
/// }
/// ```
pub impl Show for String with fn to_string(self) {
  self
}

///|
#deprecated("Use Debug instead of Show for debugging purposes. See https://github.com/moonbitlang/core/blob/main/debug/README.mbt.md")
pub impl[X : Show] Show for X?

///|
pub impl[X : Show] Show for X? with fn output(self, logger) {
  match self {
    None => logger.write_string("None")
    Some(arg) =>
      logger <+
        $|Some(\{arg})
  }
}

///|
#deprecated("Use Debug instead of Show for debugging purposes. See https://github.com/moonbitlang/core/blob/main/debug/README.mbt.md")
pub impl[T : Show, E : Show] Show for Result[T, E]

///|
pub impl[T : Show, E : Show] Show for Result[T, E] with fn output(self, logger) {
  match self {
    Ok(x) =>
      logger <+
        $|Ok(\{x})
    Err(e) =>
      logger <+
        $|Err(\{e})
  }
}

///|
#deprecated("Use Debug instead of Show for debugging purposes. See https://github.com/moonbitlang/core/blob/main/debug/README.mbt.md")
pub impl[X : Show] Show for FixedArray[X]

///|
pub impl[X : Show] Show for FixedArray[X] with fn output(self, logger) {
  logger.write_iter(self.iter())
}

///|
#deprecated("Use Debug instead of Show for debugging purposes. See https://github.com/moonbitlang/core/blob/main/debug/README.mbt.md")
pub impl[X : Show] Show for Array[X]

///|
pub impl[X : Show] Show for Array[X] with fn output(self, logger) {
  logger.write_iter(self.iter())
}