package layouts

// |@@| C

import (
	"context"
	"gardening/src/lib/config"
	"gardening/src/lib/dev_tool"
	erro "gardening/src/lib/error"
	"gardening/src/lib/kit"
	"gardening/src/lib/message"
	"gardening/src/lib/searcher"
	"gardening/src/srv/layout_config"
	"gardening/src/srv/menus"
	"gardening/src/srv/urlers"
	"strconv"
	"strings"

	"gitlab.com/ccyrillee/kitcla/components/atoms/icons"
	"gitlab.com/ccyrillee/kitcla/components/organisms/navbars"
	"gitlab.com/ccyrillee/kitcla/goc"
)

type Layout struct {
	Kit          *kit.Kit
	AppUrler     *urlers.AppUrler
	TopMenu      *menus.TopMenu
	Config       *config.Config
	DevTool      *dev_tool.DevTool
	LayoutConfig *layout_config.LayoutConfig
}

type LayoutMod struct {
	Ctx             context.Context
	Content         goc.HTML
	Messages        []*message.Message
	Scope           []*searcher.ScopeItem
	JsScripts       []JsScript
	ImportMap       []string
	BreadcrumbItems []*navbars.BreadcrumbItem
}

func (this *LayoutMod) AddJsScript(source string) {
	this.JsScripts = append(this.JsScripts, JsScript{Source: source})
}

func (this *LayoutMod) AddJsModuleScript(source string) {
	this.JsScripts = append(this.JsScripts, JsScript{Source: source, Module: true})
}

type JsScript struct {
	Source string
	Module bool
}

func (this *Layout) H(mod *LayoutMod) goc.HTML {
	return goc.H("html", goc.Attr{"lang": "en"},
		this.head(mod),
		this.body(mod),
	)
}

func (this *Layout) body(mod *LayoutMod) goc.HTML {
	return goc.H(
		"body",
		goc.Attr{"class": "theme-light bg-gray-50"},
		this.topBar(mod),
		this.main(mod),
		this.search(mod),
	)
}

func (this *Layout) menuItemLink(url string, label string) goc.HTML {
	return goc.H("a",
		goc.Attr{"href": url, "class": ""},
		label,
	)
}

func (this *Layout) topBarLeftContent(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Ccs("div", "flex items-center",
		this.logoContainer(),
		this.navbar(mod),
	)
}

func (this *Layout) debugLink(mod *LayoutMod) goc.HTML {
	url := this.DevTool.GetUrlForLogReport(mod.Ctx)
	mod2 := this.Kit.Atoms.Buttons.Button.Mod()
	return this.Kit.Atoms.Buttons.Button.TertiaryIconLink("code", url, mod2)
}

func (this *Layout) logoutLink(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Cav("a", goc.Attr{"href": "/auth/logout"}, "Logout")
}

func (this *Layout) topBarRightContent(mod *LayoutMod) goc.HTML {
	var links []goc.HTML

	messageButton := this.messageButton(mod)
	links = append(links, messageButton)
	//notificationLink := this.Kit.Molecules.Popovers.Popover.GhostlyIconPopover("inbox", this.logoutLink(mod))
	//links = append(links, notificationLink)
	//searchLink := this.Kit.Atoms.Buttons.Button.TertiaryIconLink("magnifying-glass", "#")
	//links = append(links, searchLink)
	//eventsLink := this.Kit.Molecules.Popovers.Popover.GhostlyIconPopover("chart-column", this.logoutLink(mod))
	//links = append(links, eventsLink)
	if this.Config.IsDevOrTestEnv() {
		debugComponent := this.debugComponent(mod, this.debugLink(mod))
		links = append(links, debugComponent)
	}
	logoutLink := this.Kit.Molecules.Popovers.Popover.GhostlyIconPopover("user", this.logoutLink(mod))
	links = append(links, logoutLink)

	return this.Kit.Component.Ccs("div", "flex flex-row space-x-2 items-center", links...)
}

