package app

import (
	"strings"

	. "kjol/vdom"
	ui "kjol/webui"
)

// What kjøl is made of, as data.
//
// There are two kinds of thing here, and conflating them was the mistake this file used
// to make — one flat list called "the layers", holding both.
//
//	LAYERS       are LANGUAGES. What kjøl is written in, and what it gives you in each:
//	             the Go base, the TypeScript kit, the C base, the Jai modules. A layer is
//	             a directory of code you can use on its own.
//
//	COMPOSITIONS are FRAMEWORKS. What you get when the layers are assembled into
//	             something that does a job — the two web engines. A composition is not
//	             another language; it is a use of them.
//
// Kjøl Wasm Web is Go, all the way down. Kjøl JS Web is TypeScript compiled by a Go
// toolchain — two layers, one framework. Listing that beside "C" as though they were the
// same kind of noun told the reader nothing about either.
//
// This is the Go mirror of frontend/src/layers.ts. The site has two front-ends built by
// two completely different pipelines, and these menus have to be identical in both — so
// each is a LIST, not markup, and the two lists are the only thing that has to be kept in
// step.
//
// (A shared source would be better than a mirrored one. There isn't one: this side
// compiles to WebAssembly and the other is bundled by esbuild, and nothing is upstream of
// both. Keeping each to a flat slice of plain data is what makes the duplication
// survivable — you can diff them by eye.)

type Layer struct {
	Name    string
	Href    string
	Tagline string
	// Sub is the half-line beside the wordmark while you are inside this layer. It says
	// what you are standing in — "Go + WebAssembly", "arenas, strings, a lexer" — and a
	// wordmark that says the same thing everywhere is one more thing the reader has to
	// keep track of himself.
	Sub string
	// Live means you can click into worked examples. The others are documented but
	// have no demo — they still appear, because a menu that silently omits half the
	// library teaches the reader that the library is half the size it is.
	Live bool
	Icon string
}

// Wordmark is what the CHROME calls this layer — the top bar, and the page's own title.
// The menu calls it Name.
//
// They differ, and only for the languages. In a menu headed "Layers" the row says "C",
// because the row is answering "which language"; up in the top bar, alone, "C" is the name
// of a language rather than the name of the thing you are reading, and it has to say whose
// C this is. The compositions are already named "Kjøl Wasm Web" — the product's name is
// part of what they ARE, not a prefix bolted on — so they are returned unchanged.
func (l Layer) Wordmark() string {
	if strings.HasPrefix(l.Name, "Kjøl") {
		return l.Name
	}
	return "Kjøl " + l.Name
}

// Languages: what kjøl is written in.
func Languages() []Layer {
	return []Layer{
		{
			Name:    "Go",
			Href:    "/go",
			Tagline: "The base: config, database, logging, HTTP, mail, validation — and both web engines.",
			Sub:     "the base layer",
			Live:    true,
			Icon:    "server",
		},
		{
			Name:    "TypeScript",
			Href:    "/ts",
			Tagline: "The Solid component kit, the vendored runtime, and the generic scaffolding apps import as @kjol/*.",
			Icon:    "squares",
		},
		{
			Name:    "C",
			Href:    "/c",
			Tagline: "Arena allocator, counted strings, math, a lexer, a platform layer — and a build system that is a C file.",
			Sub:     "a base layer in C",
			Live:    true,
			Icon:    "bolt",
		},
		{
			Name:    "Jai",
			Href:    "/jai",
			Tagline: "Console rendering. Early.",
			Icon:    "cube",
		},
	}
}

// Compositions: what the languages are assembled into.
func Compositions() []Layer {
	return []Layer{
		{
			Name:    "Kjøl Wasm Web",
			Href:    "/wasm",
			Tagline: "Web interfaces written in Go, compiled to WebAssembly. SSR + hydration, and no JavaScript build at all.",
			Sub:     "Go + WebAssembly",
			Live:    true,
			Icon:    "code",
		},
		{
			Name:    "Kjøl JS Web",
			Href:    "/js",
			Tagline: "The Solid component kit, bundled by a Go toolchain: TSX → Solid → esbuild, Tailwind in Go.",
			Sub:     "Solid + Go toolchain",
			Live:    true,
			Icon:    "table",
		},
	}
}

// CurrentLayer is the layer or composition the given path belongs to, or nil on the front
// page. The wordmark uses it to name where you are standing.
func CurrentLayer(path string) *Layer {
	all := append(Compositions(), Languages()...)
	for i, l := range all {
		if path == l.Href || strings.HasPrefix(path, l.Href+"/") {
			return &all[i]
		}
	}
	return nil
}

// The two menus' controllers.
//
// They are created ONCE, here, at package level — not inside the functions below, which a
// layout calls on every single render. A floating component is a controller: it owns an
// open signal, a positioning engine and document listeners, and building a fresh one per
// render would leak all three and give you a menu that never opens. Same rule as Theme, a
// few lines up in pages.go.
//
// TWO menus, not one with two headings inside it. They are different questions — "what is
// this written in" and "what can I read" — and a reader who wants the second should not
// have to scroll past the first to find it. The kit's single-open manager means opening
// one closes the other, so they behave like one control with two halves.
var (
	LayersMenuCtl       = ui.NewMenu(ui.MenuOptions{Placement: ui.PlacementBottomEnd})
	CompositionsMenuCtl = ui.NewMenu(ui.MenuOptions{Placement: ui.PlacementBottomEnd})
)

