module builtin

pub struct string {
pub:
	str JS.String
	len int
}

pub fn (s string) runes() []rune {
	mut runes := []rune{}
	for i := 0; i < s.len; i++ {
		mut r := rune(`0`)
		#r = new rune(s.str[i.val].charCodeAt())
		runes << r
	}
	return runes
}

pub fn (s string) slice(a int, b int) string {
	return string(s.str.slice(JS.Number(a), JS.Number(b)))
}

pub fn (s string) substr(start int, end int) string {
	return s.slice(start, end)
}

pub fn (s string) after(dot string) string {
	return string(s.str.slice(JS.Number(int(s.str.lastIndexOf(dot.str)) + 1), s.str.length))
}

pub fn (s string) after_char(dot u8) string {
	// TODO: Implement after byte
	return s
}

pub fn (s string) all_after(dot string) string {
	pos := if dot.len == 0 { -1 } else { int(s.str.indexOf(dot.str)) }
	if pos == -1 {
		return s.clone()
	}
	return s[pos + dot.len..]
}

// why does this exist?
pub fn (s string) all_after_last(dot string) string {
	pos := if dot.len == 0 { -1 } else { int(s.str.lastIndexOf(dot.str)) }
	if pos == -1 {
		return s.clone()
	}
	return s[pos + dot.len..]
}

pub fn (s string) all_before(dot string) string {
	pos := if dot.len == 0 { -1 } else { int(s.str.indexOf(dot.str)) }
	if pos == -1 {
		return s.clone()
	}
	return s[..pos]
	// return string(s.str.slice(0, s.str.indexOf(dot.str)))
}

pub fn (s string) all_before_last(dot string) string {
	pos := if dot.len == 0 { -1 } else { int(s.str.lastIndexOf(dot.str)) }
	if pos == -1 {
		return s.clone()
	}
	return s[..pos]
}

pub fn (s string) bool() bool {
	return s == 'true'
}

pub fn (s string) split(dot string) []string {
	tmparr := s.str.split(dot.str).map(fn (it JS.Any) JS.Any {
		res := ''
		#res.str = it

		return res
	})
	_ := tmparr
	mut arr := []string{}
	#arr = new array(new array_buffer({arr: tmparr,index_start: new int(0),len: new int(tmparr.length)}))

	return arr
}

pub fn (s string) bytes() []u8 {
	sep := ''
	tmparr := s.str.split(sep.str).map(fn (it JS.Any) JS.Any {
		return JS.Any(u8(JS.String(it).charCodeAt(JS.Number(0))))
	})
	_ := tmparr
	mut arr := []u8{}
	#arr = new array(new array_buffer({arr: tmparr,index_start: new int(0),len: new int(tmparr.length)}))

	return arr
}

pub fn (s string) capitalize() string {
	part := string(s.str.slice(JS.Number(1), s.str.length))
	return string(s.str.charAt(JS.Number(0)).toUpperCase().concat(part.str))
}

pub fn (s string) clone() string {
	return string(s.str)
}

// contains returns `true` if the string contains `substr`.
// See also: [`string.index`](#string.index)
pub fn (s string) contains(substr string) bool {
	return bool(s.str.includes(substr.str))
}

// contains_any returns `true` if the string contains any chars in `chars`.
pub fn (s string) contains_any(chars string) bool {
	sep := ''
	res := chars.str.split(sep.str)
	for i in 0 .. int(res.length) {
		if bool(s.str.includes(JS.String(res.at(JS.Number(i))))) {
			return true
		}
	}
	return false
}

// contains_only returns `true`, if the string contains only the characters in `chars`.
pub fn (s string) contains_only(chars string) bool {
	if chars.len == 0 {
		return false
	}
	for ch in s {
		mut res := 0
		for c in chars {
			if ch == c {
				res++
				break
			}
		}
		if res == 0 {
			return false
		}
	}
	return true
}

pub fn (s string) contains_any_substr(chars []string) bool {
	if chars.len == 0 {
		return true
	}
	for x in chars {
		if bool(s.str.includes(x.str)) {
			return true
		}
	}
	return false
}

