// String embedded code and methods

///|
pub let string_embedded_code : Map[String, RuntimeFunction] = {
  "%string_length": string_length,
  "%string_add": string_add_fn,
  "%string_eq": string_eq_fn,
  "%string_to_string": string_to_string_fn,
}

///|
let string_methods : Map[String, RuntimeFunction] = {
  "length": string_length,
  "char_length": string_char_length_fn,
  "char_length_eq": string_char_length_eq_fn,
  "char_length_ge": string_char_length_ge_fn,
  "add": string_add_fn,
  "equal": string_eq_fn,
  "to_string": string_to_string_fn,
  "lexical_compare": string_lexical_compare_fn,
  "make": string_make_fn,
  "from_array": string_from_array_fn,
  "from_iter": string_from_iter_fn,
  "iter": string_iter_fn,
  "iter2": string_iter2_fn,
  "offset_of_nth_char": string_offset_of_nth_char_fn,
  "to_array": string_to_array_fn,
  "contains": string_contains_fn,
  "contains_any": string_contains_any_fn,
  "contains_char": string_contains_char_fn,
  "find": string_find_fn,
  "find_by": string_find_by_fn,
  "fold": string_fold_fn,
  "has_prefix": string_has_prefix_fn,
  "has_suffix": string_has_suffix_fn,
  "is_blank": string_is_blank_fn,
  "is_empty": string_is_empty_fn,
  "pad_end": string_pad_end_fn,
  "pad_start": string_pad_start_fn,
  "repeat": string_repeat_fn,
  "replace": string_replace_fn,
  "replace_all": string_replace_all_fn,
  "rev": string_rev_fn,
  "rev_find": string_rev_find_fn,
  "rev_fold": string_rev_fold_fn,
  "rev_iter": string_rev_iter_fn,
  "split": string_split_fn,
  "split_once": string_split_once_fn,
  "before": string_before_fn,
  "after": string_after_fn,
  "rev_split_once": string_rev_split_once_fn,
  "rev_before": string_rev_before_fn,
  "rev_after": string_rev_after_fn,
  "strip_prefix": string_strip_prefix_fn,
  "strip_suffix": string_strip_suffix_fn,
  "to_bytes": string_to_bytes_fn,
  "to_lower": string_to_lower_fn,
  "to_upper": string_to_upper_fn,
  "trim": string_trim_fn,
  "trim_end": string_trim_end_fn,
  "trim_space": string_trim_space_fn,
  "trim_start": string_trim_start_fn,
  "escape": string_escape_fn,
  "get": string_get_fn,
  "get_char": string_get_char_fn,
  "at": string_at_fn,
  "unsafe_get": string_unsafe_get_fn,
  "unsafe_charcode_at": string_unsafe_charcode_at_fn,
  "sub": string_sub_fn,
  "substring": string_substring_fn,
  "unsafe_substring": string_unsafe_substring_fn,
  "suffixes": string_suffixes_fn,
  "view": string_view_fn,
  "code_unit_at": string_code_unit_at_fn,
  "output": string_output_fn,
}

// Core string functions

///|
let string_length : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Int(s.length(), raw=None)
    _ => Int(0, raw=None)
  }
}

///|
let string_code_unit_at_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(i, ..), .. }] =>
      UInt16(s.code_unit_at(i))
    _ => UInt16(0)
  }
}

///|
let string_add_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(a), .. }, { val: String(b), .. }] => String(a + b)
    _ => String("")
  }
}

///|
let string_eq_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(a), .. }, { val: String(b), .. }] => Bool(a == b)
    _ => Bool(false)
  }
}

///|
let string_to_string_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s)
    _ => String("")
  }
}

///|
let string_output_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringBuilder(logger), .. }] => {
      s.output(logger)
      Unit
    }
    _ => Unit
  }
}

// Simple implementations for other methods

///|
let string_make_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: Int(len, ..), .. }, { val: Char(value), .. }] =>
      String(String::make(len, value))
    _ => String("")
  }
}

///|
let string_iter_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Iter(s.iter().map(x => Char(x)))
    _ => panic()
  }
}

///|
let string_to_array_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Array(s.to_array().map(x => Char(x)))
    _ => Array([])
  }
}

///|
let string_contains_fn : RuntimeFunction = ctx => {
  Bool(
    match ctx.args {
      [{ val: String(str), .. }, { val: String(str2), .. }] =>
        str.contains(str2)
      _ => false
    },
  )
}

///|
let string_contains_char_fn : RuntimeFunction = ctx => {
  Bool(
    match ctx.args {
      [{ val: String(str), .. }, { val: Char(c), .. }] => str.contains_char(c)
      _ => false
    },
  )
}

