package server

import (
	"fes/modules/config"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	lua "github.com/yuin/gopher-lua"
)

/* this is the request data we pass over the bus to the application, via the fes.bus interface */
type reqData struct {
	path   string
	params map[string]string
}

/* returns a string of rendered html */
func render(luapath string, requestData reqData) ([]byte, error) {
	L := lua.NewState()
	defer L.Close()

	if lib, err := fs.ReadDir(config.Lib, "lib"); err == nil {
		for _, de := range lib {
			if de.IsDir() || !strings.HasSuffix(de.Name(), ".lua") {
				continue
			}
			path := filepath.Join("lib", de.Name())
			if data, err := config.Lib.ReadFile(path); err != nil {
				continue
			} else {
				L.DoString(string(data))
			}
		}
	}

	preloadLuaModule := func(name, path string) {
		L.PreloadModule(name, func(L *lua.LState) int {
			fileData, err := config.Lib.ReadFile(path)
			if err != nil {
				panic(err)
			}
			if err := L.DoString(string(fileData)); err != nil {
				panic(err)
			}
			L.Push(L.Get(-1))
			return 1
		})
	}

	preloadLuaModule("lib.std", "lib/std.lua")
	preloadLuaModule("lib.symbol", "lib/symbol.lua")
	preloadLuaModule("lib.util", "lib/util.lua")

	L.PreloadModule("fes", func(L *lua.LState) int {
		mod := L.NewTable()
		libModules := []string{}
		if ents, err := fs.ReadDir(config.Lib, "lib"); err == nil {
			for _, e := range ents {
				if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") {
					continue
				}
				libModules = append(libModules, strings.TrimSuffix(e.Name(), ".lua"))
			}
		}
		for _, modName := range libModules {
			path := filepath.Join("lib", modName+".lua")
			fileData, err := config.Lib.ReadFile(path)
			if err != nil {
				continue
			}
			if err := L.DoString(string(fileData)); err != nil {
				continue
			}
			val := L.Get(-1)
			L.Pop(1)
			tbl, ok := val.(*lua.LTable)
			if !ok || tbl == nil {
				tbl = L.NewTable()
			}
			if modName == "fes" {
				tbl.ForEach(func(k, v lua.LValue) { mod.RawSet(k, v) })
			} else {
				mod.RawSetString(modName, tbl)
			}
		}

		mod.RawSetString("app", func() *lua.LTable {
			app := L.NewTable()
			includeDir := "include"

			includes, err := os.ReadDir(includeDir)
			if err != nil {
				return app // load no includes
			}

			for _, de := range includes {
				if de.IsDir() || !strings.HasSuffix(de.Name(), ".lua") {
					continue
				}

				base := strings.TrimSuffix(de.Name(), ".lua")
				path := filepath.Join(includeDir, de.Name())

				if _, err := os.Stat(path); err != nil {
					continue
				} else if err := L.DoFile(path); err != nil {
					continue
				}

				val := L.Get(-1)
				L.Pop(1)
				tbl, ok := val.(*lua.LTable)
				if !ok || tbl == nil {
					tbl = L.NewTable()
				}
				app.RawSetString(base, tbl)
			}

			return app
		}())

		bus := L.NewTable()
		bus.RawSetString("url", lua.LString(requestData.path))
		params := L.NewTable()
		for k, v := range requestData.params {
			params.RawSetString(k, lua.LString(v))
		}
		bus.RawSetString("params", params)
		mod.RawSetString("bus", bus)

		mod.RawSetString("markdown_to_html", L.NewFunction(func(L *lua.LState) int {
			L.Push(lua.LString(markdownToHTML(L.ToString(1))))
			return 1
		}))

		L.Push(mod)
		return 1
	})

	if err := L.DoFile(luapath); err != nil {
		return []byte(""), err
	}

	if L.GetTop() == 0 {
		return []byte(""), nil
	}

	L.SetGlobal("__fes_result", L.Get(-1))
	if err := L.DoString("return tostring(__fes_result)"); err != nil {
		L.GetGlobal("__fes_result")
		if s := L.ToString(-1); s != "" {
			return []byte(s), nil
		}
		return []byte(""), nil
	}

	if s := L.ToString(-1); s != "" {
		return []byte(s), nil
	}

	return []byte(""), nil
}