pub fn (s string) count(substr string) int {
	// TODO: "error: `[]JS.String` is not a struct" when returning arr.length or arr.len
	arr := s.str.split(substr.str)
	len := int(arr.length)
	if len == 0 {
		return 0
	} else {
		return len - 1
	}
}

pub fn (s string) ends_with(p string) bool {
	mut res := false
	#res.val = s.str.endsWith(p.str)

	return res
}

pub fn (s string) starts_with(p string) bool {
	return bool(s.str.startsWith(p.str))
}

pub fn (s string) fields() []string {
	mut res := []string{}
	mut word_start := 0
	mut word_len := 0
	mut is_in_word := false
	mut is_space := false
	for i, c in s {
		is_space = c in [32, 9, 10]
		if !is_space {
			word_len++
		}
		if !is_in_word && !is_space {
			word_start = i
			is_in_word = true
			continue
		}
		if is_space && is_in_word {
			res << s[word_start..word_start + word_len]
			is_in_word = false
			word_len = 0
			word_start = 0
			continue
		}
	}
	if is_in_word && word_len > 0 {
		// collect the remainder word at the end
		res << s[word_start..s.len]
	}
	return res
}

pub fn (s string) find_between(start string, end string) string {
	return string(s.str.slice(JS.Number(int(s.str.indexOf(start.str)) + 1), s.str.indexOf(end.str)))
}

// unnecessary in the JS backend, implemented for api parity.
pub fn (s &string) free() {}

pub fn (s string) hash() int {
	mut h := u32(0)
	if h == 0 && s.len > 0 {
		for c in s {
			h = h * 31 + u32(c)
		}
	}
	return int(h)
}

// int returns the value of the string as an integer `'1'.int() == 1`.
pub fn (s string) int() int {
	res := int(0)
	#if (typeof(s) == "string") { res.val = parseInt(s) }
	#else { res.val = parseInt(s.str) }

	return res
}

// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`.
pub fn (s string) i64() i64 {
	return i64(JS.parseInt(s.str))
}

// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`.
pub fn (s string) i8() i8 {
	return i8(JS.parseInt(s.str))
}

// i16 returns the value of the string as i16 `'1'.i16() == i16(1)`.
pub fn (s string) i16() i16 {
	return i16(JS.parseInt(s.str))
}

// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`.
pub fn (s string) f32() f32 {
	// return C.atof(&char(s.str))
	return f32(JS.parseFloat(s.str))
}

// f64 returns the value of the string as f64 `'1.0'.f64() == f64(1)`.
pub fn (s string) f64() f64 {
	return f64(JS.parseFloat(s.str))
}

// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`.
pub fn (s string) u16() u16 {
	return u16(JS.parseInt(s.str))
}

// u32 returns the value of the string as u32 `'1'.u32() == u32(1)`.
pub fn (s string) u32() u32 {
	return u32(JS.parseInt(s.str))
}

// u64 returns the value of the string as u64 `'1'.u64() == u64(1)`.
pub fn (s string) u64() u64 {
	return u64(JS.parseInt(s.str))
}

pub fn (s string) u8() u64 {
	res := u8(0)
	#res.val = u8(JS.parseInt(s.str))

	return res
}

// trim_right strips any of the characters given in `cutset` from the right of the string.
// Example: assert ' Hello V d'.trim_right(' d') == ' Hello V'
pub fn (s string) trim_right(cutset string) string {
	if s.len < 1 || cutset.len < 1 {
		return s.clone()
	}

	mut pos := s.len - 1

	for pos >= 0 {
		mut found := false
		for cs in cutset {
			if s[pos] == cs {
				found = true
			}
		}
		if !found {
			break
		}
		pos--
	}

	if pos < 0 {
		return ''
	}

	return s[..pos + 1]
}

// trim_left strips any of the characters given in `cutset` from the left of the string.
// Example: assert 'd Hello V developer'.trim_left(' d') == 'Hello V developer'
[direct_array_access]
pub fn (s string) trim_left(cutset string) string {
	if s.len < 1 || cutset.len < 1 {
		return s.clone()
	}
	mut pos := 0
	for pos < s.len {
		mut found := false
		for cs in cutset {
			if s[pos] == cs {
				found = true
				break
			}
		}
		if !found {
			break
		}
		pos++
	}
	return s[pos..]
}

