// Package main m2.go
package main

import (
	"bufio"
	"bytes"
	"embed"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	htmpl "html/template"
	//htmpl	"github.com/gofiber/template/html/v2"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	ttmpl "text/template"
	"time"
	"unicode"
	"reflect"

	"github.com/alecthomas/chroma/quick"

	"github.com/bitfield/script"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/logger"

	"github.com/0magnet/calvin"
	cc "github.com/ivanpirog/coloredcobra"
	"github.com/spf13/cobra"
	"github.com/stripe/stripe-go/v81"
	"github.com/stripe/stripe-go/v81/paymentintent"
)

//Files: head.html header.html header1.html main.html category.html product.html front.html font.html test.html sitemap.xml

//go:embed m2.go
var quine string

//go:embed htmpl/*
var templatesFS embed.FS

//go:embed content/*
var contentFS embed.FS

func mustReadFileToString(path string, fs embed.FS) (string) {
	data, _ := fs.ReadFile(path)
	return string(data)
}
func mustReadFileToBytes(path string, fs embed.FS) (res []byte) {
	res, _ = fs.ReadFile(path)
	return res
}

type htmlTemplate struct {
	Head string
	Header string
	Categories string
	CatSubcats string
	Footer string
	MainPage string
	FrontPage string
	CategoryPage string
	ProductPage string
	Schema string
	Cart string
	XmlSitemap string
	Wasm string
	Clock string
	AboutPage string
	PolicyPage string
	LinksPage string
	CheckoutPage string
	CompletePage string
	CheckoutCSS string
	StyleCSS string
	FaviconICO []byte
}

var h = htmlTemplate{
	Head: mustReadFileToString("htmpl/head.html", templatesFS),
	Header: mustReadFileToString("htmpl/header.html", templatesFS),
	Categories: mustReadFileToString("htmpl/categories.html", templatesFS),
	CatSubcats: mustReadFileToString("htmpl/catsubcats.html", templatesFS),
	Footer: mustReadFileToString("htmpl/footer.html", templatesFS),
	MainPage: mustReadFileToString("htmpl/main.html", templatesFS),
	FrontPage: mustReadFileToString("htmpl/front.html", templatesFS),
	CategoryPage: mustReadFileToString("htmpl/category.html", templatesFS),
	ProductPage: mustReadFileToString("htmpl/product.html", templatesFS),
	Schema: mustReadFileToString("htmpl/schema.html", templatesFS),
	Cart: mustReadFileToString("htmpl/cart.html", templatesFS),
	XmlSitemap: mustReadFileToString("htmpl/sitemap.xml", templatesFS),
	Wasm: mustReadFileToString("htmpl/wasm.html", templatesFS),
	Clock: mustReadFileToString("htmpl/clock.html", templatesFS),
	CompletePage: mustReadFileToString("htmpl/complete.html", templatesFS),
	AboutPage: mustReadFileToString("content/about.html", contentFS),
	PolicyPage: mustReadFileToString("content/policy.html", contentFS),
	LinksPage: mustReadFileToString("content/links.html", contentFS),
	CheckoutPage: mustReadFileToString("content/checkout.html", contentFS),
	CheckoutCSS: mustReadFileToString("content/checkout.css", contentFS),
	StyleCSS: mustReadFileToString("content/style.css", contentFS),
	FaviconICO: mustReadFileToBytes("content/favicon.ico", contentFS),
}


func main() {
	Execute()
}


func init() {
	stripe.EnableTelemetry = false
	rootCmd.CompletionOptions.DisableDefaultCmd = true
	rootCmd.AddCommand(
		runCmd,
		genCmd,
	)
	var helpflag bool
	rootCmd.SetUsageTemplate(help)
	rootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for "+rootCmd.Use)
	rootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
	rootCmd.PersistentFlags().MarkHidden("help") //nolint

}

var rootCmd = &cobra.Command{
	Use:   "m2",
	Short: "web store server",
	Long:  "web store server",
}

var genCmd = &cobra.Command{
	Use:   "gen",
	Short: "generate conf template",
	Long:  "generate conf template",
	Run: func(_ *cobra.Command, _ []string) {
		fmt.Println(envfiletemplate)
	},
}

// Execute executes the root cli command
func Execute() {
	cc.Init(&cc.Config{
		RootCmd:         rootCmd,
		Headings:        cc.HiBlue + cc.Bold,
		Commands:        cc.HiBlue + cc.Bold,
		CmdShortDescr:   cc.HiBlue,
		Example:         cc.HiBlue + cc.Italic,
		ExecName:        cc.HiBlue + cc.Bold,
		Flags:           cc.HiBlue + cc.Bold,
		FlagsDescr:      cc.HiBlue,
		NoExtraNewlines: true,
		NoBottomNewline: true,
	})
	if err := rootCmd.Execute(); err != nil {
		log.Fatal("Failed to execute command: ", err)
	}
}

var menvfile = os.Getenv("MENV")

type FlagVars struct {
	Teststripekey      bool
	ProductsCSV        string
	WebPort            int
	StripelivePK       string
	StripeliveSK       string
	StripetestPK       string
	StripetestSK       string
	StripeSK           string
	StripePK           string
	Siteimagesrc       string
	Siteordersurl string
	Sitename           string
	Siteext            string
	Sitedomain         string
	Sitelongname       string
	Sitetagline        string
	Sitemeta           string
	Siteprettyname     string
	Siteprettynamecap  string
	Siteprettynamecaps string
	Siteasciilogo      string
	Tgcontact    string
	Tgchannel    string
	UseTinygo bool
	StaticWasm bool
	WasmSRC []string
	WasmExecPath string
	WasmExecPathGo string
	WasmExecPathTinyGo string
	Gobuild string
	Tinygobuild string
	Buildwasmwith string
	LDFlagsX string
}


var f = FlagVars{
//	WasmSRC: []string{"wasm/stl2.go","wasm/checkout_wasm.go"},
	ProductsCSV: "products.csv",
	WasmExecPath: runtime.GOROOT() + "/misc/wasm/wasm_exec.js",
	WasmExecPathGo: runtime.GOROOT() + "/misc/wasm/wasm_exec.js",
	WasmExecPathTinyGo: strings.TrimSuffix(runtime.GOROOT(), "go") + "tinygo" + "/targets/wasm_exec.js",
	Gobuild: "go build",
	Tinygobuild: "tinygo build -target=wasm --no-debug",
	Buildwasmwith: "go build",
	LDFlagsX: "stripePK",
}

var (
	// Hardcoded array of valid shorthand characters, excluding "h"
	shorthandChars = []rune("abcdefgijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
	nextShortIndex = 0 // Index for the next shorthand flag
)

// Get the next available shorthand flag
func getNextShortFlag() string {
	if nextShortIndex >= len(shorthandChars) {
		return ""
	}
	short := shorthandChars[nextShortIndex]
	nextShortIndex++
	return string(short)
}

var a = true
var b = false

func addStringFlag(cmd *cobra.Command, f interface{}, fieldPtr *string, description string) {
	cmd.Flags().StringVarP(fieldPtr,ccc(fieldPtr, f, b),getNextShortFlag(),scriptExecString(fmt.Sprintf("${%s%s}", ccc(fieldPtr, f, a), func(s string) string {
		if s != "" {s= "-"+s}
		return s
		}(*fieldPtr))),	fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)))
}

func addStringSliceFlag(cmd *cobra.Command, f interface{}, fieldPtr *[]string, description string) {
	cmd.Flags().StringSliceVarP(
		fieldPtr,
		ccc(fieldPtr, f, b),
		getNextShortFlag(),
		scriptExecStringSlice(fmt.Sprintf("${%s[@]}", ccc(fieldPtr, f, a))),
		fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)),
	)
}

