package stringsupport

import (
	"bytes"
	"fmt"
	"io"
	"log/slog"
	"strconv"
	"strings"

	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"
)

// UTF8ToSJIS encodes a UTF-8 string to Shift-JIS bytes, silently dropping any
// runes that cannot be represented in Shift-JIS.
func UTF8ToSJIS(x string) []byte {
	e := japanese.ShiftJIS.NewEncoder()
	xt, _, err := transform.String(e, x)
	if err != nil {
		// Filter out runes that can't be encoded to Shift-JIS instead of
		// crashing the server (see PR #116).
		var filtered []rune
		for _, r := range x {
			if _, _, err := transform.String(japanese.ShiftJIS.NewEncoder(), string(r)); err == nil {
				filtered = append(filtered, r)
			}
		}
		xt, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), string(filtered))
	}
	return []byte(xt)
}

// SJISToUTF8 decodes Shift-JIS bytes to a UTF-8 string.
func SJISToUTF8(b []byte) (string, error) {
	d := japanese.ShiftJIS.NewDecoder()
	result, err := io.ReadAll(transform.NewReader(bytes.NewReader(b), d))
	if err != nil {
		return "", fmt.Errorf("ShiftJIS decode: %w", err)
	}
	return string(result), nil
}

// SJISToUTF8Lossy decodes Shift-JIS bytes to a UTF-8 string, logging
// any decoding error at debug level instead of returning it.
func SJISToUTF8Lossy(b []byte) string {
	s, err := SJISToUTF8(b)
	if err != nil {
		slog.Debug("SJIS decode failed", "error", err, "raw_len", len(b))
	}
	return s
}

// ToNGWord converts a UTF-8 string into a slice of uint16 values in the
// Shift-JIS byte-swapped format used by the MHF NG-word (chat filter) system.
func ToNGWord(x string) []uint16 {
	var w []uint16
	for _, r := range x {
		if r > 0xFF {
			t := UTF8ToSJIS(string(r))
			if len(t) > 1 {
				w = append(w, uint16(t[1])<<8|uint16(t[0]))
			} else if len(t) == 1 {
				w = append(w, uint16(t[0]))
			}
			// Skip runes that produced no SJIS output (unsupported characters)
		} else {
			w = append(w, uint16(r))
		}
	}
	return w
}

// PaddedString returns a fixed-width null-terminated byte slice of the given
// size. If t is true the string is first encoded to Shift-JIS.
func PaddedString(x string, size uint, t bool) []byte {
	if t {
		e := japanese.ShiftJIS.NewEncoder()
		xt, _, err := transform.String(e, x)
		if err != nil {
			return make([]byte, size)
		}
		x = xt
	}
	out := make([]byte, size)
	copy(out, x)
	out[len(out)-1] = 0
	return out
}

// CSVAdd appends v to the comma-separated integer list if not already present.
func CSVAdd(csv string, v int) string {
	if len(csv) == 0 {
		return strconv.Itoa(v)
	}
	if CSVContains(csv, v) {
		return csv
	} else {
		return csv + "," + strconv.Itoa(v)
	}
}

// CSVRemove removes v from the comma-separated integer list.
func CSVRemove(csv string, v int) string {
	s := strings.Split(csv, ",")
	for i, e := range s {
		if e == strconv.Itoa(v) {
			s[i] = s[len(s)-1]
			s = s[:len(s)-1]
		}
	}
	return strings.Join(s, ",")
}

// CSVContains reports whether v is present in the comma-separated integer list.
func CSVContains(csv string, v int) bool {
	s := strings.Split(csv, ",")
	for i := 0; i < len(s); i++ {
		j, _ := strconv.ParseInt(s[i], 10, 32)
		if int(j) == v {
			return true
		}
	}
	return false
}

// CSVLength returns the number of elements in the comma-separated list.
func CSVLength(csv string) int {
	if csv == "" {
		return 0
	}
	s := strings.Split(csv, ",")
	return len(s)
}

// CSVElems parses the comma-separated integer list into an int slice.
func CSVElems(csv string) []int {
	var r []int
	if csv == "" {
		return r
	}
	s := strings.Split(csv, ",")
	for i := 0; i < len(s); i++ {
		j, _ := strconv.ParseInt(s[i], 10, 32)
		r = append(r, int(j))
	}
	return r
}

// CSVGetIndex returns the integer at position i in the comma-separated list,
// or 0 if i is out of range.
func CSVGetIndex(csv string, i int) int {
	s := CSVElems(csv)
	if i < len(s) {
		return s[i]
	}
	return 0
}

// CSVSetIndex replaces the integer at position i in the comma-separated list
// with v. If i is out of range the list is returned unchanged.
func CSVSetIndex(csv string, i int, v int) string {
	s := CSVElems(csv)
	if i < len(s) {
		s[i] = v
	}
	var r []string
	for j := 0; j < len(s); j++ {
		r = append(r, fmt.Sprintf(`%d`, s[j]))
	}
	return strings.Join(r, ",")
}
