package handler

import (
	"html"
	"net/http"
	"strings"

	"codeberg.org/VARASYS/ZDDC/zddc/internal/apps"
)

// AppsVirtualPrefix is a virtual, public directory that serves the standalone
// tool HTMLs so anyone can grab one and run it against their OWN local
// filesystem — download it, open it from disk, and it runs offline via the
// browser's File System Access picker. It carries no project data, so it's
// served with no auth/ACL. `_`-prefixed names are system-reserved (mkdir
// rejects them), so /_apps/ never collides with content, and it's virtual —
// nothing exists on disk.
const AppsVirtualPrefix = "/_apps/"

type standaloneApp struct {
	File string // URL + filename, e.g. "classifier.html"
	Name string
	Desc string
}

// The order here is the order shown on the index; "local-first" tools lead.
var standaloneApps = []standaloneApp{
	{"classifier.html", "Classifier", "Rename a local folder of files to ZDDC naming conventions."},
	{"browse.html", "Browse", "Browse + edit a local directory tree — markdown, YAML, CSV, and more."},
	{"transmittal.html", "Transmittal", "Assemble a transmittal package from local files."},
	{"tables.html", "Tables", "View and edit a directory of YAML rows as a sortable table."},
	{"form.html", "Form", "Edit a single YAML record with a schema-driven form."},
	{"archive.html", "Archive", "Review an archive of ZDDC-named files."},
}

// appBytesForFile returns the HTML for a /_apps/<file> request, or nil for an
// unknown file. It prefers the site .zddc.zip bundle member (operator
// override / the freshest dev build) and falls back to the binary's embedded
// copy. tables + form share the one embedded tables/form bundle.
func appBytesForFile(appsSrv *apps.Server, file string) []byte {
	if appsSrv != nil && appsSrv.Bundle != nil {
		if b, ok := appsSrv.Bundle.Member(file); ok && len(b) > 0 {
			return b
		}
	}
	switch file {
	case "classifier.html":
		return apps.EmbeddedBytes("classifier")
	case "browse.html":
		return apps.EmbeddedBytes("browse")
	case "transmittal.html":
		return apps.EmbeddedBytes("transmittal")
	case "archive.html":
		return apps.EmbeddedBytes("archive")
	case "tables.html", "form.html":
		return EmbeddedTablesHTML()
	}
	return nil
}

// ServeApps handles GET/HEAD under /_apps/. "/_apps/" → an index of the
// standalone tools (Download / Open links); "/_apps/<tool>.html" → that tool's
// embedded HTML. Append ?download to force a save dialog. No auth — tool UI
// only, no data.
func ServeApps(appsSrv *apps.Server, w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodHead {
		w.Header().Set("Allow", "GET, HEAD")
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return
	}
	rest := strings.TrimPrefix(r.URL.Path, AppsVirtualPrefix)
	if rest == "" || rest == "index" {
		serveAppsIndex(w, r)
		return
	}
	if strings.Contains(rest, "/") {
		http.NotFound(w, r)
		return
	}
	body := appBytesForFile(appsSrv, rest)
	if body == nil {
		http.NotFound(w, r)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	// Conditional-GET-friendly: revalidate each load; bytes change only on a
	// binary redeploy. (Index is no-store; it's tiny + generated.)
	w.Header().Set("Cache-Control", "max-age=0, must-revalidate")
	if r.URL.Query().Get("download") != "" {
		w.Header().Set("Content-Disposition", `attachment; filename="`+rest+`"`)
	}
	if r.Method == http.MethodHead {
		return
	}
	_, _ = w.Write(body)
}

func serveAppsIndex(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-store")
	if r.Method == http.MethodHead {
		return
	}
	var b strings.Builder
	b.WriteString(`<!doctype html><html lang="en"><head><meta charset="utf-8">`)
	b.WriteString(`<meta name="viewport" content="width=device-width, initial-scale=1">`)
	b.WriteString(`<title>ZDDC standalone apps</title><style>`)
	b.WriteString(`body{font-family:system-ui,-apple-system,sans-serif;max-width:48rem;margin:2rem auto;padding:0 1rem;line-height:1.5;color:#222}`)
	b.WriteString(`h1{font-size:1.4rem;margin:0 0 .3rem}.lead{color:#555;font-size:.92rem;margin:0 0 1.2rem}`)
	b.WriteString(`.app{border:1px solid #e2e2e2;border-radius:8px;padding:.7rem 1rem;margin:.55rem 0}`)
	b.WriteString(`.app h2{font-size:1.05rem;margin:0 0 .15rem}.app p{margin:.15rem 0 .55rem;color:#555;font-size:.88rem}`)
	b.WriteString(`a.btn{display:inline-block;padding:.28rem .7rem;border:1px solid #2868c8;border-radius:5px;color:#2868c8;text-decoration:none;margin-right:.4rem;font-size:.85rem}`)
	b.WriteString(`a.btn:hover{background:#2868c8;color:#fff}`)
	b.WriteString(`</style></head><body>`)
	b.WriteString(`<h1>ZDDC standalone apps</h1>`)
	b.WriteString(`<p class="lead">Each tool is a single, self-contained HTML file. <strong>Download</strong> one and open it from your disk to run it offline against a folder on your own machine (use a Chromium-family browser — it needs the File System Access API). <strong>Open</strong> runs it here, against the server.</p>`)
	for _, a := range standaloneApps {
		b.WriteString(`<div class="app"><h2>` + html.EscapeString(a.Name) + `</h2>`)
		b.WriteString(`<p>` + html.EscapeString(a.Desc) + `</p>`)
		b.WriteString(`<a class="btn" href="` + AppsVirtualPrefix + a.File + `?download=1" download="` + a.File + `">Download</a>`)
		b.WriteString(`<a class="btn" href="` + AppsVirtualPrefix + a.File + `" target="_blank" rel="noopener">Open</a>`)
		b.WriteString(`</div>`)
	}
	b.WriteString(`</body></html>`)
	_, _ = w.Write([]byte(b.String()))
}