func addBoolFlag(cmd *cobra.Command, f interface{}, fieldPtr *bool, description string) {
	cmd.Flags().BoolVarP(fieldPtr,ccc(fieldPtr, f, b),getNextShortFlag(),scriptExecBool(fmt.Sprintf("${%s%s}", ccc(fieldPtr, f, a), func(b bool) string {
		return "-"+strconv.FormatBool(b)
		}(*fieldPtr))),	fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)))
}
func addIntFlag(cmd *cobra.Command, f interface{}, fieldPtr *int, description string) {
	cmd.Flags().IntVarP(fieldPtr,ccc(fieldPtr, f, b),getNextShortFlag(),scriptExecInt(fmt.Sprintf("${%s%s}", ccc(fieldPtr, f, a), func(i int) string {
		return fmt.Sprintf("-%d", i)
		}(*fieldPtr))),	fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)))
}

func init() {
	runCmd.Flags().SortFlags = false
	addStringFlag(runCmd, &f, &f.ProductsCSV, "products csv file")
	addBoolFlag(runCmd, &f, &f.Teststripekey, "use stripe test api keys instead of live key")
	addStringFlag(runCmd, &f, &f.StripeliveSK, "stripe live api sk")
	addStringFlag(runCmd, &f, &f.StripelivePK, "stripe live api pk")
	addStringFlag(runCmd, &f, &f.StripetestSK, "stripe test api sk")
	addStringFlag(runCmd, &f, &f.StripetestPK, "stripe test api pk")
	addIntFlag(runCmd, &f, &f.WebPort, "port to serve on")
	addStringFlag(runCmd, &f, &f.Siteimagesrc, "domain for images - leave blank to serve images")
	addStringFlag(runCmd, &f, &f.Siteordersurl, "domain for orders - leave blank for same domain")
	addStringFlag(runCmd, &f, &f.Sitename, "site name")
	addStringFlag(runCmd, &f, &f.Siteext, "site domain extension")
	addStringFlag(runCmd, &f, &f.Sitelongname, "site long name")
	addStringFlag(runCmd, &f, &f.Sitetagline, "site domain extension")
	addStringFlag(runCmd, &f, &f.Sitemeta, "site meta")
	addStringFlag(runCmd, &f, &f.Tgcontact, "telegram contact")
	addStringFlag(runCmd, &f, &f.Tgchannel, "telegram channel")
	addBoolFlag(runCmd, &f, &f.UseTinygo, "use tinygo instead of go to compile wasm")
	addBoolFlag(runCmd, &f, &f.StaticWasm, "compile wasm once and serve instead of per request")
	addStringSliceFlag(runCmd, &f, &f.WasmSRC, "wasm source code files RELATIVE PATHS without '..'")
}
// change case
func ccc(val interface{}, strct interface{}, upper bool) string {
	v := reflect.ValueOf(strct)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		panic("uc: second argument must be a pointer to a struct")
	}
	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		if field.CanAddr() && field.Addr().Interface() == val {
			if upper {
				return strings.ToUpper(v.Type().Field(i).Name)
			}
			return strings.ToLower(v.Type().Field(i).Name)
		}
	}
	return ""
}

var runCmd = &cobra.Command{
	Use:   "run",
	Short: "run the web application",
	Long: func() string {
		helptext := `Run the web application
Generate a config file first

Config defaults file may also be specified with:
MENV=m2.conf m2 run
OR
MENV=/path/to/m2.conf m2 run
print the MENV file template with:
m2 gen`
		if menvfile == "" {
			return helptext
		}
		if _, err := os.Stat(menvfile); err == nil {
			return `Run the web application

menv file detected: ` + menvfile
		}
		return helptext
	}(),
	Run: func(_ *cobra.Command, _ []string) {
		f.Sitedomain = f.Sitename + f.Siteext

		f.StripeSK = f.StripeliveSK
		f.StripePK = f.StripelivePK
		if f.Teststripekey {
			f.StripeSK = f.StripetestSK
			f.StripePK = f.StripetestPK
		}
		stripe.Key = f.StripeSK

		// awkward way to do this
		f.LDFlagsX += "="+f.StripePK

		f.Siteprettyname = calvin.BlackboardBold(f.Sitedomain)                      //"π•„π•’π•˜π•Ÿπ•–π•₯𝕠𝕀𝕑𝕙𝕖𝕣𝕖.π•Ÿπ•–π•₯"
		f.Siteprettynamecap = calvin.BlackboardBold(strings.Title(f.Sitedomain))    //"π•„π•’π•˜π•Ÿπ•–π•₯𝕠𝕀𝕑𝕙𝕖𝕣𝕖.π•Ÿπ•–π•₯"
		f.Siteprettynamecaps = calvin.BlackboardBold(strings.ToUpper(f.Sitedomain)) //"π•„π”Έπ”Ύβ„•π”Όπ•‹π•†π•Šβ„™β„π”Όβ„π”Ό.ℕ𝔼𝕋"
		f.Siteasciilogo = strings.Replace(strings.Replace(calvin.AsciiFont(f.Sitedomain), " ", "&nbsp;", -1), "\n", "<br>\n", -1)


		if f.UseTinygo {
			f.WasmExecPath = f.WasmExecPathTinyGo
			f.Buildwasmwith = f.Tinygobuild
		}
		if len(f.WasmSRC) == 0 {
			f.WasmExecPath = ""
			f.Buildwasmwith = ""
		}

		fileInfo, err := os.Stat("products.csv")
		if err != nil {
			log.Fatal("Error getting file info:", err)
			return
		}
		lastModTime = fileInfo.ModTime()
		allproducts = readCSV("products.csv")
		go func() {
			for {
				fileInfo, err := os.Stat("products.csv")
				if err != nil {
					log.Println("Error getting file info:", err)
				}

				currentModTime := fileInfo.ModTime()
				if currentModTime != lastModTime {
					log.Println("CSV file has been modified!")
					allproducts = readCSV("products.csv")
					lastModTime = currentModTime
				}

				time.Sleep(1 * time.Second)
			}
		}()
		server()
	},
}

var cWasm []byte
var lastModTime time.Time
var htmlPageTemplateData htmlTemplateData
var tmpl *htmpl.Template


func ldflags(s string) (ss string) {
	if f.LDFlagsX != "" {
		res, _ := script.File(s).Match(strings.Split(f.LDFlagsX, "=")[0]).String()
		if res != "" {
			ss += fmt.Sprintf(` -X 'main.%s' `,f.LDFlagsX)
		}
	}
	res, _ := script.File(s).Match("wasmName").String()
	if res != "" {
		ss += fmt.Sprintf(` -X 'main.wasmName=%s' `, strings.TrimSuffix(filepath.Base(s), filepath.Ext(s)) + ".wasm")
	}
	if ss != "" {
		ss = `-ldflags="` + ss + `"`
	}
	return ss
}

