package templates

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"code.nonshy.com/nonshy/website/pkg/config"
	"code.nonshy.com/nonshy/website/pkg/markdown"
	"code.nonshy.com/nonshy/website/pkg/models"
	"code.nonshy.com/nonshy/website/pkg/photo"
	"code.nonshy.com/nonshy/website/pkg/session"
	"code.nonshy.com/nonshy/website/pkg/utility"
)

// Generics
type Number interface {
	int | int64 | uint64 | float32 | float64
}

// TemplateFuncs available to all pages.
func TemplateFuncs(r *http.Request) template.FuncMap {
	return template.FuncMap{
		"InputCSRF":         InputCSRF(r),
		"SincePrettyCoarse": SincePrettyCoarse(),
		"ComputeAge":        utility.Age,
		"Split":             strings.Split,
		"ToMarkdown":        ToMarkdown,
		"ToJSON":            ToJSON,
		"ToHTML":            ToHTML,
		"PhotoURL":          photo.URLPath,
		"Now":               time.Now,
		"PrettyTitle": func() template.HTML {
			return template.HTML(fmt.Sprintf(
				`<strong style="color: #0077FF">non</strong>` +
					`<strong style="color: #FF77FF">shy</strong>`,
			))
		},
		"PrettyTitleShort": func() template.HTML {
			return template.HTML(`<strong style="color: #0077FF">n</strong>` +
				`<strong style="color: #FF77FF">s</strong>`,
			)
		},
		"PrettyCircle": func() template.HTML {
			return template.HTML(
				`<span style="color: #0077ff">I</span><span style="color: #1c77ff">n</span><span style="color: #3877ff">n</span><span style="color: #5477ff">e</span><span style="color: #7077ff">r</span><span style="color: #8c77ff"> </span><span style="color: #aa77ff">c</span><span style="color: #b877ff">i</span><span style="color: #c677ff">r</span><span style="color: #d477ff">c</span><span style="color: #e277ff">l</span><span style="color: #f077ff">e</span>`,
			)
		},
		"Pluralize":    Pluralize[int],
		"Pluralize64":  Pluralize[int64],
		"PluralizeU64": Pluralize[uint64],
		"Substring":    Substring,
		"TrimEllipses": TrimEllipses,
		"IterRange":    IterRange,
		"SubtractInt":  SubtractInt,
		"UrlEncode":    UrlEncode,
		"QueryPlus":    QueryPlus(r),
		"SimplePager":  SimplePager(r),
		"HasSuffix":    strings.HasSuffix,

		// Test if a photo should be blurred ({{BlurExplicit .Photo}})
		"BlurExplicit": BlurExplicit(r),
	}
}

// InputCSRF returns the HTML snippet for a CSRF token hidden input field.
func InputCSRF(r *http.Request) func() template.HTML {
	return func() template.HTML {
		ctx := r.Context()
		if token, ok := ctx.Value(session.CSRFKey).(string); ok {
			return template.HTML(fmt.Sprintf(
				`<input type="hidden" name="%s" value="%s">`,
				config.CSRFInputName,
				token,
			))
		} else {
			return template.HTML(`[CSRF middleware error]`)
		}
	}
}

// BlurExplicit returns true if the current user has the blur_explicit setting on and the given Photo is Explicit.
func BlurExplicit(r *http.Request) func(*models.Photo) bool {
	return func(photo *models.Photo) bool {
		if !photo.Explicit {
			return false
		}

		currentUser, err := session.CurrentUser(r)
		if err != nil {
			return false
		}

		return currentUser.GetProfileField("blur_explicit") == "true"
	}
}

// SincePrettyCoarse formats a time.Duration in plain English. Intended for "joined 2 months ago" type
// strings - returns the coarsest level of granularity.
func SincePrettyCoarse() func(time.Time) template.HTML {
	return func(since time.Time) template.HTML {
		return template.HTML(utility.FormatDurationCoarse(time.Since(since)))
	}
}

