package lipgloss

import (
	"math"
	"strings"

	"github.com/muesli/reflow/ansi"
)

// JoinHorizontal is a utility function for horizontally joining two
// potentially multi-lined strings along a vertical axis. The first argument is
// the position, with 0 being all the way at the top and 1 being all the way
// at the bottom.
//
// If you just want to align to the left, right or center you may as well just
// use the helper constants Top, Center, and Bottom.
//
// Example:
//
//	blockB := "...\n...\n..."
//	blockA := "...\n...\n...\n...\n..."
//
//	// Join 20% from the top
//	str := lipgloss.JoinHorizontal(0.2, blockA, blockB)
//
//	// Join on the top edge
//	str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB)
func JoinHorizontal(pos Position, strs ...string) string {
	if len(strs) == 0 {
		return ""
	}
	if len(strs) == 1 {
		return strs[0]
	}

	var (
		// Groups of strings broken into multiple lines
		blocks = make([][]string, len(strs))

		// Max line widths for the above text blocks
		maxWidths = make([]int, len(strs))

		// Height of the tallest block
		maxHeight int
	)

	// Break text blocks into lines and get max widths for each text block
	for i, str := range strs {
		blocks[i], maxWidths[i] = getLines(str)
		if len(blocks[i]) > maxHeight {
			maxHeight = len(blocks[i])
		}
	}

	// Add extra lines to make each side the same height
	for i := range blocks {
		if len(blocks[i]) >= maxHeight {
			continue
		}

		extraLines := make([]string, maxHeight-len(blocks[i]))

		switch pos { //nolint:exhaustive
		case Top:
			blocks[i] = append(blocks[i], extraLines...)

		case Bottom:
			blocks[i] = append(extraLines, blocks[i]...)

		default: // Somewhere in the middle
			n := len(extraLines)
			split := int(math.Round(float64(n) * pos.value()))
			top := n - split
			bottom := n - top

			blocks[i] = append(extraLines[top:], blocks[i]...)
			blocks[i] = append(blocks[i], extraLines[bottom:]...)
		}
	}

	// Merge lines
	var b strings.Builder
	for i := range blocks[0] { // remember, all blocks have the same number of members now
		for j, block := range blocks {
			b.WriteString(block[i])

			// Also make lines the same length
			b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.PrintableRuneWidth(block[i])))
		}
		if i < len(blocks[0])-1 {
			b.WriteRune('\n')
		}
	}

	return b.String()
}

// JoinVertical is a utility function for vertically joining two potentially
// multi-lined strings along a horizontal axis. The first argument is the
// position, with 0 being all the way to the left and 1 being all the way to
// the right.
//
// If you just want to align to the left, right or center you may as well just
// use the helper constants Left, Center, and Right.
//
// Example:
//
//	blockB := "...\n...\n..."
//	blockA := "...\n...\n...\n...\n..."
//
//	// Join 20% from the top
//	str := lipgloss.JoinVertical(0.2, blockA, blockB)
//
//	// Join on the right edge
//	str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB)
func JoinVertical(pos Position, strs ...string) string {
	if len(strs) == 0 {
		return ""
	}
	if len(strs) == 1 {
		return strs[0]
	}

	var (
		blocks   = make([][]string, len(strs))
		maxWidth int
	)

	for i := range strs {
		var w int
		blocks[i], w = getLines(strs[i])
		if w > maxWidth {
			maxWidth = w
		}
	}

	var b strings.Builder
	for i, block := range blocks {
		for j, line := range block {
			w := maxWidth - ansi.PrintableRuneWidth(line)

			switch pos { //nolint:exhaustive
			case Left:
				b.WriteString(line)
				b.WriteString(strings.Repeat(" ", w))

			case Right:
				b.WriteString(strings.Repeat(" ", w))
				b.WriteString(line)

			default: // Somewhere in the middle
				if w < 1 {
					b.WriteString(line)
					break
				}

				split := int(math.Round(float64(w) * pos.value()))
				right := w - split
				left := w - right

				b.WriteString(strings.Repeat(" ", left))
				b.WriteString(line)
				b.WriteString(strings.Repeat(" ", right))
			}

			// Write a newline as long as we're not on the last line of the
			// last block.
			if !(i == len(blocks)-1 && j == len(block)-1) {
				b.WriteRune('\n')
			}
		}
	}

	return b.String()
}
