// Package render generates installer scripts by injecting release
// metadata into the package-install template.
//
// The template uses shell-style variable markers:
//
//	#WEBI_VERSION=        →  WEBI_VERSION='1.2.3'
//	#export WEBI_PKG_URL= →  export WEBI_PKG_URL='https://...'
//
// The package's install.sh is injected at the {{ installer }} marker.
package render

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Params holds all the values to inject into the installer template.
type Params struct {
	// Host is the base URL of the webi server (e.g. "https://webinstall.dev").
	Host string

	// Checksum is the webi.sh bootstrap script checksum (first 8 hex chars of SHA-1).
	Checksum string

	// Package name (e.g. "bat", "node").
	PkgName string

	// Tag is the version selector from the URL (e.g. "20", "stable", "").
	Tag string

	// OS, Arch, Libc are the detected platform strings.
	OS   string
	Arch string
	Libc string

	// Resolved release info.
	Version   string
	Major     string
	Minor     string
	Patch     string
	Build     string
	GitTag    string
	GitBranch string
	LTS       string // "true" or "false"
	Channel   string
	Ext       string // archive extension (e.g. "tar.gz", "zip")
	Formats   string // comma-separated format list

	// Download info.
	PkgURL  string // download URL
	PkgFile string // filename

	// Releases API URL for this request.
	ReleasesURL string

	// CSV line for WEBI_CSV.
	CSV string

	// Package catalog info.
	PkgStable  string
	PkgLatest  string
	PkgOSes    string // space-separated
	PkgArches  string // space-separated
	PkgLibcs   string // space-separated
	PkgFormats string // space-separated
}

// Bash renders a complete bash installer script by injecting params
// into the template and splicing in the package's install.sh.
func Bash(tplPath, installersDir, pkgName string, p Params) (string, error) {
	tpl, err := os.ReadFile(tplPath)
	if err != nil {
		return "", fmt.Errorf("render: read template: %w", err)
	}

	// Read the package's install.sh.
	installPath := filepath.Join(installersDir, pkgName, "install.sh")
	installSh, err := os.ReadFile(installPath)
	if err != nil {
		return "", fmt.Errorf("render: read %s/install.sh: %w", pkgName, err)
	}

	text := string(tpl)

	// Inject environment variables.
	vars := []struct {
		name  string
		value string
	}{
		{"WEBI_CHECKSUM", p.Checksum},
		{"WEBI_PKG", p.PkgName + "@" + p.Tag},
		{"WEBI_HOST", p.Host},
		{"WEBI_OS", p.OS},
		{"WEBI_ARCH", p.Arch},
		{"WEBI_LIBC", p.Libc},
		{"WEBI_TAG", p.Tag},
		{"WEBI_RELEASES", p.ReleasesURL},
		{"WEBI_CSV", p.CSV},
		{"WEBI_VERSION", p.Version},
		{"WEBI_MAJOR", p.Major},
		{"WEBI_MINOR", p.Minor},
		{"WEBI_PATCH", p.Patch},
		{"WEBI_BUILD", p.Build},
		{"WEBI_GIT_BRANCH", p.GitBranch},
		{"WEBI_GIT_TAG", p.GitTag},
		{"WEBI_LTS", p.LTS},
		{"WEBI_CHANNEL", p.Channel},
		{"WEBI_EXT", p.Ext},
		{"WEBI_FORMATS", p.Formats},
		{"WEBI_PKG_URL", p.PkgURL},
		{"WEBI_PKG_PATHNAME", p.PkgFile},
		{"WEBI_PKG_FILE", p.PkgFile},
		{"PKG_NAME", p.PkgName},
		{"PKG_STABLE", p.PkgStable},
		{"PKG_LATEST", p.PkgLatest},
		{"PKG_OSES", p.PkgOSes},
		{"PKG_ARCHES", p.PkgArches},
		{"PKG_LIBCS", p.PkgLibcs},
		{"PKG_FORMATS", p.PkgFormats},
	}

	for _, v := range vars {
		text = InjectVar(text, v.name, v.value)
	}

	// Inject the installer script at the {{ installer }} marker.
	// The marker sits inside __init_installer() at 8-space indent.
	// Production pads every line of install.sh to match, and replaces
	// the entire line (including leading whitespace).
	padded := padScript(string(installSh), "        ")
	text = replaceMarkerLine(text, "{{ installer }}", padded)

	return text, nil
}