// trim_string_left strips `str` from the start of the string.
// Example: assert 'WorldHello V'.trim_string_left('World') == 'Hello V'
pub fn (s string) trim_string_left(str string) string {
	if s.starts_with(str) {
		return s[str.len..]
	}
	return s.clone()
}

// trim_string_right strips `str` from the end of the string.
// Example: assert 'Hello VWorld'.trim_string_right('World') == 'Hello V'
pub fn (s string) trim_string_right(str string) string {
	if s.ends_with(str) {
		return s[..s.len - str.len]
	}
	return s.clone()
}

// compare_strings returns `-1` if `a < b`, `1` if `a > b` else `0`.
pub fn compare_strings(a &string, b &string) int {
	if a < b {
		return -1
	}
	if a > b {
		return 1
	}
	return 0
}

// compare_strings_reverse returns `1` if `a < b`, `-1` if `a > b` else `0`.
fn compare_strings_reverse(a &string, b &string) int {
	if a < b {
		return 1
	}
	if a > b {
		return -1
	}
	return 0
}

// compare_strings_by_len returns `-1` if `a.len < b.len`, `1` if `a.len > b.len` else `0`.
fn compare_strings_by_len(a &string, b &string) int {
	if a.len < b.len {
		return -1
	}
	if a.len > b.len {
		return 1
	}
	return 0
}

// compare_lower_strings returns the same as compare_strings but converts `a` and `b` to lower case before comparing.
fn compare_lower_strings(a &string, b &string) int {
	aa := a.to_lower()
	bb := b.to_lower()
	return compare_strings(&aa, &bb)
}

// at returns the byte at index `idx`.
// Example: assert 'ABC'.at(1) == u8(`B`)
fn (s string) at(idx int) u8 {
	mut result := u8(0)
	#result = new u8(s.str.charCodeAt(result))

	return result
}

pub fn (s string) to_lower() string {
	mut result := ''
	#let str = s.str.toLowerCase()
	#result = new string(str)

	return result
}

// TODO: check if that behaves the same as V's own string.replace(old_sub,new_sub):
pub fn (s string) replace(old_sub string, new_sub string) string {
	mut result := ''
	#result = new string( s.str.replaceAll(old_sub.str, new_sub.str) )

	return result
}

pub fn (s string) to_upper() string {
	mut result := ''
	#let str = s.str.toUpperCase()
	#result = new string(str)

	return result
}

// sort sorts the string array.
pub fn (mut s []string) sort() {
	s.sort_with_compare(compare_strings)
}

// sort_ignore_case sorts the string array using case insensitive comparing.
pub fn (mut s []string) sort_ignore_case() {
	s.sort_with_compare(compare_lower_strings)
}

// sort_by_len sorts the the string array by each string's `.len` length.
pub fn (mut s []string) sort_by_len() {
	s.sort_with_compare(compare_strings_by_len)
}

// str returns a copy of the string
pub fn (s string) str() string {
	return s.clone()
}

pub fn (s string) repeat(count int) string {
	mut result := ''
	#result = new string(s.str.repeat(count))

	return result
}

// TODO(playX): Use this iterator instead of using .split('').map(c => u8(c))
#function string_iterator(string) { this.stringIteratorFieldIndex = 0; this.stringIteratorIteratedString = string.str; }
#string_iterator.prototype.next = function next() {
#var done = true;
#var value = undefined;
#var position = this.stringIteratorFieldIndex;
#if (position !== -1) {
#var string = this.stringIteratorIteratedString;
#var length = string.length >>> 0;
#if (position >= length) {
#this.stringIteratorFieldIndex = -1;
#} else {
#done = false;
#var first = string.charCodeAt(position);
#if (first < 0xD800 || first > 0xDBFF || position + 1 === length)
#value = new u8(string[position]);
#else {
#value = new u8(string[position]+string[position+1])
#}
#this.stringIteratorFieldIndex = position + value.length;
#}
#}
#return {
#value, done
#}
#}
#string.prototype[Symbol.iterator] = function () { return new string_iterator(this) }