func (this *Layout) messageButton(mod *LayoutMod) goc.HTML {
	return this.Kit.Molecules.Popovers.Popover.GhostlyIconPopover("envelope-open", this.messageButtonBody(mod))
}

func (this *Layout) debugComponent(mod *LayoutMod, debugLink goc.HTML) goc.HTML {
	port := this.DevTool.GetBindingPort()
	// Create wrapper with debug connection functionality
	debugWrapper := this.Kit.Component.Das(goc.Attr{
		"class":   "flex flex-row space-x-1 items-center testy",
		"x-cloak": "",
		"x-data":  "debug('" + port + "')",
		"x-init":  "connect"},
		debugLink,
		this.lostConnectionIndicator(),
	)
	// Wrap in hover popover for additional options
	return this.Kit.Component.Das(
		goc.Attr{
			"class":       "relative",
			"x-data":      "{ showMenu: false }",
			"@mouseenter": "showMenu = true",
			"@mouseleave": "showMenu = false",
		},
		debugWrapper,
		this.debugHoverMenu(mod),
	)
}

func (this *Layout) topBarContent(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Ccs("div", "mx-auto container flex items-center justify-between",
		this.topBarLeftContent(mod),
		this.topBarCenterContent(mod),
		this.topBarRightContent(mod),
	)
}

func (this *Layout) topBar(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Ccs("div", "bg-scale-0 text-scale-10 shadow-sm py-2", this.topBarContent(mod))
}

func (this *Layout) LogoImg() goc.HTML {
	return this.Kit.Component.Cav("a", goc.Attr{"class": "text-xl", "href": "/app/"},
		this.LayoutConfig.GetIcon(),
	)
}

func (this *Layout) logoContainer() goc.HTML {
	return goc.H("div",
		goc.Attr{"class": "flex items-center px-4"},
		this.LogoImg(),
	)
}

func (this *Layout) main(mod *LayoutMod) goc.HTML {
	return goc.H(
		"main",
		goc.Attr{"class": "mx-auto container mb-8 mt-6"},
		mod.Content,
	)
}

func (this *Layout) iconData(mod *LayoutMod) string {
	icon := this.LayoutConfig.GetIcon()

	return "data:image/svg+xml,%3Csvg%20xmlns='" + "http://www.w3.org/2000/svg'%20viewBox='" +
		"0%200%2016%2016'%3E%3Ctext%20x='0'%20y='14'%3E" + icon + "%3C/text%3E%3C/svg%3E"
}

