package web

import (
	"bytes"
	"embed"
	"html/template"
	"net/http"
	"time"

	"github.com/sirupsen/logrus"
	"sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
)

// tmplFS holds the page templates. Each page is parsed together with the shared
// layout into its own template set so that per-page "content"/"scripts" defines
// do not collide across pages.
//
//go:embed templates/*.html
var tmplFS embed.FS

// staticFS holds the built front-end assets (the hashed bundle.<hash>.js, the
// hashed stylesheet, logo.svg). It is served read-only under /static/.
//
//go:embed static
var staticFS embed.FS

// funcMap holds the template helpers shared by every page.
//
// It starts from chrome.Funcs — so the shared partials find the helpers they
// were written against, and so "shortsha" is the instance's one abbreviation
// rule rather than this service's copy of it — and adds compare's own on top.
// Adding after is deliberate: a name may then be shadowed on purpose rather than
// by accident of map ordering.
var funcMap = func() template.FuncMap {
	m := chrome.Funcs()

	// date formats a commit timestamp for display.
	m["date"] = func(t time.Time) string {
		return t.UTC().Format("2006-01-02 15:04 MST")
	}

	// statusClass maps a git file-change status letter to a CSS modifier used by
	// the .diff-status badge (see the diff-status rules in layout.html). Anything
	// unrecognized falls back to the neutral "o".
	m["statusClass"] = func(s string) string {
		if s == "" {
			return "o"
		}
		switch s[0] {
		case 'A', 'a':
			return "a"
		case 'M', 'm':
			return "m"
		case 'D', 'd':
			return "d"
		case 'R', 'r':
			return "r"
		default:
			return "o"
		}
	}

	// statusLabel spells out a status letter for the badge's tooltip.
	m["statusLabel"] = func(s string) string {
		if s == "" {
			return "changed"
		}
		switch s[0] {
		case 'A', 'a':
			return "added"
		case 'M', 'm':
			return "modified"
		case 'D', 'd':
			return "deleted"
		case 'R', 'r':
			return "renamed"
		default:
			return "changed"
		}
	}

	return m
}()

// pageNames are the content templates; each is parsed with layout.html.
var pageNames = []string{"index", "repo", "compare", "commit", "error"}

// pages maps a page name to its parsed template set: the shared chrome partials
// of sr-ht-ecore, the layout, and that one page. The partials are attached to
// every set rather than to a shared one, for the same reason the layout is —
// each page defines its own "content", and one set would let the last parsed win.
var pages = func() map[string]*template.Template {
	m := make(map[string]*template.Template, len(pageNames))
	for _, name := range pageNames {
		t := chrome.MustAttach(template.New("layout.html").Funcs(funcMap))
		t = template.Must(t.ParseFS(tmplFS, "templates/layout.html", "templates/"+name+".html"))
		m[name] = t
	}
	return m
}()

// render executes a page into a buffer first, so a template error yields a clean
// 500 rather than a half-written response. On success it writes the status and
// the buffered HTML.
func (s *Server) render(w http.ResponseWriter, status int, page string, vd viewData) {
	t, ok := pages[page]
	if !ok {
		logrus.WithField("page", page).Error("web: unknown template page")
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	var buf bytes.Buffer
	if err := t.ExecuteTemplate(&buf, "layout.html", vd); err != nil {
		logrus.WithError(err).WithField("page", page).Error("web: template execution failed")
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(status)
	_, _ = buf.WriteTo(w)
}

// errorData is the payload of the error page.
type errorData struct {
	Status     int
	StatusText string
	Message    string
}

// renderError renders the chrome-wrapped error page. It never recurses into
// render on failure (render falls back to http.Error itself).
func (s *Server) renderError(w http.ResponseWriter, r *http.Request, status int, message string) {
	vd := s.view(r, http.StatusText(status))
	vd.Data = errorData{
		Status:     status,
		StatusText: http.StatusText(status),
		Message:    message,
	}
	s.render(w, status, "error", vd)
}