// TODO: Make these functions actually work.
// strip_margin allows multi-line strings to be formatted in a way that removes white-space
// before a delimiter. By default `|` is used.
// Note: the delimiter has to be a byte at this time. That means surrounding
// the value in ``.
//
// Example:
// st := 'Hello there,
// |this is a string,
// |    Everything before the first | is removed'.strip_margin()
// Returns:
// Hello there,
// this is a string,
// Everything before the first | is removed
pub fn (s string) strip_margin() string {
	return s.strip_margin_custom(`|`)
}

// strip_margin_custom does the same as `strip_margin` but will use `del` as delimiter instead of `|`
[direct_array_access]
pub fn (s string) strip_margin_custom(del u8) string {
	mut sep := del
	if sep.is_space() {
		eprintln('Warning: `strip_margin` cannot use white-space as a delimiter')
		eprintln('    Defaulting to `|`')
		sep = `|`
	}
	// don't know how much space the resulting string will be, but the max it
	// can be is this big
	mut ret := []u8{}
	#ret = new array()

	mut count := 0
	for i := 0; i < s.len; i++ {
		if s[i] in [10, 13] {
			unsafe {
				ret[count] = s[i]
			}
			count++
			// CRLF
			if s[i] == 13 && i < s.len - 1 && s[i + 1] == 10 {
				unsafe {
					ret[count] = s[i + 1]
				}
				count++
				i++
			}
			for s[i] != sep {
				i++
				if i >= s.len {
					break
				}
			}
		} else {
			unsafe {
				ret[count] = s[i]
			}
			count++
		}
	}
	/*
	unsafe {
		ret[count] = 0
		return ret.vstring_with_len(count)
	}*/
	mut result := ''
	#for (let x of ret.arr) result.str += String.fromCharCode(x.val)

	return result
}

// split_nth splits the string based on the passed `delim` substring.
// It returns the first Nth parts. When N=0, return all the splits.
// The last returned element has the remainder of the string, even if
// the remainder contains more `delim` substrings.
[direct_array_access]
pub fn (s string) split_nth(delim string, nth int) []string {
	mut res := []string{}
	mut i := 0

	match delim.len {
		0 {
			i = 1
			for ch in s {
				if nth > 0 && i >= nth {
					res << s[i..]
					break
				}
				res << ch.str()
				i++
			}
			return res
		}
		1 {
			mut start := 0
			delim_byte := delim[0]

			for i < s.len {
				if s[i] == delim_byte {
					was_last := nth > 0 && res.len == nth - 1
					if was_last {
						break
					}
					val := s[start..i] //.substr(start, i)
					res << val
					start = i + delim.len
					i = start
				} else {
					i++
				}
			}

			// Then the remaining right part of the string
			if nth < 1 || res.len < nth {
				res << s[start..]
			}
			return res
		}
		else {
			mut start := 0
			// Take the left part for each delimiter occurrence
			for i <= s.len {
				is_delim := i + delim.len <= s.len && s[i..i + delim.len] == delim
				if is_delim {
					was_last := nth > 0 && res.len == nth - 1
					if was_last {
						break
					}
					val := s[start..i] // .substr(start, i)
					res << val
					start = i + delim.len
					i = start
				} else {
					i++
				}
			}
			// Then the remaining right part of the string
			if nth < 1 || res.len < nth {
				res << s[start..]
			}
			return res
		}
	}
}

struct RepIndex {
	idx     int
	val_idx int
}