func server() {
	wg := new(sync.WaitGroup)
	wg.Add(1)
	htmlPageTemplateData = htmlTemplateData{
		TestMode: f.Teststripekey,
		Title:              f.Sitelongname,
		StripePK:           f.StripePK,
		SiteName:           f.Sitedomain,
		SiteTagLine:           f.Sitetagline,
		SiteName1:          htmpl.HTML(checkerBoard(f.Sitedomain)),
		SiteLongName:       f.Sitelongname,
		SiteAsciiLogo:      htmpl.HTML(f.Siteasciilogo),
		SitePrettyName:     f.Siteprettyname,
		SitePrettyNameCap:  f.Siteprettynamecap,
		SitePrettyNameCaps: f.Siteprettynamecaps,
		TelegramContact:    f.Tgcontact,
		TelegramChannel:    f.Tgchannel,
		WasmBinary:     func() (ret []string) {
			 if len(f.WasmSRC) == 0 {
				 return ret
			 }
//			if f.StaticWasm {
				for _, wasmSRC := range f.WasmSRC {
					outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + ".wasm"
					compilecmd := fmt.Sprintf("bash -c 'GOOS=js GOARCH=wasm %s %s -o %s %s'", f.Gobuild, ldflags(wasmSRC), outputFile, wasmSRC)
					log.Println("Compiling wasm with:")
					log.Println(compilecmd)
					startTime := time.Now()
					data, err := script.Exec(compilecmd).Bytes()
					if err != nil {
						log.Println(string(data))
						log.Fatal(err)
					}
					log.Println("Compiled wasm! Compile time:", time.Since(startTime))
					log.Println("size: ")
					_, _ = script.Exec(fmt.Sprintf("du -h %s", outputFile)).Stdout()
				}
				for _, wasmSRC := range f.WasmSRC {
					outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + "-tiny.wasm"
					compilecmd := fmt.Sprintf("bash -c 'GOOS=js GOARCH=wasm %s %s -o %s %s'",f.Tinygobuild, ldflags(wasmSRC), outputFile, wasmSRC)
					log.Println("compiling wasm with:")
					log.Println(compilecmd)
					startTime := time.Now()
					data, err := script.Exec(compilecmd).Bytes()
					if err != nil {
						log.Println(string(data))
						log.Fatal(err)
					}
					log.Println("Compiled wasm! Compile time:", time.Since(startTime))
					log.Println("size: ")
					_, _ = script.Exec(fmt.Sprintf("du -h %s", outputFile)).Stdout()
				}
				if f.UseTinygo {
					for _, wasmSRC := range f.WasmSRC {
						outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + "-tiny.wasm"
						ret = append(ret, outputFile)
					}
					return ret
				}
				for _, wasmSRC := range f.WasmSRC {
					outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + ".wasm"
					ret = append(ret, outputFile)
				}
				return ret
//			}
			return ret
	}(),
		WasmExecPath:   f.WasmExecPath,
		WasmExecRel:    "/wasm_exec.js",
		Cats:           getcats(),
		LenAllProducts: len(allproducts),
		ImgSRC: func() (ret string) {
			ret = f.Siteimagesrc
			if ret == "" {
				ret = "/img"
			}
			return ret
		}(),
		Page: "front",
		Time: time.Now().Format(time.RFC3339Nano),
		Year: fmt.Sprintf("%v", time.Now().Year()),
	}
	htmlPageTemplateData.CatsCounts, htmlPageTemplateData.Cats, htmlPageTemplateData.SubCatsCounts, htmlPageTemplateData.SubCatsByCat = getcategories(allproducts)
	var err1 error
	completetmpl, err1 := htmpl.New("index").Parse(h.CompletePage)
	if err1 != nil {
		log.Println("Error parsing complete page template:", err1)
	}
	_, err1 = completetmpl.New("wasm").Parse(h.Wasm)
	if err1 != nil {
		log.Println("Error parsing wasm template:", err1)
	}

	tmpl, err1 = htmpl.New("index").Funcs(htmpl.FuncMap{"replace": replace, "mul": mul, "div": div, "safeHTML": safeHTML, "safeJS": safeJS, "stripProtocol": stripProtocol, "add": add, "sub": sub, "toFloat": toFloat, "equalsIgnoreCase": equalsIgnoreCase, "getsubcats": getsubcats, "escapesubcat": escapesubcat, "sortsubcats": sortsubcats, "repeat": repeat}).Parse(h.MainPage)
	if err1 != nil {
		log.Println("Error parsing index template:", err1)
	}
	_, err1 = tmpl.New("head").Parse(h.Head)
	if err1 != nil {
		log.Println("Error parsing head template:", err1)
	}
	_, err1 = tmpl.New("schema").Parse(h.Schema)
	if err1 != nil {
		log.Println("Error parsing schema template:", err1)
	}
	_, err1 = tmpl.New("header").Parse(h.Header)
	if err1 != nil {
		log.Println("Error parsing header template:", err1)
	}
	_, err1 = tmpl.New("catsubcats").Parse(h.CatSubcats)
	if err1 != nil {
		log.Println("Error parsing header template:", err1)
	}
	_, err1 = tmpl.New("categories").Parse(h.Categories)
	if err1 != nil {
		log.Println("Error parsing categories template:", err1)
	}
	_, err1 = tmpl.New("footer").Parse(h.Footer)
	if err1 != nil {
		log.Println("Error parsing footer template:", err1)
	}
	_, err1 = tmpl.New("cart").Parse(h.Cart)
	if err1 != nil {
		log.Println("Error parsing cart template:", err1)
	}
	_, err1 = tmpl.New("wasm").Parse(h.Wasm)
	if err1 != nil {
		log.Println("Error parsing wasm template:", err1)
	}

	r := fiber.New(fiber.Config{
		ErrorHandler: func(c *fiber.Ctx, err error) error {
			code := fiber.StatusInternalServerError
			var e *fiber.Error
			if errors.As(err, &e) {
				code = e.Code
			}
			c.SendStatus(code)
			return nil
		},
	})
	r.Use(logger.New(logger.Config{
		Done:       nil,
		Format:     "${time} | ${status} | ${latency} | ${ip} | ${ips} | ${method} | ${path}\n",
		TimeFormat: "2006-01-02 15:04:05",
	}))

	r.Get("/m2.go", func(c *fiber.Ctx) error {
		c.Set("Content-Type", "text/html;charset=utf-8")
		lang := "go"
		var buf bytes.Buffer
		err := quick.Highlight(&buf, quine, lang, "html", "monokai")
		if err != nil {
			log.Println("error in function serveSyntaxHighlighted ; error on quick.Highlight: ", err)
			c.SendStatus(fiber.StatusInternalServerError)
			return err
		}
		c.Status(fiber.StatusOK).Write(buf.Bytes())
		return nil
	})

	if f.WasmExecPath != "" {
		_, err := script.File(f.WasmExecPath).Bytes()
		if err != nil {
			log.Printf("Error reading %s: %v\n", f.WasmExecPath, err)
		} else { //the wasm exec must be present or none of the webassembly stuff will work ; provided by the golang installaton
			r.Get("/wasm_exec.js", func(c *fiber.Ctx) error {
				wasmExecData, err := script.File(f.WasmExecPath).Bytes()
				if err != nil {
					log.Printf("Error reading %s: %v\n", f.WasmExecPath, err)
					c.SendStatus(fiber.StatusNotFound)
					return err
				}
				c.Status(fiber.StatusOK).Write(wasmExecData)
				return nil
			})
			if f.UseTinygo {
				for _, wasmSRC := range f.WasmSRC {
					outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + "-tiny.wasm"
					r.Get("/"+outputFile, func(c *fiber.Ctx) error {
						data, err := script.File(outputFile).Bytes()
						if err != nil {
							script.File(outputFile).Stdout()
							c.SendStatus(fiber.StatusInternalServerError)
							return err
						}
						c.Set("Content-Type", "application/wasm")
						c.Status(fiber.StatusOK).Send(data)
						return nil
					})
				}
			} else {
				for _, wasmSRC := range f.WasmSRC {
					outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + ".wasm"
					r.Get("/"+outputFile, func(c *fiber.Ctx) error {
						data, err := script.File(outputFile).Bytes()
						if err != nil {
							script.File(outputFile).Stdout()
							c.SendStatus(fiber.StatusInternalServerError)
							return err
						}
						c.Set("Content-Type", "application/wasm")
						c.Status(fiber.StatusOK).Send(data)
						return nil
					})
				}
			}
			for _, wasmSRC := range f.WasmSRC {
			r.Get("/"+wasmSRC, func(c *fiber.Ctx) error {
				return serveSyntaxHighlighted(c)
			})
		}
			/*
			r.Get("/b.wasm", func(c *fiber.Ctx) error {
				compilecmd := fmt.Sprintf("bash -c 'GOOS=js GOARCH=wasm %s %s -o /dev/stdout %s'", f.Buildwasmwith, ldflags(), f.WasmSRC)
				log.Println("Compiling wasm with:")
				log.Println(compilecmd)
				startTime := time.Now()
				data, err := script.Exec(compilecmd).Bytes()
				if err != nil {
					script.Exec(compilecmd).Stdout()
					c.SendStatus(fiber.StatusNotFound)
					return err
				}
				log.Println("Compiled wasm! Compile time:", time.Since(startTime))
				c.Set("Content-Type", "application/wasm")
				c.Status(fiber.StatusOK).Send(data)
				return nil
			})
			r.Get("/c.wasm", func(c *fiber.Ctx) error {
				c.Set("Content-Type", "application/wasm")
				c.Status(fiber.StatusOK).Send(cWasm)
				return nil
			})
			r.Get("/d.wasm", func(c *fiber.Ctx) error {
				c.Set("Content-Type", "application/wasm")
				dWasm, err := script.File("d.wasm").Bytes()
				if err != nil {
					c.SendStatus(fiber.StatusNotFound)
					return err
				}
				c.Status(fiber.StatusOK).Send(dWasm)
				return nil
			})
			r.Get("/e.wasm", func(c *fiber.Ctx) error {
				c.Set("Content-Type", "application/wasm")
				dWasm, err := script.File("e.wasm").Bytes()
				if err != nil {
					c.SendStatus(fiber.StatusNotFound)
					return err
				}
				c.Status(fiber.StatusOK).Send(dWasm)
				return nil
			})
			*/
		}
	}

	r.Static("/logo.png", "./logo.png")
	r.Static("/logo.html", "./logo.html")
	r.Static("/mobilelogo.html", "./mobilelogo.html")
	r.Static("/logolarge.html", "./logolarge.html")
	r.Get("/favicon.ico",  func(c *fiber.Ctx) error {
		c.Set("Content-Type", "image/jpeg")
		c.Status(fiber.StatusOK).Write(h.FaviconICO)
		return nil
	})
	r.Get("/robots.txt", func(c *fiber.Ctx) error {
		c.Set("Content-Type", "text/html;charset=utf-8")
		c.Status(fiber.StatusOK).Write([]byte(fmt.Sprintf("User-Agent: *\n\nSitemap: https://%s/sitemap", c.OriginalURL())))
		return nil
	})
	if f.Siteimagesrc == "" {
		r.Static("/img", "./img")
	}
	if len(f.WasmSRC) == 0 {
		r.Get("/stl/:filename", func(c *fiber.Ctx) error {
			stlfile, err := script.File("img/stl/" + c.Params("filename")).Bytes()
			if err != nil {
				c.SendStatus(fiber.StatusNotFound)
				return err
			}
			c.Write(stlfile)
			return nil
		})
		r.Get("/stl/base64/:filename", func(c *fiber.Ctx) error {
			stlfile, err := script.File("img/stl/" + c.Params("filename")).Bytes()
			if err != nil {
				c.SendStatus(fiber.StatusNotFound)
				return err
			}
			base64Data := base64.StdEncoding.EncodeToString(stlfile)
			c.Status(fiber.StatusOK).Write([]byte("data:model/stl;base64," + base64Data))
			return nil
		})
	}

	r.Get("/site.webmanifest", func(c *fiber.Ctx) error {
		c.JSON([]byte(`{"name":"","short_name":"","icons":[{"src":"` + f.Siteimagesrc + `/img/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"` + f.Siteimagesrc + `/img/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}`))
		return nil
	})
	r.Get("/sitemap", func(c *fiber.Ctx) error {
		c.XML(generateSitemapXML())
		return nil
	})
	r.Get("/sitemap.xml", func(c *fiber.Ctx) error {
		c.XML(generateSitemapXML())
		return nil
	})

	r.Get("/coffee", func(c *fiber.Ctx) error {
		c.SendStatus(fiber.StatusTeapot)
		return nil
	})

	r.Get("/clock", func(c *fiber.Ctx) error {
		c.Set("Content-Type", "text/html;charset=utf-8")
		c.Status(fiber.StatusOK).Write([]byte(h.Clock))
		return nil
	})

	r.Get("/", func(c *fiber.Ctx) error {
		tmpl0, err1 := tmpl.Clone()
		if err1 != nil {
			log.Println("Error cloning template:", err1)
		}
		_, err1 = tmpl0.New("main").Parse(h.FrontPage)
		if err1 != nil {
			log.Println("Error parsing Front Page template:", err1)
		}
		_, err1 = tmpl0.New("about").Parse(h.AboutPage)
		if err1 != nil {
			log.Println("Error parsing about template:", err1)
		}
		_, err1 = tmpl0.New("policy").Parse(h.PolicyPage)
		if err1 != nil {
			log.Println("Error parsing about template:", err1)
		}
		_, err1 = tmpl0.New("links").Parse(h.LinksPage)
		if err1 != nil {
			log.Println("Error parsing about template:", err1)
		}
		tmpl := tmpl0
		log.Println(c.Get("User-Agent"))
		c.Set("Content-Type", "text/html;charset=utf-8")
		htmlPageTemplateData1 := htmlPageTemplateData
		htmlPageTemplateData1.Canonical = c.Protocol() + `://` + string(c.Request().Host()) + c.OriginalURL()
		htmlPageTemplateData1.BaseURL = c.Protocol() + `://` + string(c.Request().Host())
		htmlPageTemplateData1.RequestHost = string(c.Request().Host())
		htmlPageTemplateData1.Protocol = c.Protocol()
//		htmlPageTemplateData1.Mobile = strings.Contains(strings.ToLower(c.Get("User-Agent")), "mobile")
		htmlPageTemplateData1.CatsCounts, htmlPageTemplateData1.Cats, htmlPageTemplateData1.SubCatsCounts, htmlPageTemplateData1.SubCatsByCat = getcategories(allproducts)
		htmlPageTemplateData1.LenAllProducts = len(allproducts)
		htmlPageTemplateData1.Time = time.Now().Format(time.RFC3339Nano)
		htmlPageTemplateData1.Year = fmt.Sprintf("%v", time.Now().Year())
		htmlPageTemplateData1.MetaDesc = f.Sitemeta
		htmlPageTemplateData1.KeyWords = strings.Replace(f.Sitelongname, " ", ", ", -1)
		tmplData := map[string]interface{}{
			"Page":  htmlPageTemplateData1,
			"Prods": allproducts,
		}
		var result bytes.Buffer
		err := tmpl.Execute(&result, tmplData)
		if err != nil {
			log.Println("error: ", err)
			c.SendStatus(fiber.StatusInternalServerError)
			return err
		}
		c.Status(fiber.StatusOK).Write(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(result.Bytes(), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1))
		return nil
	})

	r.Get("/p/:partno", func(c *fiber.Ctx) error {
		tmpl0, err1 := tmpl.Clone()
		if err1 != nil {
			log.Println("Error cloning template:", err1)
		}
		_, err1 = tmpl0.New("main").Parse(h.ProductPage)
		if err1 != nil {
			log.Println("Error parsing product page template:", err1)
		}
		tmpl := tmpl0
		c.Set("Content-Type", "text/html;charset=utf-8")
		for _, prod := range allproducts {
			if prod.Partno == c.Params("partno") {
				var result bytes.Buffer
				htmlPageTemplateData1 := htmlPageTemplateData
				htmlPageTemplateData1.Canonical = c.Protocol() + `://` + string(c.Request().Host()) + c.OriginalURL()
				htmlPageTemplateData1.BaseURL = c.Protocol() + `://` + string(c.Request().Host())
				htmlPageTemplateData1.RequestHost = string(c.Request().Host())
				htmlPageTemplateData1.Protocol = c.Protocol()
				htmlPageTemplateData1.Title = fmt.Sprintf("%s | %s", prod.Name, htmlPageTemplateData1.Title)
//				htmlPageTemplateData1.Mobile = strings.Contains(strings.ToLower(c.Get("User-Agent")), "mobile")
				htmlPageTemplateData1.Page = "product"
				htmlPageTemplateData1.Time = time.Now().Format(time.RFC3339Nano)
				htmlPageTemplateData1.Year = fmt.Sprintf("%v", time.Now().Year())
				tmplData := map[string]interface{}{
					"Prod":  prod,
					"Page":  htmlPageTemplateData1,
					"Prods": allproducts,
				}
				err := tmpl.Execute(&result, tmplData)
				if err != nil {
					log.Println("error: ", err)
					c.Status(fiber.StatusInternalServerError).Write(result.Bytes())
					return err
				}
				c.Status(fiber.StatusOK).Write(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(result.Bytes(), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1))
				return nil
			}
		}
		log.Printf("product %s does not match any existing product\n", c.Params("partno"))
		c.Redirect("/cat", fiber.StatusMovedPermanently)
		return nil
	})
	r.Get("/post/:partno", handlecat)
	r.Get("/p", handlecat)
	r.Get("/cat", handlecat)
	r.Get("/cat/:cat", handlecat)
	r.Get("/cat/:cat/:subcat", handlecat)

	r.Get("/checkout.css", func(c *fiber.Ctx) error {
		c.Set("Content-Type", "text/css;charset=utf-8")
		c.Status(fiber.StatusOK).Write([]byte(h.CheckoutCSS))
		return nil
	})
	r.Get("/style.css", func(c *fiber.Ctx) error {
		c.Set("Content-Type", "text/css;charset=utf-8")
		c.Status(fiber.StatusOK).Write([]byte(h.StyleCSS))
		return nil
	})

	r.Get("/complete", func(c *fiber.Ctx) error {
		htmlPageTemplateData1 := htmlPageTemplateData
		htmlPageTemplateData1.Canonical = c.Protocol() + `://` + c.Hostname() + c.OriginalURL()
		htmlPageTemplateData1.BaseURL = c.Protocol() + `://` + c.Hostname()
		htmlPageTemplateData1.RequestHost = c.Hostname()
		htmlPageTemplateData1.Protocol = c.Protocol()
		htmlPageTemplateData1.Time = time.Now().Format(time.RFC3339Nano)
		htmlPageTemplateData1.Year = fmt.Sprintf("%v", time.Now().Year())
		tmplData := map[string]interface{}{
			"Page":  htmlPageTemplateData1,
		}
		var result bytes.Buffer
		err := completetmpl.Execute(&result, tmplData)
		if err != nil {
			msg := fmt.Sprintf("Could not execute html template %v\n", err)
			log.Println(msg)
			return c.Status(fiber.StatusInternalServerError).SendString(msg)
		}
		c.Set("Content-Type", "text/html;charset=utf-8")
		return c.Status(fiber.StatusOK).Send(result.Bytes())
	})

	r.Get("/order/:piid", func(c *fiber.Ctx) error {
		piid := c.Params("piid")
		order, err := script.File("orders/" + piid + ".json").Bytes()
		if err != nil {
			return c.Status(fiber.StatusNotFound).SendString("Order not found")
		}
		return c.Status(fiber.StatusOK).Send(order)
	})

	r.Post("/create-payment-intent", func(c *fiber.Ctx) error {
		rawBody := c.Body()
		if rawBody == nil {
			log.Printf("Failed to read raw request body")
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to read request body"})
		}
		log.Printf("Raw request body: %s", string(rawBody))

		var req struct {
			Items []item `json:"items"`
		}
		if err := json.Unmarshal(rawBody, &req); err != nil {
			log.Printf("Failed to parse JSON: %v", err)
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
		}

		total := int64(0)
		for _, item := range req.Items {
			total += item.Amount
		}

		params := &stripe.PaymentIntentParams{
			Amount:   stripe.Int64(total),
			Currency: stripe.String(string(stripe.CurrencyUSD)),
		}
		pi, err := paymentintent.New(params)
		if err != nil {
			log.Printf("Failed to create PaymentIntent: %v", err)
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
		}

		log.Printf("Created PaymentIntent with ClientSecret: %v", pi.ClientSecret)
		return c.Status(fiber.StatusOK).JSON(fiber.Map{
			"clientSecret":   pi.ClientSecret,
			"dpmCheckerLink": fmt.Sprintf("https://dashboard.stripe.com/settings/payment_methods/review?transaction_id=%s", pi.ID),
		})
	})

	r.Post("/submit-order", func(c *fiber.Ctx) error {
		var requestData struct {
			LocalStorageData map[string]interface{} `json:"localStorageData"`
			PaymentIntentId  string                 `json:"paymentIntentId"`
		}

		if err := c.BodyParser(&requestData); err != nil {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request data"})
		}

		log.Printf("Received order data: %+v", requestData.LocalStorageData)
		log.Printf("Received payment intent ID: %s", requestData.PaymentIntentId)

		paymentIntent, err := paymentintent.Get(requestData.PaymentIntentId, nil)
		if err != nil {
			log.Printf("Error retrieving payment intent: %v", err)
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to verify payment"})
		}

		if paymentIntent.Status != stripe.PaymentIntentStatusSucceeded {
			log.Printf("Payment was not successful, status: %s", paymentIntent.Status)
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Payment not successful"})
		}

		ordersDir := "./orders"
		if err := os.MkdirAll(ordersDir, os.ModePerm); err != nil {
			log.Printf("Error creating orders directory: %v", err)
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save order"})
		}

		filePath := filepath.Join(ordersDir, fmt.Sprintf("%s.json", requestData.PaymentIntentId))

		data, err := json.MarshalIndent(requestData.LocalStorageData, "", "  ")
		if err != nil {
			log.Printf("Error marshalling data: %v", err)
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save order"})
		}

		if err := os.WriteFile(filePath, data, 0644); err != nil {
			log.Printf("Error writing data to file: %v", err)
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save order"})
		}

		return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Order submitted successfully"})
	})




	go func() {
		err := r.Listen(fmt.Sprintf(":%d", f.WebPort))
		if err != nil {
			log.Println("Error serving http: ", err)
		}
		wg.Done()
	}()
	wg.Wait()
}

