package headers

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"strings"
)

// Rule contains headers for a single path.
type Rule struct {
	Path    string
	Headers http.Header
}

// Parse parses the given reader.
func Parse(r io.Reader) (rules []Rule, err error) {
	scanner := bufio.NewScanner(r)
	current := Rule{}
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" || strings.HasPrefix(strings.TrimLeft(line, " \t"), "#") {
			continue
		}
		if line[0] != ' ' && line[0] != '\t' {
			// this is a path line
			if current.Headers != nil {
				rules = append(rules, current)
			}
			current = Rule{Path: strings.TrimRight(line, " \t"), Headers: http.Header{}}
		} else {
			// this is a header line
			if current.Path == "" {
				return nil, fmt.Errorf("header line without preceding path line: %q", line)
			}
			name, value, found := strings.Cut(line, ":")
			if !found {
				return nil, fmt.Errorf("header line without separator: %q", line)
			}
			current.Headers.Add(strings.TrimSpace(name), strings.TrimSpace(value))
		}
	}
	if current.Headers != nil {
		rules = append(rules, current)
	}
	err = scanner.Err()
	return
}

// ParseString parses the given string.
func ParseString(str string) ([]Rule, error) {
	return Parse(strings.NewReader(str))
}

// Unparse serializes to the given writer.
func Unparse(w io.Writer, rules []Rule) (err error) {
	for _, rule := range rules {
		if strings.IndexAny(rule.Path, " \t\n") == 0 {
			return fmt.Errorf("invalid path: %v", rule.Path)
		}
		_, err = fmt.Fprintf(w, "%s\n", rule.Path)
		if err != nil {
			return
		}
		for name, values := range rule.Headers {
			for _, value := range values {
				_, err = fmt.Fprintf(w, "\t%s: %s\n", name, value)
				if err != nil {
					return
				}
			}
		}
	}
	return
}

// UnparseString serializes to a string.
func UnparseString(rules []Rule) (result string, err error) {
	builder := strings.Builder{}
	err = Unparse(&builder, rules)
	return builder.String(), err
}

// Panics if there is an error.
func Must[T any](value T, err error) T {
	if err != nil {
		panic(err)
	}
	return value
}