// replace_each replaces all occurrences of the string pairs given in `vals`.
// Example: assert 'ABCD'.replace_each(['B','C/','C','D','D','C']) == 'AC/DC'
[direct_array_access]
pub fn (s string) replace_each(vals []string) string {
	if s.len == 0 || vals.len == 0 {
		return s.clone()
	}

	if vals.len % 2 != 0 {
		eprintln('string.replace_each(): odd number of strings')
		return s.clone()
	}

	// `rep` - string to replace
	// `with_` - string to replace with_
	// Remember positions of all rep strings, and calculate the length
	// of the new string to do just one allocation.

	mut idxs := []RepIndex{}
	mut idx := 0
	mut new_len := s.len
	s_ := s.clone()
	#function setCharAt(str,index,chr) {
	#if(index > str.length-1) return str;
	#return str.substring(0,index) + chr + str.substring(index+1);
	#}

	for rep_i := 0; rep_i < vals.len; rep_i = rep_i + 2 {
		rep := vals[rep_i]

		mut with_ := vals[rep_i + 1]
		with_ = with_

		for {
			idx = s_.index_after(rep, idx)
			if idx == -1 {
				break
			}

			for i in 0 .. rep.len {
				mut j_ := i
				j_ = j_
				#s_.str = setCharAt(s_.str,idx + i, String.fromCharCode(127))
			}

			rep_idx := RepIndex{
				idx: 0
				val_idx: 0
			}
			// todo: primitives should always be copied
			#rep_idx.idx = idx.val
			#rep_idx.val_idx = new int(rep_i.val)
			idxs << rep_idx
			idx += rep.len
			new_len += with_.len - rep.len
		}
	}

	if idxs.len == 0 {
		return s.clone()
	}

	idxs.sort(a.idx < b.idx)

	mut b := ''
	#for (let i = 0; i < new_len.val;i++) b.str += String.fromCharCode(127)

	new_len = new_len
	mut idx_pos := 0
	mut cur_idx := idxs[idx_pos]
	mut b_i := 0
	for i := 0; i < s.len; i++ {
		if i == cur_idx.idx {
			rep := vals[cur_idx.val_idx]
			with_ := vals[cur_idx.val_idx + 1]
			for j in 0 .. with_.len {
				mut j_ := j

				j_ = j_
				#b.str = setCharAt(b.str,b_i, with_.str[j])
				//#b.str[b_i] = with_.str[j]
				b_i++
			}
			i += rep.len - 1
			idx_pos++
			if idx_pos < idxs.len {
				cur_idx = idxs[idx_pos]
			}
		} else {
			#b.str = setCharAt(b.str,b_i,s.str[i]) //b.str[b_i] = s.str[i]
			b_i++
		}
	}

	return b
}

// last_index returns the position of the last occurrence of the input string.
fn (s string) last_index_(p string) int {
	if p.len > s.len || p.len == 0 {
		return -1
	}
	mut i := s.len - p.len
	for i >= 0 {
		mut j := 0
		for j < p.len && s[i + j] == p[j] {
			j++
		}
		if j == p.len {
			return i
		}
		i--
	}
	return -1
}

// last_index returns the position of the last occurrence of the input string.
pub fn (s string) last_index(p string) ?int {
	idx := s.last_index_(p)
	if idx == -1 {
		return none
	}
	return idx
}

pub fn (s string) trim_space() string {
	res := ''
	#res.str = s.str.trim()

	return res
}

pub fn (s string) index_after(p string, start int) int {
	if p.len > s.len {
		return -1
	}

	mut strt := start
	if start < 0 {
		strt = 0
	}
	if start >= s.len {
		return -1
	}
	mut i := strt

	for i < s.len {
		mut j := 0
		mut ii := i
		for j < p.len && s[ii] == p[j] {
			j++
			ii++
		}

		if j == p.len {
			return i
		}
		i++
	}
	return -1
}

pub fn (s string) split_into_lines() []string {
	mut res := []string{}
	if s.len == 0 {
		return res
	}
	#res.arr.arr = s.str.split(/\r?\n|\r/)
	#if (res.arr.arr[res.arr.arr.length-1] == "") res.arr.arr.pop();
	#res.arr.len = new int(res.arr.arr.length);
	#res.arr.cap = new int(res.arr.arr.length);

	return res
}

// replace_once replaces the first occurrence of `rep` with the string passed in `with`.
pub fn (s string) replace_once(rep string, with_ string) string {
	s2 := ''
	#s2.val = s.str.replace(rep.str,with_.str)

	return s2
}

pub fn (s string) title() string {
	words := s.split(' ')
	mut tit := []string{}
	for word in words {
		tit << word.capitalize()
	}

	title := tit.join(' ')
	return title
}

// index_any returns the position of any of the characters in the input string - if found.
pub fn (s string) index_any(chars string) int {
	for i, ss in s {
		for c in chars {
			if c == ss {
				return i
			}
		}
	}
	return -1
}