type item struct {
	Id     string
	Amount int64
}

func htmlErr(msg string) []byte {
	return []byte(fmt.Sprintf(`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Error</title></head><body style='background-color: black; color: white;'><div>%s</div></body></html>`, strings.ReplaceAll(msg, "\n", "<br>")))
}

func handlecomplete(c *fiber.Ctx) error {
	tmpl2, err2 := htmpl.New("index").Parse(h.CompletePage)
	if err2 != nil {
		log.Println("Error parsing h.CompletePage template:", err2)
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err2.Error()})
	}
	_, err2 = tmpl2.New("css").Parse(h.CheckoutCSS)
	if err2 != nil {
		log.Println("Error parsing checkoutCSS template:", err2)
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err2.Error()})
	}
	htmlPageTemplateData2 := htmlTemplateData{
		StripePK: f.StripePK,
		OrdersURL: f.Siteordersurl,
	}
	var result bytes.Buffer
	tmplData := map[string]interface{}{
		"Page": htmlPageTemplateData2,
	}
	err2 = tmpl2.Execute(&result, tmplData)
	if err2 != nil {
		log.Println("error: ", err2)
		c.Status(fiber.StatusInternalServerError).Write(result.Bytes())
		return err2
	}
	c.Status(fiber.StatusOK).Type("html").Write(result.Bytes())
	return nil
}

