package templateutil

import (
	"fmt"
	"html/template"
	"io/fs"

	"source.toby3d.me/toby3d/home/internal/site"
	"source.toby3d.me/toby3d/home/internal/templateutil/collections"
	"source.toby3d.me/toby3d/home/internal/templateutil/partials"
	"source.toby3d.me/toby3d/home/internal/templateutil/safe"
	"source.toby3d.me/toby3d/home/internal/templateutil/strings"
	"source.toby3d.me/toby3d/home/internal/templateutil/transform"
	"source.toby3d.me/toby3d/home/internal/templateutil/urls"
)

type Function struct {
	Handler func(v ...any) any
	Methods template.FuncMap
	Name    string
}

func New(dir fs.FS, siter site.UseCase) (template.FuncMap, error) {
	partialDir, err := fs.Sub(dir, "partials")
	if err != nil {
		return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err)
	}

	funcMap := make(template.FuncMap)
	funcs := make([]Function, 0)
	stringsNamespace := strings.New()
	funcs = append(funcs, Function{
		Name:    "strings",
		Handler: func(v ...any) any { return stringsNamespace },
		Methods: template.FuncMap{
			"hasPrefix": stringsNamespace.HasPrefix,
			"hasSuffix": stringsNamespace.HasSuffix,
		},
	})
	safeNamespace := safe.New()
	funcs = append(funcs, Function{
		Name:    "safe",
		Handler: func(v ...any) any { return safeNamespace },
		Methods: template.FuncMap{
			"safeHTML": safeNamespace.HTML,
		},
	})
	partialsNamespace := partials.New(partialDir, funcMap)
	funcs = append(funcs, Function{
		Name:    "partials",
		Handler: func(v ...any) any { return partialsNamespace },
		Methods: template.FuncMap{
			"partial": partialsNamespace.Include,
		},
	})
	urlsNamespace := urls.New(siter)
	funcs = append(funcs, Function{
		Name:    "urls",
		Handler: func(v ...any) any { return urlsNamespace },
		Methods: template.FuncMap{
			"absURL": urlsNamespace.AbsURL,
			"relURL": urlsNamespace.RelURL,
		},
	})
	transformNamespace := transform.New()
	funcs = append(funcs, Function{
		Name:    "transform",
		Handler: func(v ...any) any { return transformNamespace },
		Methods: template.FuncMap{
			"markdownify": transformNamespace.Markdownify,
		},
	})
	collectionsNamespace := collections.New()
	funcs = append(funcs, Function{
		Name:    "collections",
		Handler: func(v ...any) any { return collectionsNamespace },
		Methods: template.FuncMap{
			"slice": collectionsNamespace.Slice,
			"seq":   collectionsNamespace.Seq,
			"index": collectionsNamespace.Index,
		},
	})

	for _, f := range funcs {
		funcMap[f.Name] = f.Handler

		for name, m := range f.Methods {
			funcMap[name] = m
		}
	}

	return funcMap, nil
}
