package pterm

import (
	"fmt"
	"io"
	"strings"

	"github.com/mattn/go-runewidth"

	"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
	Writer          io.Writer
}

// 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
}

// WithWriter sets the custom Writer.
func (p HeaderPrinter) WithWriter(writer io.Writer) *HeaderPrinter {
	p.Writer = writer
	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 ...any) 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 := runewidth.StringWidth(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 strings.Builder

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

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

	return ret.String()
}

func splitText(text string, width int) string {
	var lines []string
	linesTmp := strings.Split(text, "\n")
	for _, line := range linesTmp {
		if runewidth.StringWidth(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 ...any) 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 ...any) 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 ...any) 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 ...any) *TextPrinter {
	Fprint(p.Writer, 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 ...any) *TextPrinter {
	Fprint(p.Writer, 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 ...any) *TextPrinter {
	Fprint(p.Writer, 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 ...any) *TextPrinter {
	Fprint(p.Writer, 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 ...any) *TextPrinter {
	for _, arg := range a {
		if err, ok := arg.(error); ok {
			if err != nil {
				p.Println(err)
			}
		}
	}

	tp := TextPrinter(p)
	return &tp
}

// PrintOnErrorf wraps every error which is not nil and prints it.
// If every error is nil, nothing will be printed.
// This can be used for simple error checking.
func (p *HeaderPrinter) PrintOnErrorf(format string, a ...any) *TextPrinter {
	for _, arg := range a {
		if err, ok := arg.(error); ok {
			if err != nil {
				p.Println(fmt.Errorf(format, err))
			}
		}
	}

	tp := TextPrinter(p)
	return &tp
}