func scriptFile(a string) string {
	re, err := script.File(a).String()
	if err != nil {
		log.Printf("Error on script.File(%s).String(): %v", a, err)
	}
	return re
}

func isAllUpperCase(s string) bool {
	for _, char := range s {
		if unicode.IsLetter(char) && !unicode.IsUpper(char) {
			return false
		}
	}
	return true
}

func serveSyntaxHighlighted(c *fiber.Ctx) error {

	c.Set("Content-Type", "text/html;charset=utf-8")
	data, err := script.File(strings.TrimLeft(c.OriginalURL(), "/")).String()
	if err != nil {
		log.Println("error in function serveSyntaxHighlighted ; error on script.File: ", err)
		c.SendStatus(fiber.StatusNotFound)
		return err
	}
	lang := strings.TrimLeft(filepath.Ext(strings.TrimLeft(c.OriginalURL(), "/")), ".")
	if lang == "sh" {
		lang = "bash"
	}
	var buf bytes.Buffer
	err = quick.Highlight(&buf, data, lang, "html", "monokai")
	if err != nil {
		log.Println("error in function serveSyntaxHighlighted ; error on quick.Highlight: ", err)
		c.SendStatus(fiber.StatusInternalServerError)
		return err
	}
	if c.Context().IsHead() {
		c.SendStatus(fiber.StatusOK)
		return nil
	}
	c.Status(fiber.StatusOK).Write(buf.Bytes())
	return nil
}

