package webui

import (
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"testing"

	"kjol/wasmruntime/vdom"
)

// An unregistered icon name renders as an empty box: right size, no glyph. Nothing
// errors, nothing logs — the icons just quietly aren't there. That is exactly how
// this broke, so scan our own source for every name we ask for and assert the
// registry has it.
func TestEveryIconTheKitUsesIsRegistered(t *testing.T) {
	// Icon("name", …), IconInline("name", …), and the Icon: "name" struct field.
	calls := regexp.MustCompile(`\bIcon(?:Inline|Success|Error)?\("([a-z0-9-]+)"`)
	fields := regexp.MustCompile(`\bIcon:\s*"([a-z0-9-]+)"`)

	files, err := filepath.Glob("*.go")
	if err != nil {
		t.Fatal(err)
	}

	used := map[string][]string{} // icon name -> files that ask for it
	for _, f := range files {
		if strings.HasSuffix(f, "_test.go") || f == "icons.go" {
			continue // icons.go is the registry itself
		}
		src, err := os.ReadFile(f)
		if err != nil {
			t.Fatal(err)
		}
		for _, re := range []*regexp.Regexp{calls, fields} {
			for _, m := range re.FindAllStringSubmatch(string(src), -1) {
				if m[1] == "" {
					continue
				}
				used[m[1]] = append(used[m[1]], f)
			}
		}
	}
	// Icon names also reach Icon() through lookup tables. Those cannot be found by
	// scanning call sites, so add them from the tables themselves.
	for _, name := range toastTypeIcons {
		if name != "" {
			used[name] = append(used[name], "toast.go (toastTypeIcons)")
		}
	}
	if len(used) == 0 {
		t.Fatal("scanned no icon usages — the regexes have rotted")
	}

	var missing []string
	for name, where := range used {
		if _, ok := iconRegistry[name]; !ok {
			missing = append(missing, name+" (used in "+strings.Join(dedupe(where), ", ")+")")
		}
	}
	sort.Strings(missing)
	for _, m := range missing {
		t.Errorf("icon not registered, renders as an empty box: %s", m)
	}
}

// A registered icon must actually draw something. An entry with empty content is
// the same silent failure as a missing one.
func TestRegisteredIconsHaveContent(t *testing.T) {
	for name, def := range iconRegistry {
		if strings.TrimSpace(def.content) == "" {
			t.Errorf("icon %q is registered with no SVG content", name)
		}
		if !strings.Contains(def.content, "<path") {
			t.Errorf("icon %q has no <path>: %q", name, def.content)
		}
		if def.viewBox == "" {
			t.Errorf("icon %q has no viewBox", name)
		}
	}
}

func TestIconRendersRegisteredContent(t *testing.T) {
	html := renderNode(Icon("check", 20, "text-red-500"))
	if !strings.Contains(html, "<path") {
		t.Errorf("Icon did not render its path: %s", html)
	}
	for _, want := range []string{`width="20"`, `height="20"`, "text-red-500", `viewBox="0 0 24 24"`} {
		if !strings.Contains(html, want) {
			t.Errorf("Icon output missing %s: %s", want, html)
		}
	}
	// An unknown name still renders a correctly sized, empty box rather than
	// collapsing the layout.
	if empty := renderNode(Icon("no-such-icon", 16, "")); strings.Contains(empty, "<path") {
		t.Errorf("unknown icon drew something: %s", empty)
	}
}

// renderNode serializes a component's VNode the way the server does.
func renderNode(n *vdom.VNode) string { return vdom.RenderHTML(n) }

func dedupe(xs []string) []string {
	seen := map[string]bool{}
	out := []string{}
	for _, x := range xs {
		if !seen[x] {
			seen[x] = true
			out = append(out, x)
		}
	}
	sort.Strings(out)
	return out
}