// PowerShell renders a complete PowerShell installer script by injecting
// params into the template and splicing in the package's install.ps1.
func PowerShell(tplPath, installersDir, pkgName string, p Params) (string, error) {
	tpl, err := os.ReadFile(tplPath)
	if err != nil {
		return "", fmt.Errorf("render: read template: %w", err)
	}

	installPath := filepath.Join(installersDir, pkgName, "install.ps1")
	installPs1, err := os.ReadFile(installPath)
	if err != nil {
		return "", fmt.Errorf("render: read %s/install.ps1: %w", pkgName, err)
	}

	text := string(tpl)

	vars := []struct {
		name  string
		value string
	}{
		{"WEBI_PKG", p.PkgName + "@" + p.Tag},
		{"WEBI_HOST", p.Host},
		{"WEBI_VERSION", p.Version},
		{"WEBI_GIT_TAG", p.GitTag},
		{"WEBI_PKG_URL", p.PkgURL},
		{"WEBI_PKG_FILE", p.PkgFile},
		{"WEBI_PKG_PATHNAME", p.PkgFile},
		{"PKG_NAME", p.PkgName},
	}

	for _, v := range vars {
		text = InjectPSVar(text, v.name, v.value)
	}

	// PS1 marker is at column 0, no padding needed.
	text = replaceMarkerLine(text, "{{ installer }}", string(installPs1))

	return text, nil
}

// InjectPSVar replaces a PowerShell template variable line with its value.
// Matches lines like:
//
//	#$Env:WEBI_VERSION = v12.16.2
//	$Env:WEBI_HOST = 'https://webinstall.dev'
func InjectPSVar(text, name, value string) string {
	p := getPSVarPattern(name)
	return p.ReplaceAllString(text, "${1}$$Env:"+name+" = '"+sanitizePSValue(value)+"'")
}

var psVarPatterns = map[string]*regexp.Regexp{}

func getPSVarPattern(name string) *regexp.Regexp {
	if p, ok := psVarPatterns[name]; ok {
		return p
	}
	// Match: optional leading whitespace, optional #, $Env:NAME, =, rest of line
	p := regexp.MustCompile(`(?m)^([ \t]*)#?\$Env:` + regexp.QuoteMeta(name) + `\s*=.*$`)
	psVarPatterns[name] = p
	return p
}

// sanitizePSValue escapes single quotes for PowerShell single-quoted strings.
// In PowerShell, single quotes inside single-quoted strings are doubled: ''
func sanitizePSValue(s string) string {
	return strings.ReplaceAll(s, "'", "''")
}

// varPattern matches shell variable declarations in the template.
// Matches lines like:
//
//	#WEBI_VERSION=
//	#export WEBI_PKG_URL=
//	    #WEBI_OS=
var varPatterns = map[string]*regexp.Regexp{}

func getVarPattern(name string) *regexp.Regexp {
	if p, ok := varPatterns[name]; ok {
		return p
	}
	// Match: optional leading whitespace, optional #, optional export, the var name, =, rest of line
	p := regexp.MustCompile(`(?m)^([ \t]*)#?([ \t]*)(export[ \t]+)?[ \t]*(` + regexp.QuoteMeta(name) + `)=.*$`)
	varPatterns[name] = p
	return p
}

// InjectVar replaces a template variable line with its value.
// It matches lines like:
//
//	#WEBI_VERSION=
//	#export WEBI_PKG_URL=
//	export WEBI_HOST=
//
// and replaces them with the value in single quotes.
func InjectVar(text, name, value string) string {
	p := getVarPattern(name)
	return p.ReplaceAllString(text, "${1}${3}"+name+"='"+sanitizeShellValue(value)+"'")
}

// sanitizeShellValue ensures a value is safe to embed in single quotes.
// Single quotes in shell can't be escaped inside single quotes, so we
// close-quote, add escaped quote, re-open quote: 'foo'\''bar'
func sanitizeShellValue(s string) string {
	return strings.ReplaceAll(s, "'", `'\''`)
}

// padScript prepends each line of a script with the given indent string.
// This matches production behavior where install.sh content is indented
// to align with the surrounding template code.
func padScript(script, indent string) string {
	lines := strings.Split(script, "\n")
	for i, line := range lines {
		if line != "" {
			lines[i] = indent + line
		}
	}
	return strings.Join(lines, "\n")
}

// replaceMarkerLine replaces an entire line containing the marker
// (including any leading whitespace) with the replacement text.
// This matches production's regex: /\s*#?\s*{{ installer }}/
func replaceMarkerLine(text, marker, replacement string) string {
	re := regexp.MustCompile(`(?m)^[ \t]*#?[ \t]*` + regexp.QuoteMeta(marker) + `[^\n]*`)
	return re.ReplaceAllString(text, replacement)
}