///|
let string_find_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(str), .. }, { val: StringView(view), .. }] =>
      RuntimeValue::from_option(str.find(view).map(x => Int(x, raw=None)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_find_by_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Fn(func), .. }] =>
      RuntimeValue::from_option(
        s
        .find_by(char => {
          let res = ctx.context.call(func.val, ctx.pkg, [
            { val: Char(char), kind: Positional },
          ]) catch {
            _ => Unit
          }
          match res {
            Bool(bool) => bool
            _ => false
          }
        })
        .map(x => Int(x, raw=None)),
      )
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_fold_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: init, .. }, { val: Fn(func), .. }, ..] =>
      s.fold(init~, fn(acc, char) {
        ctx.context.call(func.val, ctx.pkg, [
          { val: acc, kind: Positional },
          { val: Char(char), kind: Positional },
        ]) catch {
          _ => Unit
        }
      })
    _ => String("")
  }
}

///|
let string_has_prefix_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(prefix), .. }] =>
      Bool(s.has_prefix(prefix))
    _ => Bool(false)
  }
}

///|
let string_has_suffix_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(suffix), .. }] =>
      Bool(s.has_suffix(suffix))
    _ => Bool(false)
  }
}

///|
let string_is_empty_fn : RuntimeFunction = ctx => {
  if ctx.args.length() > 0 {
    match ctx.args[0].val {
      String(s) => Bool(s.length() == 0)
      _ => Bool(false)
    }
  } else {
    Bool(false)
  }
}

///|
let string_is_blank_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Bool(s.is_blank())
    _ => Bool(false)
  }
}

///|
let string_pad_end_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [
      { val: String(s), .. },
      { val: Int(total_width, ..), .. },
      { val: Char(padding_char), .. },
    ] => String(s.pad_end(total_width, padding_char))
    _ => String("")
  }
}

///|
let string_pad_start_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [
      { val: String(s), .. },
      { val: Int(total_width, ..), .. },
      { val: Char(padding_char), .. },
    ] => String(s.pad_start(total_width, padding_char))
    _ => String("")
  }
}

///|
let string_repeat_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(count, ..), .. }] =>
      String(s.repeat(count))
    _ => String("")
  }
}

///|
let string_replace_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [
      { val: String(s), .. },
      { val: String(from), kind: Labelled("old") },
      { val: String(to), kind: Labelled("new") },
    ] => String(s.replace(old=from, new=to))
    _ => String("")
  }
}

///|
let string_replace_all_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(from), .. }, { val: String(to), .. }] =>
      String(s.replace_all(old=from, new=to))
    _ => String("")
  }
}

///|
let string_rev_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s.rev())
    _ => String("")
  }
}

///|
let string_rev_find_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(substr), .. }] =>
      RuntimeValue::from_option(s.rev_find(substr).map(x => Int(x, raw=None)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_rev_fold_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [
      { val: String(s), .. },
      { val: init, kind: Labelled("init") },
      { val: Fn(func), .. },
      ..,
    ] =>
      s.rev_fold(init~, fn(acc, char) {
        ctx.context.call(func.val, ctx.pkg, [
          { val: acc, kind: Positional },
          { val: Char(char), kind: Positional },
        ]) catch {
          _ => Unit
        }
      })
    _ => Unit
  }
}

///|
let string_rev_iter_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Iter(s.rev_iter().map(x => Char(x)))
    _ => Unit
  }
}

///|
let string_split_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(sep), .. }] =>
      Iter(s.split(sep).map(x => StringView(x)))
    _ => Unit
  }
}

///|
let string_strip_prefix_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(prefix), .. }] =>
      RuntimeValue::from_option(s.strip_prefix(prefix).map(x => StringView(x)))
    _ => StringView("")
  }
}

///|
let string_strip_suffix_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: String(suffix), .. }] =>
      RuntimeValue::from_option(s.strip_suffix(suffix).map(x => StringView(x)))
    _ => StringView("")
  }
}

///|
let string_to_bytes_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Bytes(@encoding/utf8.encode(s))
    _ => Bytes(b"")
  }
}

///|
let string_to_lower_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s.to_lower())
    _ => String("")
  }
}

///|
let string_to_upper_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s.to_upper())
    _ => String("")
  }
}

///|
let string_trim_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(char_set), .. }] =>
      StringView(s.trim(char_set~))
    _ => StringView("")
  }
}

///|
let string_trim_start_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(char_set), .. }] =>
      StringView(s.trim_start(char_set~))
    _ => StringView("")
  }
}

///|
let string_trim_end_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(char_set), .. }] =>
      StringView(s.trim_end(char_set~))
    _ => StringView("")
  }
}

///|
let string_trim_space_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => StringView(s.trim())
    _ => StringView("")
  }
}

///|
let string_view_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [
      { val: String(s), .. },
      { val: Int(start_offset, ..), kind: Labelled("start_offset") },
      { val: Int(end_offset, ..), kind: Labelled("end_offset") },
      ..,
    ] => StringView(s.view(start_offset~, end_offset~))
    [
      { val: String(s), .. },
      { val: Int(start_offset, ..), kind: Labelled("start_offset") },
      ..,
    ] => StringView(s.view(start_offset~))
    [
      { val: String(s), .. },
      { val: Int(end_offset, ..), kind: Labelled("end_offset") },
      ..,
    ] => StringView(s.view(end_offset~))
    [{ val: String(s), .. }] => StringView(s.view())
    _ => StringView("")
  }
}

