package format

import (
	"fmt"
	"strings"

	"github.com/fatih/color"

	"github.com/nicholas-fedor/shoutrrr/pkg/util"
)

// Constants for console rendering.
const (
	DescriptionColumnWidth = 60 // Width of the description column in console output
	ItemSeparatorLength    = 2  // Length of the ", " separator between container items
	DefaultValueOffset     = 16 // Minimum offset before description when no values are shown
	ValueOffset            = 30 // Offset before description when values are shown
	ContainerBracketLength = 4  // Length of container delimiters (e.g., "{ }" or "[ ]")
	KeySeparatorLength     = 2  // Length of the ": " separator after a key in containers
)

// ConsoleTreeRenderer renders a ContainerNode tree into an ansi-colored console string.
type ConsoleTreeRenderer struct {
	WithValues bool
}

// RenderTree renders a ContainerNode tree into an ansi-colored console string.
func (r ConsoleTreeRenderer) RenderTree(root *ContainerNode, _ string) string {
	stringBuilder := strings.Builder{}

	for _, node := range root.Items {
		fieldKey := node.Field().Name
		stringBuilder.WriteString(fieldKey)

		for i := len(fieldKey); i <= root.MaxKeyLength; i++ {
			stringBuilder.WriteRune(' ')
		}

		var valueLen int // Initialize without assignment; set later

		preLen := DefaultValueOffset // Default spacing before the description when no values are rendered

		field := node.Field()

		if r.WithValues {
			preLen = ValueOffset // Adjusts the spacing when values are included
			valueLen = r.writeNodeValue(&stringBuilder, node)
		} else {
			// Since no values were supplied, substitute the value with the type
			typeName := field.Type.String()

			// If the value is an enum type, providing the name is a bit pointless
			// Instead, use a common string "option" to signify the type
			if field.EnumFormatter != nil {
				typeName = "option"
			}

			valueLen = len(typeName)
			stringBuilder.WriteString(color.CyanString(typeName))
		}

		stringBuilder.WriteString(strings.Repeat(" ", util.Max(preLen-valueLen, 1)))
		stringBuilder.WriteString(ColorizeDesc(field.Description))
		stringBuilder.WriteString(
			strings.Repeat(" ", util.Max(DescriptionColumnWidth-len(field.Description), 1)),
		)

		if len(field.URLParts) > 0 && field.URLParts[0] != URLQuery {
			stringBuilder.WriteString(" <URL: ")

			for i, part := range field.URLParts {
				if i > 0 {
					stringBuilder.WriteString(", ")
				}

				if part > URLPath {
					part = URLPath
				}

				stringBuilder.WriteString(ColorizeEnum(part))
			}

			stringBuilder.WriteString(">")
		}

		if len(field.Template) > 0 {
			stringBuilder.WriteString(
				fmt.Sprintf(" <Template: %s>", ColorizeString(field.Template)),
			)
		}

		if len(field.DefaultValue) > 0 {
			stringBuilder.WriteString(
				fmt.Sprintf(
					" <Default: %s>",
					ColorizeValue(field.DefaultValue, field.EnumFormatter != nil),
				),
			)
		}

		if field.Required {
			stringBuilder.WriteString(fmt.Sprintf(" <%s>", ColorizeFalse("Required")))
		}

		if len(field.Keys) > 1 {
			stringBuilder.WriteString(" <Aliases: ")

			for i, key := range field.Keys {
				if i == 0 {
					// Skip primary alias (as it's the same as the field name)
					continue
				}

				if i > 1 {
					stringBuilder.WriteString(", ")
				}

				stringBuilder.WriteString(ColorizeString(key))
			}

			stringBuilder.WriteString(">")
		}

		if field.EnumFormatter != nil {
			stringBuilder.WriteString(ColorizeContainer(" ["))

			for i, name := range field.EnumFormatter.Names() {
				if i != 0 {
					stringBuilder.WriteString(", ")
				}

				stringBuilder.WriteString(ColorizeEnum(name))
			}

			stringBuilder.WriteString(ColorizeContainer("]"))
		}

		stringBuilder.WriteRune('\n')
	}

	return stringBuilder.String()
}

func (r ConsoleTreeRenderer) writeNodeValue(stringBuilder *strings.Builder, node Node) int {
	if contNode, isContainer := node.(*ContainerNode); isContainer {
		return r.writeContainer(stringBuilder, contNode)
	}

	if valNode, isValue := node.(*ValueNode); isValue {
		stringBuilder.WriteString(ColorizeToken(valNode.Value, valNode.tokenType))

		return len(valNode.Value)
	}

	stringBuilder.WriteRune('?')

	return 1
}

func (r ConsoleTreeRenderer) writeContainer(
	stringBuilder *strings.Builder,
	node *ContainerNode,
) int {
	kind := node.Type.Kind()
	hasKeys := !util.IsCollection(kind)

	totalLen := ContainerBracketLength // Length of the opening and closing brackets ({ } or [ ])

	if hasKeys {
		stringBuilder.WriteString("{ ")
	} else {
		stringBuilder.WriteString("[ ")
	}

	for i, itemNode := range node.Items {
		if i != 0 {
			stringBuilder.WriteString(", ")

			totalLen += KeySeparatorLength // Accounts for the : separator between keys and values in containers
		}

		if hasKeys {
			itemKey := itemNode.Field().Name
			stringBuilder.WriteString(itemKey)
			stringBuilder.WriteString(": ")

			totalLen += len(itemKey) + ItemSeparatorLength
		}

		valLen := r.writeNodeValue(stringBuilder, itemNode)
		totalLen += valLen
	}

	if hasKeys {
		stringBuilder.WriteString(" }")
	} else {
		stringBuilder.WriteString(" ]")
	}

	return totalLen
}
