package cli

// Description: Useful small IO and helper functions.

import (
	"bufio"
	"bytes"
	"io"
	"strings"

	"gitlab.com/M0M097/pkg/internal/must"
)

// ANSI color codes for terminal output.
const (
	Reset   = "\033[0m"
	Red     = "\033[31m"
	Green   = "\033[32m"
	Yellow  = "\033[33m"
	Blue    = "\033[34m"
	Magenta = "\033[35m"
	Cyan    = "\033[36m"
	Gray    = "\033[37m"
	White   = "\033[97m"
)

// Banner creates a formatted banner with a heading and a message.
// It can be printed to raise attention to a message.
func Banner(heading, msg string, maxLineLength int) string {
	nsep := maxLineLength - len(heading) - 2
	return "\n" + Sep(nsep/2) + " " + heading + " " + Sep(nsep/2+nsep%2) +
		"\n" + WrapLines(msg, maxLineLength) + "\n" +
		Sep(maxLineLength)
}

// Sep returns a string consisting of nlen dashes.
func Sep(nlen int) string { return strings.Repeat("-", nlen) }

// WrapLines applies the WrapLine function to each line in the input string,
// effectively wrapping the entire text to the specified maximum line length
// while preserving existing line breaks.
func WrapLines(s string, maxLineLength int) string {
	lines := strings.Split(s, "\n")
	for i, line := range lines {
		lines[i] = WrapLine(line, maxLineLength)
	}
	return strings.Join(lines, "\n")
}

// WrapLine wraps a single line of text to the specified maximum line length.
// It breaks lines at spaces when possible. Words longer than the maximum line
// length are not split.
func WrapLine(s string, maxLineLength int) string {
	lineStart, b := 0, []byte(s)
	for lineStart+maxLineLength < len(b) {
		end := lineStart + maxLineLength
		i := bytes.LastIndexByte(b[lineStart:end+1], ' ')
		if i == -1 {
			i = bytes.IndexByte(b[end+1:], ' ')
			if i == -1 {
				break
			}
		}
		pos := lineStart + i
		b[pos], lineStart = '\n', pos+1
	}
	return string(b)
}

// StripCommentsAndTokenize reads from the provided io.Reader, strips comments
// (lines starting with '#'), and tokenizes the remaining lines into words
// which are returned as a slice of strings.
func StripCommentsAndTokenize(r io.Reader) (tokens []string) {
	for scanner := bufio.NewScanner(r); scanner.Scan(); {
		line := scanner.Text()
		commentIndex := strings.Index(line, "#")
		if commentIndex != -1 {
			line = line[:commentIndex]
		}
		tokens = append(tokens, strings.Fields(line)...)
	}
	return
}

// PromptUser writes a message to w and waits for a 'y' or 'n' response read
// from r. It returns true if the user responds with 'y' or 'Y', and false
// otherwise.
func PromptUser(w *bufio.Writer, r io.Reader, prompt string) bool {
	w.WriteString(prompt)
	w.WriteString(" [y/N]: ")
	w.Flush()
	b := must.ReadChar(r)
	for c := b; c != '\n'; { // consume until newline
		c = must.ReadChar(r)
	}
	return b == 'y' || b == 'Y'
}
