// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package htmlutil

import (
	"fmt"
	"html/template"
	"io"
	"slices"
	"strings"
)

// ParseSizeAndClass get size and class from string with default values
// If present, "others" expects the new size first and then the classes to use
func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) {
	size := defaultSize
	if len(others) >= 1 {
		if v, ok := others[0].(int); ok && v != 0 {
			size = v
		}
	}
	class := defaultClass
	if len(others) >= 2 {
		if v, ok := others[1].(string); ok && v != "" {
			if class != "" {
				class += " "
			}
			class += v
		}
	}
	return size, class
}

func htmlFormatArgs(s template.HTML, rawArgs []any) []any {
	if !strings.Contains(string(s), "%") || len(rawArgs) == 0 {
		panic("HTMLFormat requires one or more arguments")
	}
	args := slices.Clone(rawArgs)
	for i, v := range args {
		switch v := v.(type) {
		case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
			// for most basic types (including template.HTML which is safe), just do nothing and use it
		case string:
			args[i] = template.HTMLEscapeString(v)
		case template.URL:
			args[i] = template.HTMLEscapeString(string(v))
		case fmt.Stringer:
			args[i] = template.HTMLEscapeString(v.String())
		default:
			args[i] = template.HTMLEscapeString(fmt.Sprint(v))
		}
	}
	return args
}

func HTMLFormat(s template.HTML, rawArgs ...any) template.HTML {
	return template.HTML(fmt.Sprintf(string(s), htmlFormatArgs(s, rawArgs)...))
}

func HTMLPrintf(w io.Writer, s template.HTML, rawArgs ...any) (int, error) {
	return fmt.Fprintf(w, string(s), htmlFormatArgs(s, rawArgs)...)
}

func HTMLPrint(w io.Writer, s template.HTML) (int, error) {
	return io.WriteString(w, string(s))
}

func HTMLPrintTag(w io.Writer, tag template.HTML, attrs map[string]string) (written int, err error) {
	n, err := io.WriteString(w, "<"+string(tag))
	written += n
	if err != nil {
		return written, err
	}
	for k, v := range attrs {
		n, err = fmt.Fprintf(w, ` %s="%s"`, template.HTMLEscapeString(k), template.HTMLEscapeString(v))
		written += n
		if err != nil {
			return written, err
		}
	}
	n, err = io.WriteString(w, ">")
	written += n
	return written, err
}

func EscapeString(s string) template.HTML {
	return template.HTML(template.HTMLEscapeString(s))
}

type HTMLBuilder struct {
	sb strings.Builder
}

func (b *HTMLBuilder) WriteString(s string) *HTMLBuilder {
	b.sb.WriteString(template.HTMLEscapeString(s))
	return b
}

func (b *HTMLBuilder) WriteHTML(s template.HTML) *HTMLBuilder {
	b.sb.WriteString(string(s))
	return b
}

func (b *HTMLBuilder) WriteFormat(fmt template.HTML, args ...any) *HTMLBuilder {
	_, _ = HTMLPrintf(&b.sb, fmt, args...)
	return b
}

func (b *HTMLBuilder) HTMLString() template.HTML {
	return template.HTML(b.sb.String())
}

func (b *HTMLBuilder) String() string {
	return b.sb.String()
}