// layersMenu lists the LANGUAGES. compositionsMenu, below, lists the frameworks.
//
// An entry that is Live is a link. One that is not is inert and dimmed, with the word
// "reference" on it — it exists, it is documented in the repository, there is simply
// nothing here to click.
//
// Crossing into another layer is a REAL navigation, not a client-side route: /js is a
// different binary's SPA and /wasm is this one. Hence a plain href and no navigate()
// interception — an intercepted click would ask this WebAssembly to render a page it
// does not have.
func layersMenu(d Deps) *VNode {
	return dropdown(d, LayersMenuCtl, "Layers", Languages())
}

// compositionsMenu lists the FRAMEWORKS — the two things assembled out of the layers, and
// the two a reader can actually click into.
func compositionsMenu(d Deps) *VNode {
	return dropdown(d, CompositionsMenuCtl, "Compositions", Compositions())
}

func dropdown(d Deps, ctl *ui.Menu, label string, rows []Layer) *VNode {
	content := make([]*VNode, 0, len(rows))
	for _, l := range rows {
		content = append(content, layerItem(d, l))
	}

	return Div(Attr("class", "relative"),
		ctl.Trigger(ui.MenuTriggerProps{
			Class: "inline-flex items-center gap-1.5 rounded-default px-3 py-1.5 text-sm font-medium text-ink-soft hover:bg-surface-raised hover:text-ink",
		},
			Text(label),
			ui.IconInline("chevron-down", 11, "text-ink-faint"),
		),
		ctl.Content("w-96", content...),
	)
}

// layerGrid is the front page's list — the same data as the menu, laid out to be read
// rather than navigated. A layer with no examples still gets a row: the point of the page
// is what kjøl IS, and half of it having no demo yet does not make that half not exist.
func layerGrid(rows []Layer) *VNode {
	mods := []Mod{Attr("class", "mt-5 divide-y divide-line rounded-default border border-line")}
	for _, l := range rows {
		mods = append(mods, layerRow(l))
	}
	return Div(mods...)
}

// No icon beside the name, and none on the "Read the docs" link. The front page reads as a
// short list of what kjøl is, and a glyph next to every row — a boat, a table, a globe —
// asks to be decoded before the word beside it is read. The words are the point; they carry
// themselves. (The reference badge stays: it says something the name does not.)
func layerRow(l Layer) *VNode {
	head := Span(Attr("class", "flex items-center gap-2"),
		Span(Attr("class", "font-medium text-ink"), Text(l.Name)),
		iff2(l.Live,
			func() *VNode { return nil },
			func() *VNode {
				return Span(Attr("class", "rounded-full border border-line px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-ink-faint"),
					Text("reference"))
			}),
	)
	body := P(Attr("class", "mt-1 text-sm leading-relaxed text-ink-muted"), Text(l.Tagline))

	if !l.Live {
		return Div(Attr("class", "px-5 py-4 opacity-75"), head, body)
	}
	// A real navigation: the next layer is a different binary.
	return A(Attr("class", "block px-5 py-4 no-underline hover:bg-surface-muted"), Attr("href", l.Href),
		head, body,
		Span(Attr("class", "mt-2 inline-block text-sm font-medium text-accent"),
			Text("Read the docs")),
	)
}

// iff2 picks a node. Go has no ternary, and a four-line if statement inside a tree literal
// breaks the shape of the markup worse than this does.
func iff2(cond bool, a, b func() *VNode) *VNode {
	if cond {
		return a()
	}
	return b()
}

// No icon on the menu rows either — the name and its one-line tagline are the whole item,
// same as the front-page list and the sidebar. (The chevron on the menu TRIGGER stays: it
// is not a layer's glyph, it is the cue that the thing opens.)
func layerItem(d Deps, l Layer) *VNode {
	active := CurrentLayer(d.Path()) != nil && CurrentLayer(d.Path()).Href == l.Href

	if !l.Live {
		return Div(Attr("class", "flex cursor-default flex-col gap-0.5 px-3 py-2 opacity-55"),
			Span(Attr("class", "flex items-center gap-2 text-sm font-medium text-ink-muted"),
				Text(l.Name),
				Span(Attr("class", "rounded-full bg-surface-raised px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-ink-muted"),
					Text("reference")),
			),
			Span(Attr("class", "text-xs text-ink-muted"), Text(l.Tagline)),
		)
	}

	cls := "flex flex-col gap-0.5 px-3 py-2 no-underline hover:bg-surface-raised"
	if active {
		cls += " bg-primary-subtle"
	}
	return A(Attr("class", cls), Attr("href", l.Href),
		Span(Attr("class", "text-sm font-medium text-ink"), Text(l.Name)),
		Span(Attr("class", "text-xs text-ink-muted"), Text(l.Tagline)),
	)
}