// limit returns a portion of the string, starting at `0` and extending for a given number of characters afterward.
// 'hello'.limit(2) => 'he'
// 'hi'.limit(10) => 'hi'
pub fn (s string) limit(max int) string {
	u := s.runes()
	if u.len <= max {
		return s.clone()
	}
	return u[0..max].string()
}

// is_title returns true if all words of the string is capitalized.
// Example: assert 'Hello V Developer'.is_title() == true
pub fn (s string) is_title() bool {
	words := s.split(' ')
	for word in words {
		if !word.is_capital() {
			return false
		}
	}
	return true
}

// is_capital returns `true`, if the first character in the string `s`,
// is a capital letter, and the rest are NOT.
// Example: assert 'Hello'.is_capital() == true
// Example: assert 'HelloWorld'.is_capital() == false
[direct_array_access]
pub fn (s string) is_capital() bool {
	if s.len == 0 || !(s[0] >= `A` && s[0] <= `Z`) {
		return false
	}
	for i in 1 .. s.len {
		if s[i] >= `A` && s[i] <= `Z` {
			return false
		}
	}
	return true
}

// starts_with_capital returns `true`, if the first character in the string `s`,
// is a capital letter, even if the rest are not.
// Example: assert 'Hello'.starts_with_capital() == true
// Example: assert 'Hello. World.'.starts_with_capital() == true
[direct_array_access]
pub fn (s string) starts_with_capital() bool {
	if s.len == 0 || !(s[0] >= `A` && s[0] <= `Z`) {
		return false
	}
	return true
}

// is_upper returns `true` if all characters in the string is uppercase.
// Example: assert 'HELLO V'.is_upper() == true
pub fn (s string) is_upper() bool {
	res := false
	#res.val = s.str == s.str.toUpperCase() && s.str != s.str.toLowerCase()

	return res
}

// is_upper returns `true` if all characters in the string is uppercase.
// Example: assert 'HELLO V'.is_upper() == true
pub fn (s string) is_lower() bool {
	res := false
	#res.val = s.str == s.str.toLowerCase() && s.str != s.str.toUpperCase()

	return res
}

pub fn (s string) reverse() string {
	res := ''
	#res.str = [...s.str].reverse().join('')

	return res
}

pub fn (s string) trim(cutset string) string {
	if s.len < 1 || cutset.len < 1 {
		return s.clone()
	}
	mut pos_left := 0
	mut pos_right := s.len - 1
	mut cs_match := true
	for pos_left <= s.len && pos_right >= -1 && cs_match {
		cs_match = false
		for cs in cutset {
			if s[pos_left] == cs {
				pos_left++
				cs_match = true
				break
			}
		}
		for cs in cutset {
			if s[pos_right] == cs {
				pos_right--
				cs_match = true
				break
			}
		}
		if pos_left > pos_right {
			return ''
		}
	}
	return s.substr(pos_left, pos_right + 1)
}

pub fn (s []string) join(sep string) string {
	mut res := ''
	for i, str in s {
		res += str
		if i != s.len - 1 {
			res += sep
		}
	}
	return res
}

// There's no better way to find length of JS String in bytes.
#Object.defineProperty(string.prototype,"len", { get: function() {return new int(new TextEncoder().encode(this.str).length);}, set: function(l) {/* ignore */ } });
// index returns the position of the first character of the input string.
// It will return `none` if the input string can't be found.
pub fn (s string) index(search string) ?int {
	res := 0
	#res.val = s.str.indexOf(search)
	if res == -1 {
		return none
	}
	return res
}

pub fn (_rune string) utf32_code() int {
	res := 0
	#res.val = s.str.charCodeAt()

	return res
}

pub fn tos(jsstr JS.String) string {
	res := ''
	#res.str = jsstr

	return res
}

pub fn (s string) compare(a string) int {
	min_len := if s.len < a.len { s.len } else { a.len }
	for i in 0 .. min_len {
		if s[i] < a[i] {
			return -1
		}
		if s[i] > a[i] {
			return 1
		}
	}

	if s.len < a.len {
		return -1
	}
	if s.len > a.len {
		return 1
	}
	return 0
}