///|
let string_char_length_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => Int(s.char_length(), raw=None)
    _ => Int(0, raw=None)
  }
}

///|
let string_char_length_eq_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(n, ..), .. }] =>
      Bool(s.char_length_eq(n))
    _ => Bool(false)
  }
}

///|
let string_char_length_ge_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(n, ..), .. }] =>
      Bool(s.char_length_ge(n))
    _ => Bool(false)
  }
}

///|
let string_lexical_compare_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(a), .. }, { val: String(b), .. }] =>
      Int(a.lexical_compare(b), raw=None)
    _ => Int(0, raw=None)
  }
}

///|
let string_from_array_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: Array(chars), .. }] => {
      let result = String::from_array(
        chars.map(ch => {
          match ch {
            Char(c) => c
            _ => '\u{0000}'
          }
        }),
      )
      String(result)
    }
    _ => String("")
  }
}

///|
let string_from_iter_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: Iter(it), .. }] => {
      let result = String::from_iter(
        it.map(ch => {
          match ch {
            Char(c) => c
            _ => '\u{0000}'
          }
        }),
      )
      String(result)
    }
    _ => String("")
  }
}

///|
let string_contains_any_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(chars), .. }] =>
      Bool(s.contains_any(chars~))
    _ => Bool(false)
  }
}

///|
let string_get_char_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(i, ..), .. }] =>
      RuntimeValue::from_option(s.get_char(i).map(c => Char(c)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_iter2_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => {
      let it = s.iter2()
      let iterator = Iter2::new(fn() {
        match it.next() {
          Some((i, ch)) => Some((Int(i, raw=None), Char(ch)))
          None => None
        }
      })
      Iter2(iterator.iter2())
    }
    _ => Unit
  }
}

///|
let string_offset_of_nth_char_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(n, ..), .. }] =>
      RuntimeValue::from_option(
        s.offset_of_nth_char(n).map(i => Int(i, raw=None)),
      )
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_split_once_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(sep), .. }] =>
      RuntimeValue::from_option(
        s
        .split_once(sep)
        .map(pair => {
          match pair {
            (a, b) => Tuple([StringView(a), StringView(b)])
          }
        }),
      )
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_before_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(needle), .. }] =>
      RuntimeValue::from_option(s.before(needle).map(v => StringView(v)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_after_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(needle), .. }] =>
      RuntimeValue::from_option(s.after(needle).map(v => StringView(v)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_rev_split_once_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(sep), .. }] =>
      RuntimeValue::from_option(
        s
        .rev_split_once(sep)
        .map(pair => {
          match pair {
            (a, b) => Tuple([StringView(a), StringView(b)])
          }
        }),
      )
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_rev_before_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(needle), .. }] =>
      RuntimeValue::from_option(s.rev_before(needle).map(v => StringView(v)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_rev_after_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: StringView(needle), .. }] =>
      RuntimeValue::from_option(s.rev_after(needle).map(v => StringView(v)))
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_escape_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s.escape())
    [{ val: String(s), .. }, { val: Bool(quote), .. }] =>
      String(s.escape(quote~))
    _ => String("")
  }
}

///|
let string_get_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(i, ..), .. }] =>
      if i < 0 || i >= s.length() {
        RuntimeValue::from_option(None)
      } else {
        RuntimeValue::from_option(
          Some(Int(s.code_unit_at(i).to_int(), raw=None)),
        )
      }
    _ => RuntimeValue::from_option(None)
  }
}

///|
let string_at_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(i, ..), .. }] => UInt16(s.at(i))
    _ => UInt16(0)
  }
}

///|
let string_unsafe_get_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(i, ..), .. }] => UInt16(s.unsafe_get(i))
    _ => UInt16(0)
  }
}

///|
let string_unsafe_charcode_at_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }, { val: Int(i, ..), .. }] =>
      Int(s.unsafe_get(i).to_int(), raw=None)
    _ => Int(0, raw=None)
  }
}

///|
let string_sub_fn : RuntimeFunction = ctx => string_substring_fn(ctx)

///|
let string_substring_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s.view().to_owned())
    [{ val: String(s), .. }, { val: Int(start, ..), .. }] =>
      String(s.view(start_offset=start).to_owned())
    [
      { val: String(s), .. },
      { val: Int(start, ..), .. },
      { val: Int(end_, ..), .. },
    ] => String(s.view(start_offset=start, end_offset=end_).to_owned())
    _ => String("")
  }
}

///|
let string_unsafe_substring_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => String(s.view().to_owned())
    [{ val: String(s), .. }, { val: Int(start, ..), .. }] =>
      String(s.view(start_offset=start).to_owned())
    [
      { val: String(s), .. },
      { val: Int(start, ..), .. },
      { val: Int(end_, ..), .. },
    ] => String(s.view(start_offset=start, end_offset=end_).to_owned())
    _ => String("")
  }
}

///|
let string_suffixes_fn : RuntimeFunction = ctx => {
  match ctx.args {
    [{ val: String(s), .. }] => {
      let out = []
      for v in s.suffixes() {
        out.push(StringView(v))
      }
      Array(out)
    }
    _ => Array([])
  }
}