func (this *Layout) head(mod *LayoutMod) goc.HTML {
	var items []goc.HTML
	items = this.addMeta(mod, items)
	items = this.addCss(items)
	items = this.addJsLib(items)
	items = this.addFonts(items)

	if len(mod.ImportMap) > 0 {
		s := "{\n      \"imports\": {\n" + strings.Join(mod.ImportMap, ",\n") + "\n}\n}\n"
		items = append(items, goc.H("script", goc.Attr{"type": "importmap"}, goc.UnsafeContent(s)))
	}

	if this.Config.IsDevOrTestEnv() {
		items = append(items, this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/debug.js"}))
	}

	for _, jsScript := range mod.JsScripts {
		a := goc.Attr{"src": jsScript.Source}
		if jsScript.Module == true {
			a["type"] = "module"
		} else {
			a["type"] = "text/javascript"
		}
		items = append(items, goc.H("script", a))
	}

	return this.Kit.Component.Cs("head", items...)
}

func (this *Layout) search(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Cas("div", goc.Attr{
		"class":      "relative z-10 hidden",
		"role":       "dialog",
		"aria-modal": "true",
		"id":         "_search-search",
	},
		this.fadedBackground(),
		this.modalContainer(mod),
	)
}

func (this *Layout) fadedBackground() goc.HTML {
	return this.Kit.Component.Dc("fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity")
}

func (this *Layout) modalContainer(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Dcs("fixed inset-0 z-10 w-screen overflow-y-auto", this.modalOuter(mod))
}

func (this *Layout) modalOuter(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Dcs("flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0", this.modal(mod))
}

func (this *Layout) modal(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Dcs("relative transform overflow-hidden mx-auto container mb-8 rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg",
		this.modalContent(mod))
}

func (this *Layout) modalContent(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Cas("div", goc.Attr{
		"style":      "height: 600px;",
		"id":         "_search-search-component",
		"x-data":     "search()",
		"data-scope": this.dataScope(mod),
	},
		this.searchInput(mod),
		this.searchKinds(),
		this.results(),
	)
}

func (this *Layout) searchInput(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Ca("input", goc.Attr{
		"type":                    "text",
		"@keyup":                  "doSearch",
		"id":                      "_search-search-input",
		"class":                   "w-full focus:outline-none p-4 border-b",
		"placeholder":             "Search or type a command",
		"@keydown.escape.prevent": "close",
		"@keydown.enter.prevent":  "chooseCurrent",
		"@keydown.down.prevent":   "cursorDown",
		"@keydown.up.prevent":     "cursorUp",
		"@keydown.tab.prevent":    "nextSearchKind",
		"x-model":                 "search",
	})
}

func (this *Layout) results() goc.HTML {
	return this.Kit.Component.Tf("result in results", this.Kit.Component.W("flex flex-col space-y-4", this.result()))
}

func (this *Layout) result() goc.HTML {
	return this.Kit.Component.Cas("a", goc.Attr{
		"class":  "hover:bg-gray-100 m-1 px-3 py-1 flex flex-row space-x-1 rounded cursor-pointer",
		":class": "result.Selected ? 'bg-gray-100' : ''",
		"@click": "chooseCurrent()",
	}, this.text())
}

func (this *Layout) text() goc.HTML {
	return this.Kit.Component.Ca("div", goc.Attr{"x-html": "result.Html"})
}

func (this *Layout) searchKinds() goc.HTML {
	return this.Kit.Component.W("flex flex-row border-b space-x-1 p-2", this.Kit.Component.Tf("searchKind in searchKinds", this.searchKind()))
}

func (this *Layout) searchKind() goc.HTML {
	return this.Kit.Component.Das(
		goc.Attr{
			"class":  "flex flex-row space-x-1 rounded-full border px-2",
			":class": "searchKind.key === currentKind().key ? 'bg-gray-100' : ''",
		}, this.searchKindText())
}

func (this *Layout) searchKindText() goc.HTML {
	return this.Kit.Component.Ca("div", goc.Attr{"x-text": "searchKind.text"})
}

func (this *Layout) dataScope(mod *LayoutMod) string {
	var ss []string
	for _, item := range mod.Scope {
		s := "{\"name\": \"" + item.Name + "\", \"value\": \"" + item.Value + "\"}"
		ss = append(ss, s)
	}
	return "[" + strings.Join(ss, ",") + "]"
}

func (this *Layout) debugHoverMenu(mod *LayoutMod) goc.HTML {
	return this.Kit.Component.Das(
		goc.Attr{
			"x-show":                   "showMenu",
			"x-transition:enter":       "transition ease-out duration-200",
			"x-transition:enter-start": "opacity-0 transform scale-95",
			"x-transition:enter-end":   "opacity-100 transform scale-100",
			"x-transition:leave":       "transition ease-in duration-150",
			"x-transition:leave-start": "opacity-100 transform scale-100",
			"x-transition:leave-end":   "opacity-0 transform scale-95",
			"class":                    "absolute top-0 right-0 mt-8 bg-white border border-gray-200 rounded-lg shadow-lg z-50 min-w-48",
		},
		this.debugMenuContent(mod),
	)
}

func (this *Layout) debugMenuContent(mod *LayoutMod) goc.HTML {
	debugUrl := this.DevTool.GetUrlForLogReport(mod.Ctx)
	taskUrl := this.DevTool.GetUrlForTaskCreator(mod.Ctx)

	return this.Kit.Component.Dcs("flex flex-col p-2",
		this.debugMenuItem("Quick Debug", debugUrl, icons.IconCode),
		this.debugMenuItem("Open in IDE", debugUrl+"&open_ide=true", icons.IconFasBookOpen),
		this.debugMenuItem("Open in CoIDE", debugUrl+"&open_co_ide=true", icons.IconFasBookAtlas),
		this.debugMenuItem("Task from Here", taskUrl, icons.IconFasCircle),
	)
}

func (this *Layout) debugMenuItem(label string, href string, icon string) goc.HTML {
	return this.Kit.Component.Cas("a",
		goc.Attr{
			"class":  "flex items-center space-x-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-md cursor-pointer",
			"href":   href,
			"target": "_blank",
		},
		this.Kit.Atoms.Icons.Icon.Icon(icon),
		this.Kit.Component.Cv("span", label),
	)
}

func (this *Layout) lostConnectionIndicator() goc.HTML {
	return this.Kit.Component.Ti("showIndicator", this.Kit.Component.Das(
		goc.Attr{"class": "relative flex h-3 w-3"},
		this.Kit.Component.Dc("animate-ping absolute inline-flex h-full w-full rounded-full bg-gray-400 opacity-75"),
		this.Kit.Component.Dc("relative inline-flex rounded-full h-3 w-3 bg-gray-500"),
	),
	)
}

func (this *Layout) addMeta(mod *LayoutMod, items []goc.HTML) []goc.HTML {
	metaItems := []goc.HTML{
		this.Kit.Component.Ca("meta", goc.Attr{"charset": "UTF-8"}),
		this.Kit.Component.Ca("meta", goc.Attr{"http-equiv": "X-UA-Compatible", "content": "ie-edge"}),
		this.Kit.Component.Cv("title", this.LayoutConfig.GetWebsiteTitle()),
		this.Kit.Component.Ca("link", goc.Attr{"rel": "icon", "href": this.iconData(mod), "type": "image/svg+xml"}),
	}

	if this.Config.IsDevEnv() && mod.Ctx != nil {
		record := this.DevTool.GetCurrentRecord(mod.Ctx)
		if record != nil && record.Uid != "" {
			metaItems = append(metaItems,
				this.Kit.Component.Ca("meta", goc.Attr{"name": "devt-parent-uid", "content": record.Uid}),
			)
		}
	}

	return append(items, metaItems...)
}

func (this *Layout) addCss(items []goc.HTML) []goc.HTML {
	if this.Config.IsDevOrTestEnv() {
		items = append(items,
			this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/libs/tailwind_play_3_4_4.js?disableWarnings=true"}),
			this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/js/tailwind_config.js"}))
	} else {
		items = append(items, this.Kit.Component.Ca("link", goc.Attr{"href": "/assets-kit/css/output.css", "rel": "stylesheet"}))
	}
	return append(items,
		this.Kit.Component.Ca("link", goc.Attr{"href": "/assets-kit/css/ds.css", "rel": "stylesheet"}),
		this.Kit.Component.Ca("link", goc.Attr{"href": "/assets-kit/css/fonts.css", "rel": "stylesheet"}),
	)
}

func (this *Layout) addJsLib(items []goc.HTML) []goc.HTML {
	jsItems := []goc.HTML{
		this.Kit.Component.Ca("script", goc.Attr{"defer": "", "src": "/assets-kit/libs/alpine.3.8.1.min.js"}),
		this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/components/organisms/tables/table.js"}),
		this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/components/organisms/search/search.js"}),
		this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/components/organisms/forms/form.js"}),
		this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/common.js"}),
	}

	// Add devt interceptor for development environments
	if this.Config.IsDevEnv() {
		// Insert interceptor early so it's loaded before other scripts
		jsItems = append([]goc.HTML{
			this.Kit.Component.Ca("script", goc.Attr{"src": "/assets-kit/devt-interceptor.js"}),
		}, jsItems...)
	}

	return append(items, jsItems...)
}

func (this *Layout) addFonts(items []goc.HTML) []goc.HTML {
	return append(items,
		this.Kit.Component.Ca("link", goc.Attr{"rel": "preconnect", "href": "https://fonts.googleapis.com"}),
		this.Kit.Component.Ca("link", goc.Attr{"rel": "preconnect", "href": "https://fonts.gstatic.com", "crossorigin": ""}),
		this.Kit.Component.Ca("link", goc.Attr{"rel": "stylesheet", "href": "https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"}),
	)
}

func (this *Layout) topMessage(mod *LayoutMod) *message.Message {
	messages := mod.Messages
	if len(messages) == 0 {
		return nil
	}
	for _, msg := range messages {
		if msg.Kind == "failure" {
			return msg
		}
	}
	return messages[0]
}

func (this *Layout) topBarCenterContent(mod *LayoutMod) goc.HTML {
	topMessage := this.topMessage(mod)
	if topMessage == nil {
		return goc.HTML{}
	}

	iconColor := ""
	borderColor := ""
	icon := ""
	if topMessage.Kind == "success" {
		icon = "circle-check"
		iconColor = "text-teal-500"
		borderColor = "border-teal-500"
	}
	if topMessage.Kind == "failure" {
		icon = "circle-xmark"
		iconColor = "text-red-500"
		borderColor = "border-red-500"
	}

	if icon == "" {
		panic(erro.N("Cannot render message icon").KV("kind", topMessage.Kind))
	}

	var set []goc.HTML
	set = append(set, this.Kit.Component.Dcs("shrink-0 "+iconColor, this.Kit.Atoms.Icons.Icon.Icon(icon)))
	set = append(set, this.Kit.Component.Dcv("ms-3 text-sm text-gray-700", topMessage.Title))

	if len(mod.Messages) > 1 {
		count := strconv.Itoa(len(mod.Messages) - 1)
		set = append(set, this.Kit.Component.Dcv("inline-flex items-center rounded-full text-bold bg-gray-200 px-3 py-0.5 text-sm text-gray-700 ms-3", " + "+count))
	}

	return this.Kit.Component.Das(
		goc.Attr{
			"class":                       "flex flex-row items-center border border border-gray-200 rounded-lg px-3 py-2 " + borderColor,
			"x-data":                      "{ show: true }",
			"x-init":                      "setTimeout(() => show = false, 5000)",
			"x-show":                      "show",
			"x-transition.duration.500ms": "",
		},
		set...,
	)
}

func (this *Layout) messageButtonBody(mod *LayoutMod) goc.HTML {
	messages := mod.Messages
	if len(messages) == 0 {
		return this.Kit.Component.Dcv("w-72", "No message")
	}
	var set []goc.HTML
	for _, message := range messages {
		set = append(set, this.renderMessage(mod, message))
	}
	return this.Kit.Component.Dcs("flex flex-col space-y-2 w-72", set...)
}

func (this *Layout) renderMessage(mod *LayoutMod, message *message.Message) goc.HTML {
	if message.Kind == "success" {
		return this.messageElement(message, "circle-check", "text-teal-500", "border-teal-500")
	}
	if message.Kind == "failure" {
		return this.messageElement(message, "circle-xmark", "text-red-500", "border-red-500")
	}
	return this.messageElement(message, "", "text-blue", "border-blue")
}

func (this *Layout) messageElement(message *message.Message, icon string, iconColor string, borderColor string) goc.HTML {
	return this.Kit.Component.Dcs("flex max-w-xs px-3 py-2",
		this.Kit.Component.Dcs("shrink-0 "+iconColor, this.Kit.Atoms.Icons.Icon.Icon(icon)),
		this.Kit.Component.Dcv("ms-3 text-sm text-gray-700", message.Title),
	)
}

func (this *Layout) navbar(mod *LayoutMod) goc.HTML {
	mod2 := this.Kit.Organisms.Navbars.DualNavbar.Mod()
	mod2.MenuItems = this.TopMenu.GetMenuItems()
	homeItem := &navbars.BreadcrumbItem{Label: "Home", Link: "/"}
	mod2.BreadcrumbItems = append([]*navbars.BreadcrumbItem{homeItem}, mod.BreadcrumbItems...)

	return this.Kit.Organisms.Navbars.DualNavbar.DualNavbar(mod2)
}
