package HTMLTemplate

import (
	// External
	werr "git.gibonuddevalla.se/go/wrappederror"

	// Standard
	"fmt"
	"html/template"
	"io/fs"
	"net/http"
	"os"
	"regexp"
	"sync"
)

type Engine struct {
	parsedTemplates    map[string]*template.Template
	viewFS             fs.FS
	staticEmbeddedFS   http.Handler
	staticLocalFS      http.Handler
	componentFilenames []string
	DevMode            bool
}

var (
	templateLock sync.Mutex
)

func NewEngine(viewFS, staticFS fs.FS, devmode bool) (e Engine, err error) { // {{{
	e.parsedTemplates = make(map[string]*template.Template)
	e.viewFS = viewFS
	e.DevMode = devmode

	e.componentFilenames, err = e.getComponentFilenames()

	// Set up fileservers for static resources.
	// The embedded FS is using the embedded files intented for production use.
	// The local FS is for development of Javascript to avoid server rebuild (devmode).
	var staticSubFS fs.FS
	staticSubFS, err = fs.Sub(staticFS, "static")
	if err != nil {
		return
	}
	e.staticEmbeddedFS = http.FileServer(http.FS(staticSubFS))
	e.staticLocalFS = http.FileServer(http.Dir("static"))

	return
} // }}}

func (e *Engine) getComponentFilenames() (files []string, err error) { // {{{
	files = []string{}
	if err := fs.WalkDir(e.viewFS, "views/components", func(path string, d fs.DirEntry, err error) error {
		if d == nil {
			return nil
		}
		if d.IsDir() {
			return nil
		}
		files = append(files, path)
		return nil
	}); err != nil {
		return nil, err
	}

	return files, nil
} // }}}

func (e *Engine) ReloadTemplates() { // {{{
	templateLock.Lock()
	e.parsedTemplates = make(map[string]*template.Template)
	templateLock.Unlock()
} // }}}

func (e *Engine) StaticResource(w http.ResponseWriter, r *http.Request) { // {{{
	// URLs with pattern /(css|images)/v1.0.0/foobar are stripped of the version.
	// To get rid of problems with cached content in browser on a new version release,
	// while also not disabling cache altogether.
	if r.URL.Path == "/favicon.ico" {
		e.staticEmbeddedFS.ServeHTTP(w, r)
		return
	}

	rxp := regexp.MustCompile("^/(css|images|js|fonts)/v[0-9]+/(.*)$")
	if comp := rxp.FindStringSubmatch(r.URL.Path); comp != nil {
		w.Header().Add("Pragma", "public")
		w.Header().Add("Cache-Control", "max-age=604800")

		r.URL.Path = fmt.Sprintf("/%s/%s", comp[1], comp[2])
		if e.DevMode {
			e.staticLocalFS.ServeHTTP(w, r)
			return
		}
	}

	e.staticEmbeddedFS.ServeHTTP(w, r)
} // }}}
func (e *Engine) getPage(layout, page string) (tmpl *template.Template, err error) { // {{{
	layoutFilename := fmt.Sprintf("views/layouts/%s.gotmpl", layout)
	pageFilename := fmt.Sprintf("views/pages/%s.gotmpl", page)

	if tmpl, found := e.parsedTemplates[page]; found {
		return tmpl, nil
	}

	funcMap := template.FuncMap{
		/*
			"format_time": func(t time.Time) template.HTML {
				return template.HTML(
					t.In(smonConfig.Timezone()).Format(`<span class="date">2006-01-02</span> <span class="time">15:04:05<span class="seconds">:05</span></span>`),
				)
			},
		*/
	}

	filenames := []string{layoutFilename, pageFilename}
	filenames = append(filenames, e.componentFilenames...)

	if e.DevMode {
		tmpl, err = template.New(layout+".gotmpl").Funcs(funcMap).ParseFS(os.DirFS("."), filenames...)
	} else {
		tmpl, err = template.New(layout+".gotmpl").Funcs(funcMap).ParseFS(e.viewFS, filenames...)
	}
	if err != nil {
		err = werr.Wrap(err).Log()
		return
	}

	templateLock.Lock()
	e.parsedTemplates[page] = tmpl
	templateLock.Unlock()
	return
} // }}}
func (e *Engine) Render(p Page, w http.ResponseWriter, r *http.Request) (err error) { // {{{
	if e.DevMode {
		e.ReloadTemplates()
	}

	var tmpl *template.Template
	tmpl, err = e.getPage(p.GetLayout(), p.GetPage())
	if err != nil {
		err = werr.Wrap(err)
		return
	}

	data := map[string]any{
		"VERSION": p.GetVersion(),
		"LAYOUT":  p.GetLayout(),
		"PAGE":    p.GetPage(),
		"ERROR":   r.URL.Query().Get("_err"),
		"Data":    p.GetData(),
	}

	err = tmpl.Execute(w, data)
	if err != nil {
		err = werr.Wrap(err)
	}
	return
} // }}}

// vim: foldmethod=marker
