package table

import (
	"fmt"
	"strings"
	"unicode/utf8"
)

// RenderCSV renders the Table in CSV format. Example:
//
//	#,First Name,Last Name,Salary,
//	1,Arya,Stark,3000,
//	20,Jon,Snow,2000,"You know nothing, Jon Snow!"
//	300,Tyrion,Lannister,5000,
//	,,Total,10000,
func (t *Table) RenderCSV() string {
	t.initForRender(renderModeCSV)

	var out strings.Builder
	if t.numColumns > 0 {
		out.Grow(t.estimatedRenderLength())
		if t.title != "" {
			out.WriteString(t.title)
		}
		if t.autoIndex && len(t.rowsHeader) == 0 {
			t.csvRenderRow(&out, t.getAutoIndexColumnIDs(), renderHint{isAutoIndexRow: true, isHeaderRow: true})
		}
		t.csvRenderRows(&out, t.rowsHeader, renderHint{isHeaderRow: true})
		t.csvRenderRows(&out, t.rows, renderHint{})
		t.csvRenderRows(&out, t.rowsFooter, renderHint{isFooterRow: true})
		if t.caption != "" {
			out.WriteRune('\n')
			out.WriteString(t.caption)
		}
	}
	return t.render(&out)
}

// csvEscapeDoubleQuotes escapes double-quotes inside a quoted field by
// doubling them, as mandated by RFC 4180.
func (t *Table) csvEscapeDoubleQuotes(str string) string {
	return strings.ReplaceAll(str, "\"", "\"\"")
}

// csvProtectField neutralizes fields that spreadsheet applications would
// interpret as formulas (=, +, -, @, tab or CR prefix) by prefixing them with
// a single-quote.
func (t *Table) csvProtectField(str string) string {
	switch str[0] {
	case '=', '+', '-', '@', '\t', '\r':
		return "'" + str
	}
	return str
}

func (t *Table) csvRenderRow(out *strings.Builder, row rowStr, hint renderHint) {
	// when working on line number 2 or more, insert a newline first
	if out.Len() > 0 {
		out.WriteRune('\n')
	}

	// generate the columns to render in CSV format and append to "out"
	for colIdx, colStr := range row {
		// auto-index column
		if colIdx == 0 && t.autoIndex {
			if hint.isRegularRow() {
				fmt.Fprint(out, hint.rowNumber)
			}
			out.WriteRune(',')
		}
		if colIdx > 0 {
			out.WriteRune(',')
		}
		if t.style.CSV.FieldProtection && colStr != "" {
			colStr = t.csvProtectField(colStr)
		}
		if strings.ContainsAny(colStr, "\",\n") {
			out.WriteRune('"')
			out.WriteString(t.csvEscapeDoubleQuotes(colStr))
			out.WriteRune('"')
		} else if utf8.RuneCountInString(colStr) > 0 {
			out.WriteString(colStr)
		}
	}
	for colIdx := len(row); colIdx < t.numColumns; colIdx++ {
		out.WriteRune(',')
	}
}

func (t *Table) csvRenderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
	for rowIdx, row := range rows {
		hint.rowNumber = rowIdx + 1
		t.csvRenderRow(out, row, hint)
	}
}