func cathtmlfunc(c *fiber.Ctx) error {
	tmpl0, err1 := tmpl.Clone()
	if err1 != nil {
		log.Println("Error cloning template:", err1)
	}
	_, err1 = tmpl0.New("main").Parse(h.CategoryPage)
	if err1 != nil {
		log.Println("Error parsing Category page template:", err1)
	}

	tmpl := tmpl0
	var tmplData map[string]interface{}
	var result bytes.Buffer
	var categoryproducts Products
	c.Set("Content-Type", "text/html;charset=utf-8")
	htmlPageTemplateData1 := htmlPageTemplateData
	htmlPageTemplateData1.Title = fmt.Sprintf("%s | %s", func() string {
		var str string
		if c.Params("partno") != "" {
			return "No product matching partno.: " + c.Params("partno") + " | Showing All Products"
		}
		if c.Params("cat") == "" {
			return "All Products"
		} else {
			str = fmt.Sprintf("Category: %s", c.Params("cat"))
		}
		if c.Params("subcat") != "" {
			str += fmt.Sprintf("; Subcategory: %s", c.Params("subcat"))
		}
		return str
	}(), htmlPageTemplateData1.Title)
	htmlPageTemplateData1.Canonical = c.Protocol() + `://` + string(c.Request().Host()) + c.OriginalURL()
	htmlPageTemplateData1.BaseURL = c.Protocol() + `://` + string(c.Request().Host())
	htmlPageTemplateData1.RequestHost = string(c.Request().Host())
	htmlPageTemplateData1.Protocol = c.Protocol()
//	htmlPageTemplateData1.Mobile = strings.Contains(strings.ToLower(c.Get("User-Agent")), "mobile")
	htmlPageTemplateData1.Page = "category"
//	htmlPageTemplateData1.WasmBinary = ""
	htmlPageTemplateData1.CatsCounts, htmlPageTemplateData1.Cats, htmlPageTemplateData1.SubCatsCounts, htmlPageTemplateData1.SubCatsByCat = getcategories(allproducts)
	if c.Params("cat") == "" && c.Params("subcat") == "" {
		tmplData = map[string]interface{}{
			"Products":    allproducts,
			"Page":        htmlPageTemplateData1,
			"Category":    c.Params("cat"),
			"Subcategory": c.Params("subcat"),
			"Prods":       allproducts,
			"Product":     c.Params("partno"),
		}
	} else {

		for _, prod := range allproducts {
			if prod.Category == c.Params("cat") && (c.Params("subcat") == "" || escapesubcat(prod.Subcategory) == c.Params("subcat")) {
				categoryproducts = append(categoryproducts, prod)
			}
		}
		tmplData = map[string]interface{}{
			"Products":    categoryproducts,
			"Page":        htmlPageTemplateData1,
			"Category":    c.Params("cat"),
			"Subcategory": c.Params("subcat"),
			"Prods":       allproducts,
		}
	}
	err := tmpl.Execute(&result, tmplData)
	if err != nil {
		log.Println("error: ", err)
		c.SendStatus(fiber.StatusInternalServerError)
		return err
	}
	c.Status(fiber.StatusOK).Write(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(result.Bytes(), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1))
	return nil
}

func parseFloat(s string) float64 {
	if s == "" {
		return 0.0
	}
	value, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
	if err != nil {
		log.Printf(`Error on strconv.ParseFloat(strings.TrimSpace(%s), 64): %v`, s, err)
	}
	return value
}

const shcmd = `/usr/bin/bash -c`

func getcats() (cats []string) {
	var catsMap = make(map[string]int)
	for _, prod := range allproducts {
		catsMap[prod.Category]++
	}
	for cat := range catsMap {
		cats = append(cats, cat)
	}
	return cats
}
func contains(slice []string, str string) bool {
	for _, s := range slice {
		if s == str {
			return true
		}
	}
	return false
}
func getcategories(allproducts Products) (map[string]int, []string, map[string]map[string]int, map[string][]string) {
	categoryCounts := make(map[string]int)
	subcategoryCounts := make(map[string]map[string]int)
	subcategoriesByCategory := make(map[string][]string)

	for _, prod := range allproducts {
		if prod.Category != "" {
			categoryCounts[prod.Category]++
			if prod.Subcategory != "" {
				if subcategoryCounts[prod.Category] == nil {
					subcategoryCounts[prod.Category] = make(map[string]int)
				}
				subcategoryCounts[prod.Category][prod.Subcategory]++
				if !contains(subcategoriesByCategory[prod.Category], prod.Subcategory) {
					subcategoriesByCategory[prod.Category] = append(subcategoriesByCategory[prod.Category], prod.Subcategory)
				}
			}
		}
	}

	var sortableCategories []struct {
		Name  string
		Count int
	}
	for cat, count := range categoryCounts {
		sortableCategories = append(sortableCategories, struct {
			Name  string
			Count int
		}{Name: cat, Count: count})
	}
	sort.Slice(sortableCategories, func(i, j int) bool {
		return sortableCategories[i].Count > sortableCategories[j].Count
	})
	var sortedCategories []string
	for _, cat := range sortableCategories {
		sortedCategories = append(sortedCategories, cat.Name)
		var sortableSubcategories []struct {
			Name  string
			Count int
		}
		for subcat, count := range subcategoryCounts[cat.Name] {
			sortableSubcategories = append(sortableSubcategories, struct {
				Name  string
				Count int
			}{Name: subcat, Count: count})
		}
		sort.Slice(sortableSubcategories, func(i, j int) bool {
			return sortableSubcategories[i].Count > sortableSubcategories[j].Count
		})
		var sortedSubcategories []string
		for _, subcat := range sortableSubcategories {
			sortedSubcategories = append(sortedSubcategories, subcat.Name)
		}
		subcategoriesByCategory[cat.Name] = sortedSubcategories
	}
	return categoryCounts, sortedCategories, subcategoryCounts, subcategoriesByCategory
}

