package pterm

import (
	"strings"

	"github.com/pterm/pterm/internal"
)

var (
	// DefaultHeader returns the printer for a default header text.
	// Defaults to LightWhite, Bold Text and a Gray DefaultHeader background.
	DefaultHeader = HeaderPrinter{
		TextStyle:       &ThemeDefault.HeaderTextStyle,
		BackgroundStyle: &ThemeDefault.HeaderBackgroundStyle,
		Margin:          5,
	}
)

// HeaderPrinter contains the data used to craft a header.
// A header is printed as a big box with text in it.
// Can be used as title screens or section separator.
type HeaderPrinter struct {
	TextStyle       *Style
	BackgroundStyle *Style
	Margin          int
	FullWidth       bool
}

// WithTextStyle returns a new HeaderPrinter with changed
func (p HeaderPrinter) WithTextStyle(style *Style) *HeaderPrinter {
	p.TextStyle = style
	return &p
}

// WithBackgroundStyle changes the background styling of the header.
func (p HeaderPrinter) WithBackgroundStyle(style *Style) *HeaderPrinter {
	p.BackgroundStyle = style
	return &p
}

// WithMargin changes the background styling of the header.
func (p HeaderPrinter) WithMargin(margin int) *HeaderPrinter {
	p.Margin = margin
	return &p
}

// WithFullWidth enables full width on a HeaderPrinter.
func (p HeaderPrinter) WithFullWidth(b ...bool) *HeaderPrinter {
	p.FullWidth = internal.WithBoolean(b)
	return &p
}

// Sprint formats using the default formats for its operands and returns the resulting string.
// Spaces are added between operands when neither is a string.
func (p HeaderPrinter) Sprint(a ...interface{}) string {
	if RawOutput {
		return Sprint(a...)
	}

	if p.TextStyle == nil {
		p.TextStyle = NewStyle()
	}
	if p.BackgroundStyle == nil {
		p.BackgroundStyle = NewStyle()
	}

	text := Sprint(a...)

	var blankLine string

	longestLine := internal.ReturnLongestLine(text, "\n")
	longestLineLen := len(RemoveColorFromString(longestLine)) + p.Margin*2

	if p.FullWidth {
		text = splitText(text, GetTerminalWidth()-p.Margin*2)
		blankLine = strings.Repeat(" ", GetTerminalWidth())
	} else {
		if longestLineLen > GetTerminalWidth() {
			text = splitText(text, GetTerminalWidth()-p.Margin*2)
			blankLine = strings.Repeat(" ", GetTerminalWidth())
		} else {
			text = splitText(text, longestLineLen-p.Margin*2)
			blankLine = strings.Repeat(" ", longestLineLen)
		}
	}

	var marginString string
	var ret string

	if p.FullWidth {
		longestLineLen = len(RemoveColorFromString(internal.ReturnLongestLine(text, "\n")))
		marginString = strings.Repeat(" ", (GetTerminalWidth()-longestLineLen)/2)
	} else {
		marginString = strings.Repeat(" ", p.Margin)
	}

	ret += p.BackgroundStyle.Sprint(blankLine) + "\n"
	for _, line := range strings.Split(text, "\n") {
		line = strings.ReplaceAll(line, "\n", "")
		line = marginString + line + marginString
		if len(line) < len(blankLine) {
			line += strings.Repeat(" ", len(blankLine)-len(line))
		}
		ret += p.BackgroundStyle.Sprint(p.TextStyle.Sprint(line)) + "\n"
	}
	ret += p.BackgroundStyle.Sprint(blankLine) + "\n"

	return ret
}

func splitText(text string, width int) string {
	var lines []string
	linesTmp := strings.Split(text, "\n")
	for _, line := range linesTmp {
		if len(RemoveColorFromString(line)) > width {
			extraLines := []string{""}
			extraLinesCounter := 0
			for i, letter := range line {
				if i%width == 0 && i != 0 {
					extraLinesCounter++
					extraLines = append(extraLines, "")
				}
				extraLines[extraLinesCounter] += string(letter)
			}
			for _, extraLine := range extraLines {
				extraLine += "\n"
				lines = append(lines, extraLine)
			}
		} else {
			line += "\n"
			lines = append(lines, line)
		}
	}

	var line string
	for _, s := range lines {
		line += s
	}

	return strings.TrimSuffix(line, "\n")
}

// Sprintln formats using the default formats for its operands and returns the resulting string.
// Spaces are always added between operands and a newline is appended.
func (p HeaderPrinter) Sprintln(a ...interface{}) string {
	return p.Sprint(strings.TrimSuffix(Sprintln(a...), "\n"))
}

// Sprintf formats according to a format specifier and returns the resulting string.
func (p HeaderPrinter) Sprintf(format string, a ...interface{}) string {
	return p.Sprint(Sprintf(format, a...))
}

// Sprintfln formats according to a format specifier and returns the resulting string.
// Spaces are always added between operands and a newline is appended.
func (p HeaderPrinter) Sprintfln(format string, a ...interface{}) string {
	return p.Sprintf(format, a...) + "\n"
}

// Print formats using the default formats for its operands and writes to standard output.
// Spaces are added between operands when neither is a string.
// It returns the number of bytes written and any write error encountered.
func (p *HeaderPrinter) Print(a ...interface{}) *TextPrinter {
	Print(p.Sprint(a...))
	tp := TextPrinter(p)
	return &tp
}

// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func (p *HeaderPrinter) Println(a ...interface{}) *TextPrinter {
	Print(p.Sprintln(a...))
	tp := TextPrinter(p)
	return &tp
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func (p *HeaderPrinter) Printf(format string, a ...interface{}) *TextPrinter {
	Print(p.Sprintf(format, a...))
	tp := TextPrinter(p)
	return &tp
}

// Printfln formats according to a format specifier and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func (p *HeaderPrinter) Printfln(format string, a ...interface{}) *TextPrinter {
	Print(p.Sprintfln(format, a...))
	tp := TextPrinter(p)
	return &tp
}

// PrintOnError prints every error which is not nil.
// If every error is nil, nothing will be printed.
// This can be used for simple error checking.
func (p *HeaderPrinter) PrintOnError(a ...interface{}) *TextPrinter {
	for _, arg := range a {
		if err, ok := arg.(error); ok {
			if err != nil {
				p.Println(err)
			}
		}
	}

	tp := TextPrinter(p)
	return &tp
}
