package gohtml

import (
	"bytes"
	"strings"
	"unicode"
)

type formatterTokenType int

const (
	formatterTokenType_Nothing formatterTokenType = iota
	formatterTokenType_Tag
	formatterTokenType_Text
)

type formattedBuffer struct {
	buffer  *bytes.Buffer
	rawMode bool

	indentString string
	indentLevel  int

	lineWrapColumn       int
	lineWrapMaxSpillover int

	curLineLength int
	prevTokenType formatterTokenType
}

func (bf *formattedBuffer) writeLineFeed() {
	if !bf.rawMode {
		// Strip trailing newlines
		bf.buffer = bytes.NewBuffer(bytes.TrimRightFunc(
			bf.buffer.Bytes(),
			func(r rune) bool {
				return r != '\n' && unicode.IsSpace(r)
			},
		))
	}

	bf.buffer.WriteString("\n")
	bf.curLineLength = 0
	bf.prevTokenType = formatterTokenType_Nothing
}

func (bf *formattedBuffer) writeIndent() {
	bf.buffer.WriteString(strings.Repeat(bf.indentString, bf.indentLevel))
	bf.curLineLength += len(bf.indentString) * bf.indentLevel
}

func (bf *formattedBuffer) writeToken(token string, kind formatterTokenType) {
	if bf.rawMode {
		bf.buffer.WriteString(token)
		bf.curLineLength += len(token)
		return
	}

	if bf.prevTokenType == formatterTokenType_Nothing && strings.TrimSpace(token) == "" {
		// It's a whitespace token, but we already have indentation which functions
		// the same, so we ignore it
		return
	}

	toWrite := token
	if kind == formatterTokenType_Text && bf.prevTokenType == formatterTokenType_Text {
		toWrite = " " + token
	}

	if bf.prevTokenType != formatterTokenType_Nothing && bf.lineWrapColumn > 0 {
		switch {
		case bf.curLineLength > bf.lineWrapColumn:
			// Current line is too long
			fallthrough

		case bf.curLineLength+len(toWrite) > bf.lineWrapColumn+bf.lineWrapMaxSpillover:
			// Current line + new token is too long even with allowed spillover
			fallthrough

		case bf.curLineLength+len(toWrite) > bf.lineWrapColumn &&
			bf.curLineLength > bf.lineWrapColumn-bf.lineWrapMaxSpillover:
			// Current line + new token is too long and doesn't quality for spillover

			bf.writeLineFeed()
			bf.writeToken(token, kind)
			return
		}
	}

	if bf.curLineLength == 0 {
		bf.writeIndent()
	}
	bf.buffer.WriteString(toWrite)
	bf.curLineLength += len(toWrite)
	bf.prevTokenType = kind
}

// unifyLineFeed unifies line feeds.
func unifyLineFeed(s string) string {
	return strings.ReplaceAll(strings.ReplaceAll(s, "\r\n", "\n"), "\r", "\n")
}
