package printer

import (
	"strings"
	"unicode/utf8"

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/stringutil"
)

var _ EmitTextWriter = &textWriter{}

type textWriter struct {
	newLine                 string
	indentSize              int
	builder                 strings.Builder
	lastWritten             string
	indent                  int
	lineStart               bool
	lineCount               int
	linePos                 int
	hasTrailingCommentState bool
}

func (w *textWriter) Clear() {
	*w = textWriter{newLine: w.newLine, indentSize: w.indentSize, lineStart: true}
}

func (w *textWriter) Grow(n int) {
	w.builder.Grow(n)
}

func (w *textWriter) DecreaseIndent() {
	w.indent--
}

// GetColumn returns the column position measured in UTF-16 code units
// for source map compatibility.
func (w *textWriter) GetColumn() core.UTF16Offset {
	if w.lineStart {
		return core.UTF16Offset(w.indent * w.indentSize)
	}
	// Count UTF-16 code units from the last line start.
	// For ASCII-only output (the common case), this equals the byte count.
	return core.UTF16Len(w.builder.String()[w.linePos:])
}

func (w *textWriter) GetIndent() int {
	return w.indent
}

func (w *textWriter) GetLine() int {
	return w.lineCount
}

func (w *textWriter) String() string {
	return w.builder.String()
}

func (w *textWriter) GetTextPos() int {
	return w.builder.Len()
}

func (w textWriter) HasTrailingComment() bool {
	return w.hasTrailingCommentState
}

func (w *textWriter) HasTrailingWhitespace() bool {
	if w.builder.Len() == 0 {
		return false
	}
	ch, _ := utf8.DecodeLastRuneInString(w.lastWritten)
	if ch == utf8.RuneError {
		return false
	}
	return stringutil.IsWhiteSpaceLike(ch)
}

func (w *textWriter) IncreaseIndent() {
	w.indent++
}

func (w *textWriter) IsAtStartOfLine() bool {
	return w.lineStart
}

func (w *textWriter) RawWrite(s string) {
	if s != "" {
		w.builder.WriteString(s)
		w.lastWritten = s
		w.hasTrailingCommentState = false
	}
	w.updateLineCountAndPosFor(s)
}

func (w *textWriter) updateLineCountAndPosFor(s string) {
	var count int
	var lastLineStart core.TextPos

	for lineStart := range core.ComputeECMALineStartsSeq(s) {
		count++
		lastLineStart = lineStart
	}

	if count > 1 {
		w.lineCount += count - 1
		curLen := w.builder.Len()
		w.linePos = curLen - len(s) + int(lastLineStart)
		w.lineStart = (w.linePos - curLen) == 0
		return
	}
	w.lineStart = false
}

const defaultIndentSize = 4

// GetDefaultIndentSize returns the default indent size (4 spaces) used when no specific indent size is configured.
func GetDefaultIndentSize() int {
	return defaultIndentSize
}

func getIndentString(indent int, indentSize int) string {
	if indent == 0 {
		return ""
	}
	// TODO: This is cached in tsc - should it be cached here?
	return strings.Repeat(" ", indent*indentSize)
}

func (w *textWriter) writeText(s string) {
	if s != "" {
		if w.lineStart {
			w.builder.WriteString(getIndentString(w.indent, w.indentSize))
			w.lineStart = false
		}
		w.builder.WriteString(s)
		w.lastWritten = s
		w.updateLineCountAndPosFor(s)
	}
}

func (w *textWriter) Write(s string) {
	if s != "" {
		w.hasTrailingCommentState = false
	}
	w.writeText(s)
}

func (w *textWriter) WriteComment(text string) {
	if text != "" {
		w.hasTrailingCommentState = true
	}
	w.writeText(text)
}

func (w *textWriter) WriteKeyword(text string) {
	w.Write(text)
}

func (w *textWriter) writeLineRaw() {
	w.builder.WriteString(w.newLine)
	w.lastWritten = w.newLine
	w.lineCount++
	w.linePos = w.builder.Len()
	w.lineStart = true
	w.hasTrailingCommentState = false
}

func (w *textWriter) WriteLine() {
	if !w.lineStart {
		w.writeLineRaw()
	}
}

func (w *textWriter) WriteLineForce(force bool) {
	if !w.lineStart || force {
		w.writeLineRaw()
	}
}

func (w *textWriter) WriteLiteral(s string) {
	w.Write(s)
}

func (w *textWriter) WriteOperator(text string) {
	w.Write(text)
}

func (w *textWriter) WriteParameter(text string) {
	w.Write(text)
}

func (w *textWriter) WriteProperty(text string) {
	w.Write(text)
}

func (w *textWriter) WritePunctuation(text string) {
	w.Write(text)
}

func (w *textWriter) WriteSpace(text string) {
	w.Write(text)
}

func (w *textWriter) WriteStringLiteral(text string) {
	w.Write(text)
}

func (w *textWriter) WriteSymbol(text string, symbol *ast.Symbol) {
	w.Write(text)
}

func (w *textWriter) WriteTrailingSemicolon(text string) {
	w.Write(text)
}

func NewTextWriter(newLine string, indentSize int) EmitTextWriter {
	if indentSize <= 0 {
		indentSize = 4
	}
	var w textWriter
	w.newLine = newLine
	w.indentSize = indentSize
	w.Clear()
	return &w
}
