package html

import (
	"embed"
	"html/template"
	"io/fs"
	"net/http"
	"path/filepath"
	"strings"
	"sync"

	"go.fifitido.net/ytdl-web/pkg/view"
)

type Engine struct {
	fs   embed.FS
	tpl  *template.Template
	opts *Options

	loadOnce sync.Once
	mu       sync.Mutex
}

var _ view.Engine = (*Engine)(nil)

func New(fs embed.FS, options ...*Options) *Engine {
	var opts *Options
	if len(options) > 0 && options[0] != nil {
		opts = options[0]
	} else {
		opts = DefaultOptions()
	}

	tpl := template.New("/")
	tpl.Delims(opts.LeftDelim, opts.RightDelim)
	tpl.Funcs(opts.FuncMap)

	return &Engine{
		fs:   fs,
		tpl:  tpl,
		opts: opts,
	}
}

func (e *Engine) Load() error {
	var err error
	e.loadOnce.Do(func() {
		err = e.doLoad()
	})
	return err
}

func (e *Engine) doLoad() error {
	e.mu.Lock()
	defer e.mu.Unlock()

	return fs.WalkDir(e.fs, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if d == nil || d.IsDir() {
			return nil
		}

		if len(path) < len(e.opts.Extension) || path[len(path)-len(e.opts.Extension):] != e.opts.Extension {
			return nil
		}

		rel, err := filepath.Rel(e.opts.BaseDir, path)
		if err != nil {
			return err
		}

		name := filepath.ToSlash(rel)
		name = strings.TrimSuffix(name, e.opts.Extension)

		buf, err := e.fs.ReadFile(path)
		if err != nil {
			return err
		}

		_, err = e.tpl.New(name).Parse(string(buf))
		if err != nil {
			return err
		}

		return nil
	})
}

func (e *Engine) Render(w http.ResponseWriter, view string, data view.Data, layout ...string) {
	tmpl := e.tpl.Lookup(view)
	if tmpl == nil {
		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	if len(layout) > 0 && layout[0] != "" {
		lay := e.tpl.Lookup(layout[0])
		if lay == nil {
			http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			return
		}

		e.mu.Lock()
		defer e.mu.Unlock()

		lay.Funcs(map[string]interface{}{
			"yield": func() error {
				return tmpl.Execute(w, data)
			},
		})

		if err := lay.Execute(w, data); err != nil {
			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return
	}

	if err := tmpl.Execute(w, data); err != nil {
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}
}