// ToMarkdown renders input text as Markdown.
func ToMarkdown(input string) template.HTML {
	return template.HTML(markdown.Render(input))
}

// ToHTML renders input text as trusted HTML code.
func ToHTML(input string) template.HTML {
	return template.HTML(input)
}

// ToJSON will stringify any json-serializable object.
func ToJSON(v any) template.JS {
	bin, err := json.Marshal(v)
	if err != nil {
		return template.JS(err.Error())
	}
	return template.JS(string(bin))
}

// Pluralize text based on a quantity number. Provide up to 2 labels for the
// singular and plural cases, or the defaults are "", "s"
func Pluralize[V Number](count V, labels ...string) string {
	if len(labels) < 2 {
		labels = []string{"", "s"}
	}

	if count == 1 {
		return labels[0]
	}
	return labels[1]
}

// Substring safely returns the first N characters of a string.
func Substring(value string, n int) string {
	if n > len(value) {
		return value
	}
	return value[:n]
}

// TrimEllipses is like Substring but will add an ellipses if truncated.
func TrimEllipses(value string, n int) string {
	if n > len(value) {
		return value
	}
	return value[:n] + "…"
}

// IterRange returns a list of integers useful for pagination.
func IterRange(start, n int) []int {
	var result = []int{}
	for i := start; i <= n; i++ {
		result = append(result, i)
	}
	return result
}

// SubtractInt subtracts two numbers.
func SubtractInt(a, b int) int {
	return a - b
}

// UrlEncode escapes a series of values (joined with no delimiter)
func UrlEncode(values ...interface{}) string {
	var result string
	for _, value := range values {
		result += url.QueryEscape(fmt.Sprintf("%v", value))
	}
	return result
}

// QueryPlus takes the current request's query parameters and upserts them with new values.
//
// Use it like: {{QueryPlus "page" .NextPage}}
//
// Returns the query string sans the ? prefix, like "key1=value1&key2=value2"
func QueryPlus(r *http.Request) func(...interface{}) template.URL {
	return func(upsert ...interface{}) template.URL {
		// Get current parameters.
		// Note: COPY them from r.Form so we don't accidentally modify r.Form.
		var params = map[string][]string{}
		for k, v := range r.Form {
			params[k] = v
		}

		// Mix in the incoming fields.
		for i := 0; i < len(upsert); i += 2 {
			var (
				key   = fmt.Sprintf("%v", upsert[i])
				value interface{}
			)
			if len(upsert) > i {
				value = upsert[i+1]
			}

			params[key] = []string{fmt.Sprintf("%v", value)}
		}

		// Assemble and return the query string.
		var parts = []string{}
		for k, vs := range params {
			for _, v := range vs {
				parts = append(parts,
					fmt.Sprintf("%s=%s", url.QueryEscape(k), url.QueryEscape(v)),
				)
			}
		}

		// Sort them deterministically.
		sort.Strings(parts)

		return template.URL(strings.Join(parts, "&"))
	}
}

// SimplePager creates a paginator row (partial template).
//
// Use it like: {{SimplePager .Pager}}
//
// It runs the template partial 'simple_pager.html' to customize it for the site theme.
func SimplePager(r *http.Request) func(*models.Pagination) template.HTML {
	return func(pager *models.Pagination) template.HTML {
		tmpl, err := template.New("index").Funcs(template.FuncMap{
			"QueryPlus": QueryPlus(r),
		}).ParseFiles(config.TemplatePath + "/partials/simple_pager.html")
		if err != nil {
			return template.HTML(err.Error())
		}

		var (
			vars = struct {
				Pager   *models.Pagination
				Request *http.Request
			}{pager, r}

			buf = bytes.NewBuffer([]byte{})
		)

		err = tmpl.ExecuteTemplate(buf, "SimplePager", vars)
		if err != nil {
			return template.HTML(err.Error())
		}
		return template.HTML(buf.String())
	}
}