var subcats []string

func getsubcats(cat string) (subcats []string) {
	var subcatsMap = make(map[string]int)
	for _, prod := range allproducts {
		if cat == "" || cat == prod.Category {
			if prod.Subcategory != "" {
				subcat := strings.Replace(prod.Subcategory, "ΒΌ", "quarter-", -1)
				subcat = strings.Replace(subcat, "Β½", "half-", -1)
				subcat = strings.Replace(subcat, "1/16", "sixteenth-", -1)
				subcat = strings.Replace(subcat, "%", "-pct", -1)
				subcat = strings.Replace(subcat, "  ", " ", -1)
				subcat = strings.Replace(subcat, " ", "-", -1)
				subcat = strings.Replace(subcat, "--", "-", -1)
				subcat = strings.Replace(subcat, "watt1", "watt-1", -1)
				subcat = strings.Replace(subcat, "watt5", "watt-5", -1)
				subcatsMap[subcat]++
			}
		}
	}
	for subcat := range subcatsMap {
		subcats = append(subcats, subcat)
	}
	return subcats
}
func escapesubcat(subcat string) (escapedsubcat string) {
	escapedsubcat = strings.Replace(subcat, "ΒΌ", "quarter-", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "Β½", "half-", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "1/16", "sixteenth-", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "%", "-pct", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "  ", " ", -1)
	escapedsubcat = strings.Replace(escapedsubcat, " ", "-", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "--", "-", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "watt1", "watt-1", -1)
	escapedsubcat = strings.Replace(escapedsubcat, "watt5", "watt-5", -1)
	return escapedsubcat
}

func handlecat(c *fiber.Ctx) error {
	if c.Params("cat") == "" && c.Params("subcat") == "" {
		cathtmlfunc(c)
		return nil
	}
	var catexists bool
	var subcatexists bool
	catexists = false
	for _, cat := range getcats() {
		if cat == c.Params("cat") {
			catexists = true
			break
		}
	}
	subcatexists = false
	if c.Params("subcat") != "" {
		for _, subcat := range getsubcats("") {
			if escapesubcat(subcat) == c.Params("subcat") {
				subcatexists = true
				break
			}
		}
	}
	if c.Params("subcat") != "" && !subcatexists {
		log.Printf("subcategory %s does not match any existing subcategory\n", c.Params("subcat"))
		c.Redirect("/cat/"+c.Params("cat"), http.StatusMovedPermanently)
		return nil
	}
	if !catexists {
		log.Printf("category %s does not match any existing category\n", c.Params("cat"))
		c.Redirect("/cat", http.StatusMovedPermanently)
		return nil
	}
	if catexists || (catexists && subcatexists) {
		cathtmlfunc(c)
		return nil
	}
	c.SendStatus(fiber.StatusNotFound)
	return nil
}

func getCatsAndSubcats(data []byte) ([]string, map[string][]string) {
	lines := strings.Split(string(data), "\n")
	categoryCounts := make(map[string]int)
	subcategoryMap := make(map[string][]string)
	for _, line := range lines {
		fields := strings.Split(line, ",")
		if len(fields) >= 14 && fields[3] == "TRUE" {
			category := fields[13]
			categoryCounts[category]++
			subcategory := fields[14]
			if len(subcategory) > 0 {
				subcategoryMap[category] = append(subcategoryMap[category], subcategory)
			}
		}
	}
	var cats []string
	for cat := range categoryCounts {
		cats = append(cats, cat)
	}
	sort.Strings(cats)
	return cats, subcategoryMap
}

type xmlTemplateData struct {
	Cats         []string
	SubCatsByCat map[string][]string
	Products     Products
	Update       string
}

func generateSitemapXML() string {
	xmlSitemapTemplateData := xmlTemplateData{
		Products: allproducts,
		Update:   time.Now().Format("2006-01-02"),
	}
	_, xmlSitemapTemplateData.Cats, _, xmlSitemapTemplateData.SubCatsByCat = getcategories(allproducts)
	var err1 error
	xtmpl, err1 := ttmpl.New("index").Funcs(ttmpl.FuncMap{"getsubcats": getsubcats}).Parse(h.XmlSitemap)
	if err1 != nil {
		log.Println("Error parsing index template:", err1)
	}
	var result bytes.Buffer
	err1 = xtmpl.Execute(&result, xmlSitemapTemplateData)
	if err1 != nil {
		log.Println("error: ", err1)
	}
	return result.String()
}

func toFloat(s string) float64 {
	if s == "" {
		return 0.0
	}
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0.0
	}
	return f
}

func checkerBoard(input string) string {
	var result strings.Builder
	for i, char := range input {
		// Wrap every other letter with the specified HTML
		if i%2 == 0 {
			result.WriteString(fmt.Sprintf("<span class='nv'>%c</span>", char))
		} else {
			result.WriteRune(char)
		}
	}
	return result.String()
}

const help = "\r\n" +
	"  {{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
	"{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
	"Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n  " +
	"{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
	"Flags:\r\n" +
	"{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
	"Global Flags:\r\n" +
	"{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"

type htmlTemplateData struct {
	Title              string
	MetaDesc           string
	Canonical          string
	BaseURL            string
	ImgSRC             string // url where images are hosted
	OrdersURL             string // url where checkout is served from
	SiteName           string
	SiteTagLine	string
	SiteName1          htmpl.HTML //checkerboard - alternate swap text & bg color
	SiteLongName       string
	SitePrettyName     string //π•„π•’π•˜π•Ÿπ•–π•₯𝕠𝕀𝕑𝕙𝕖𝕣𝕖.π•Ÿπ•–π•₯
	SitePrettyNameCap  string //π•„π•’π•˜π•Ÿπ•–π•₯𝕠𝕀𝕑𝕙𝕖𝕣𝕖.π•Ÿπ•–π•₯
	SitePrettyNameCaps string //π•„π”Έπ”Ύβ„•π”Όπ•‹π•†π•Šβ„™β„π”Όβ„π”Ό.ℕ𝔼𝕋
	SiteAsciiLogo      htmpl.HTML
	TelegramContact    string
	TelegramChannel    string
	Protocol           string
	RequestHost        string
	KeyWords           string
	Style              htmpl.HTML
	Heading            htmpl.HTML
	StripePK           string
	Cats               []string
	CatsCounts         map[string]int
	SubCatsCounts      map[string]map[string]int
	SubCatsByCat       map[string][]string
	LenAllProducts     int
	Mobile             bool
	Gocanvas           htmpl.HTML
	WasmBinary         []string
	WasmExecPath       string
	WasmExecRel        string
	StyleFontFace      htmpl.CSS
	Message            htmpl.HTML
	Page               string
	Year               string
	Time               string
	AboutHTML          htmpl.HTML
	LinksHTML          htmpl.HTML
	PolicyHTML         htmpl.HTML
	TestMode         bool
}

