package nsdxml

import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"strings"

	"golang.org/x/text/encoding/charmap"
)

// xmlProlog — пролог windows-1251 XML, который мы пишем при сериализации.
const xmlProlog = `<?xml version="1.0" encoding="windows-1251"?>` + "\n"

// ErrUnmappable возвращается, когда руна не имеет представления в windows-1251.
var ErrUnmappable = errors.New("nsdxml: rune не представим в windows-1251")

// DecodeWindows1251 преобразует байты в кодировке windows-1251 в UTF-8.
// Использует штатную реализацию golang.org/x/text/encoding/charmap.
func DecodeWindows1251(src []byte) []byte {
	out, _ := charmap.Windows1251.NewDecoder().Bytes(src)
	return out
}

// EncodeWindows1251 преобразует UTF-8 байты в windows-1251. Если в
// исходной строке встречается руна, не выразимая в CP1251, возвращает
// ErrUnmappable с указанием смещения.
func EncodeWindows1251(src []byte) ([]byte, error) {
	out, err := charmap.Windows1251.NewEncoder().Bytes(src)
	if err != nil {
		return nil, fmt.Errorf("%w: %w", ErrUnmappable, err)
	}
	return out, nil
}

// CharsetReader пригоден к использованию в xml.Decoder.CharsetReader.
// Для charset "windows-1251"/"cp1251" возвращает декодирующий reader.
// Для UTF-8 и неуказанного charset — пробрасывает поток без изменений.
func CharsetReader(charset string, input io.Reader) (io.Reader, error) {
	switch strings.ToLower(charset) {
	case "windows-1251", "cp1251":
		return charmap.Windows1251.NewDecoder().Reader(input), nil
	case "", "utf-8", "utf8":
		return input, nil
	}
	return nil, fmt.Errorf("nsdxml: неизвестная кодировка %q", charset)
}

// Marshal сериализует v в XML, добавляет пролог с encoding="windows-1251"
// и преобразует поток в windows-1251.
func Marshal(v any) ([]byte, error) {
	utf8Body, err := xml.MarshalIndent(v, "", "\t")
	if err != nil {
		return nil, err
	}
	withProlog := append([]byte(xmlProlog), utf8Body...)
	return EncodeWindows1251(withProlog)
}

// Unmarshal разбирает XML (windows-1251 или UTF-8) в v. Использует
// CharsetReader для перекодирования входа, если в прологе указана
// windows-1251.
func Unmarshal(data []byte, v any) error {
	dec := xml.NewDecoder(bytes.NewReader(data))
	dec.CharsetReader = CharsetReader
	return dec.Decode(v)
}