func equalsIgnoreCase(a, b string) bool {
	return strings.EqualFold(strings.Join(strings.Fields(a), ""), strings.Join(strings.Fields(b), ""))
}

func replace(s, old, new string) string {
	return strings.ReplaceAll(s, old, new)
}
func mul(a, b float64) float64 {
	return a * b
}
func div(a, b float64) float64 {
	return a / b
}
func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}
func safeHTML(s string) htmpl.HTML {
	return htmpl.HTML(s)
}
func safeJS(s string) htmpl.JS {
	return htmpl.JS(s)
}
func stripProtocol(s string) string {
	return strings.Replace(strings.Replace(s, "https://", "", -1), "http://", "", -1)
}
func repeat(s string, count int) string {
	var result string
	for i := 0; i < count; i++ {
		result += s
	}
	return result
}
func sortsubcats(subcats []string, counts map[string]map[string]int) []string {
	sort.Slice(subcats, func(i, j int) bool {
		catI, catJ := subcats[i], subcats[j]
		countI, countJ := counts[catI]["count"], counts[catJ]["count"]
		return countI > countJ
	})
	return subcats
}

// Product represents a product record line in the products csv
type Product struct {
	Enable            string
	Partno            string
	Name              string
	Image1            string
	Price             string
	Quantity          string
	Shippable         string
	Minorder          string
	Maxorder          string
	Defaultquantity   string
	Stepquantity      string
	Mfgpartno         string
	Mfgname           string
	Category          string
	Subcategory       string
	Location          string
	Msrp              string
	Cost              string
	Typ               string
	Packagetype       string
	Technology        string
	Materials         string
	Value             string
	ValUnit           string
	Resistance        string
	ResUnit           string
	Tolerance         string
	VoltsRating       string
	AmpsRating        string
	WattsRating       string
	TempRating        string
	TempUnit          string
	Description1      string
	Description2      string
	Color1            string
	Color2            string
	Sourceinfo        string
	Datasheet         string
	Docs              string
	Reference         string
	Attributes        string
	Year              string
	Condition         string
	Note              string
	Warning           string
	CableLengthInches string
	LengthInches      string
	WidthInches       string
	HeightInches      string
	WeightLb          string
	WeightOz          string
}

// Products is an array of Product
type Products []Product

var allproducts Products

func readproductscsv(csvFile string) (data []byte) {
	data, err := os.ReadFile(csvFile)
	if err != nil {
		log.Printf(`Error on os.ReadFile("products.csv"): %v`, err)
	}
	return data
}

func readCSV(csvFile string) (prods Products) {
	scanner := bufio.NewScanner(bytes.NewReader(readproductscsv(csvFile)))
	for scanner.Scan() {
		line := scanner.Text()
		fields := strings.Split(line, ",")
		if len(fields) < 3 {
			continue
		}
		if fields[3] == "TRUE" {
			p := Product{
				Image1:            fields[0],
				Partno:            fields[1],
				Name:              fields[2],
				Enable:            fields[3],
				Price:             fields[4],
				Quantity:          fields[5],
				Shippable:         fields[6],
				Minorder:          fields[7],
				Maxorder:          fields[8],
				Defaultquantity:   fields[9],
				Stepquantity:      fields[10],
				Mfgpartno:         fields[11],
				Mfgname:           fields[12],
				Category:          fields[13],
				Subcategory:       fields[14],
				Location:          fields[15],
				Msrp:              fields[16],
				Cost:              fields[17],
				Typ:               fields[18],
				Packagetype:       fields[19],
				Technology:        fields[20],
				Materials:         fields[21],
				Value:             fields[22],
				ValUnit:           fields[23],
				Resistance:        fields[24],
				ResUnit:           fields[25],
				Tolerance:         fields[26],
				VoltsRating:       fields[27],
				AmpsRating:        fields[28],
				WattsRating:       fields[29],
				TempRating:        fields[30],
				TempUnit:          fields[31],
				Description1:      fields[32],
				Description2:      fields[33],
				Color1:            fields[34],
				Color2:            fields[35],
				Sourceinfo:        fields[36],
				Datasheet:         fields[37],
				Docs:              fields[38],
				Reference:         fields[39],
				Attributes:        fields[40],
				Year:              fields[41],
				Condition:         fields[42],
				Note:              fields[43],
				Warning:           fields[44],
				CableLengthInches: fields[45],
				LengthInches:      fields[46],
				WidthInches:       fields[47],
				HeightInches:      fields[48],
				WeightLb:          fields[49],
				WeightOz:          fields[50],
			}
			prods = append(prods, p)
		}
	}
	return prods
}

func scriptExecString(s string) string {
	z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s"'`, menvfile, s)).String()
	if err == nil {
		return strings.TrimSpace(z)
	}
	return ""
}

func scriptExecStringSlice(s string) []string {
	z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s" "%s"'`, menvfile,"%s\n", s)).Slice()
	if err == nil {
		return z
	}
	return []string{""}
}

func scriptExecBool(s string) bool {
	z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s"'`, menvfile, s)).String()
	if err == nil {
		b, err := strconv.ParseBool(z)
		if err == nil {
			return b
		}
	}
	return false
}

func scriptExecArray(s string) string {
	y, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, menvfile, s)).Slice()
	if err == nil {
		return strings.Join(y, ",")
	}
	return ""
}

func scriptExecInt(s string) int {
	z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s"'`, menvfile, s)).String()
	if err == nil {
		if z == "" {
			return 0
		}
		i, err := strconv.Atoi(z)
		if err == nil {
			return i
		}
	}
	return 0
}

const envfiletemplate = `#
# /etc/m2.conf
#
#########################################################################
#	M2 CONFIG TEMPLATE
# change config defaults
# or comment values with # to exclude
#########################################################################

### Stripe Configuration ################################################

#--	Live and test API keys - REQUIRED
STRIPELIVEPK='pk_live_...'
STRIPELIVESK='sk_live_...'
STRIPETESTPK='pk_test_...'
STRIPETESTSK='sk_test_...'

#--	Use Test Keys
USETESTKEY=true

### Site Product Data Configuration #####################################

#-- Products CSV path (ex. 'products.csv')
PRODUCTSCVS=''

#-- Image subdomain (ex. 'https://img.magnetosphere.net')
# no trailing slash '/' !
IMGSRC=''

#-- Orders subdomain (ex. 'https://pay.magnetosphere.net')
# no trailing slash '/' !
ORDERSURL=''

### Site Configuration ##################################################

#-- Website (Host) Name (domain minus extension - ex. 'magnetosphere')
SITENAME=''

#-- Website Domain Extension (ex. '.com' '.net')
SITEEXT=''

#-- Site Long Name (ex. 'magnetosphere electronic surplus')
SITELONGNAME=''

#-- Site Tag Line (ex. 'we have the technology')
SITETAGLINE=''

#-- Site Meta Description (ex. 'we have the technology (β—•β€Ώβ—•) electronic surplus for sale')
SITEMETA=''

#-- Site Telegram Contact
# DO NOT INCLUDE 'https://t.me/'
# ex. 'magnetosphere' will display on-site as 'https://t.me/magnetosphere'
TGCONTACT=''

#-- Site Telegram Channel
# DO NOT INCLUDE 'https://t.me/'
# ex. 'magnetospheredotnet' will display on-site as 'https://t.me/magnetospheredotnet'
TGCHANNEL=''

### Web Server Configuration ############################################

#-- Port to serve http on (ex. '9883')
WEBPORT='9883'
`