package gen

import (
	"fmt"
	"math/rand"
	"os"
	"path/filepath"
	"strings"

	"git.clearsky.net.au/cody/gex.git/gen/domquery"
	"git.clearsky.net.au/cody/gex.git/gen/img"
	"git.clearsky.net.au/cody/gex.git/gen/layout"
	"git.clearsky.net.au/cody/gex.git/gen/partial"
	"git.clearsky.net.au/cody/gex.git/utils"
)

var appRuleList cssRuleList

//var appJsStr string

func GeneratePages(dir string) error {
	if err := processTemplate(dir); err != nil {
		utils.Err(err)
		return err
	}

	// Process Partials
	if err := processPartials(dir); err != nil {
		utils.Err(err)
		return err
	}

	// Build the layout & blocks
	if err := processLayout(dir); err != nil {
		utils.Err(err)
		return err
	}

	// Process IMG on the generated files
	if err := ProcessImg(dir); err != nil {
		utils.Err(err)
		return err
	}

	// Clean block tags
	if err := cleanBlocks(dir); err != nil {
		utils.Err(err)
		return err
	}

	appRuleList = mergeMediaRules(appRuleList)
	appRuleList = orderRules(appRuleList)
	cssStr := rulesToCSSstr(appRuleList)

	os.WriteFile("app/pub/css/app.gen.css", []byte(cssStr), 0655)

	return nil
}

func processTemplate(dir string) error {
	processFile := func(fp string) error {
		if strings.Contains(fp, ".gen.tpl") {
			return nil
		}

		if !strings.Contains(fp, ".tpl") {
			return nil
		}

		// get the html string
		data, err := os.ReadFile(fp)
		if err != nil {
			utils.Err(err)
			return err
		}
		htmlStr := string(data)

		// add the scope class to attributes
		className := genRandName(8)
		htmlStr = AddClassAttr(htmlStr, className)

		// get & strip the css rules
		cssStr := ExtractCSS(&htmlStr)

		ruleList := toRuleList(cssStr)

		// scope the rules with css class
		ruleList = scopeSelectors(ruleList, className)

		appRuleList = append(appRuleList, ruleList...)

		// Process JS on the generated file
		htmlStr = ProcessJs(htmlStr, className)

		// write the .gen.html file as
		newPath := strings.Replace(fp, ".tpl", ".gen.tpl", 1)
		return os.WriteFile(newPath, []byte(htmlStr), 0655)

	}

	walk := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			utils.Err(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		return processFile(path)

	}
	return filepath.Walk(dir, walk)
}

func processPartials(dir string) error {
	processFile := func(fp string) error {
		if !strings.Contains(fp, ".gen.tpl") {
			return nil
		}

		data, err := os.ReadFile(fp)
		if err != nil {
			utils.Err(err)
			return err
		}
		htmlStr := string(data)

		htmlStr, err = partial.ProcessHTML(htmlStr, fp)
		if err != nil {
			utils.Err(err)
			return err
		}

		return os.WriteFile(fp, []byte(htmlStr), 0655)
	}

	walk := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			utils.Err(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		return processFile(path)

	}
	err := filepath.Walk(dir, walk)
	if err != nil {
		utils.Err(err)
		return err
	}
	return nil
}

func processLayout(dir string) error {
	processFile := func(fp string) error {
		if !strings.Contains(fp, ".gen.tpl") {
			return nil
		}

		data, err := os.ReadFile(fp)
		if err != nil {
			utils.Err(err)
			return err
		}
		htmlStr := string(data)

		htmlStr, err = layout.ProcessHTML(htmlStr, fp)
		if err != nil {
			utils.Err(err)
			return err
		}

		os.WriteFile(fp, []byte(htmlStr), 0655)
		return nil
	}

	walk := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			utils.Err(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		return processFile(path)
	}

	filepath.Walk(dir, walk)

	return nil
}

func ProcessJs(htmlStr string, className string) string {

	dom := domquery.LoadHTML(htmlStr)

	scripts := dom.QuerySelectorAll("script")

	i := 0
	jsTag := ""
	for _, script := range scripts {
		scriptName := className + fmt.Sprintf("%d", i)

		if script.GetAttribute("inline") != "" {
			continue
		}

		if script.GetAttribute("src") != "" {
			continue
		}

		jsStr := script.InnerHTML()

		funcStr := `if (typeof ` + scriptName + ` === 'undefined') {`
		funcStr += `window.` + scriptName + ` = () => {`
		funcStr += jsStr
		funcStr += `
				if(typeof unload == "undefined")
	                return

	            const watchInt = setInterval(() => {
	                    let watch = document.querySelector(".` + className + `");
	                    if (!watch) {
	                        unload();
	                        clearInterval(watchInt);
	                    }
	            },100);
	        }
		}
			` + scriptName + `()
		`

		srcPath := "app/pub/gen/" + scriptName + ".gen.js"
		os.WriteFile(srcPath, []byte(funcStr), 0655)

		jsTag += `<script defer src="/` + srcPath + `"></script>`

		if script.GetAttribute("type") == "module" {
			jsTag = `<script type="module" defer src="/` + srcPath + `"></script>`
		}

		script.Remove()

		i++
	}
	insDiv := dom.QuerySelector("." + className)
	insDiv.SetInnerHTML(jsTag + insDiv.InnerHTML())

	htmlStr = dom.InnerHTML()

	return htmlStr
}

func ProcessImg(dir string) error {

	processFile := func(fp string) error {
		htmlDir := filepath.Dir(fp)

		if !strings.Contains(fp, ".gen.tpl") {
			return nil
		}

		// get the html string
		data, err := os.ReadFile(fp)
		if err != nil {
			utils.Err(err)
			return err
		}
		htmlStr := string(data)

		document := domquery.LoadHTML(htmlStr)

		imgTags := document.QuerySelectorAll("img")

		for _, tag := range imgTags {
			if !(tag.HasAttribute("webp") && tag.HasAttribute("src")) {
				continue
			}

			img.Process(htmlDir, tag)
		}

		htmlStr = document.InnerHTML()
		os.WriteFile(fp, []byte(htmlStr), 0655)
		return nil
	}

	walk := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			utils.Err(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		return processFile(path)

	}

	return filepath.Walk(dir, walk)
}

func cleanBlocks(dir string) error {
	processFile := func(fp string) error {
		if !strings.Contains(fp, ".gen.tpl") {
			return nil
		}

		// get the html string
		data, err := os.ReadFile(fp)
		if err != nil {
			utils.Err(err)
			return err
		}
		htmlStr := string(data)
		dom := domquery.LoadHTML(htmlStr)

		var clean func()
		clean = func() {
			block := dom.QuerySelector("block")
			if block.TagName() != "block" {
				return
			}

			block.SetOuterHTML(block.InnerHTML())
			clean()
			return

		}
		clean()

		htmlStr = dom.InnerHTML()
		os.WriteFile(fp, []byte(htmlStr), 0655)
		return nil
	}

	walk := func(dir string, info os.FileInfo, err error) error {
		if err != nil {
			utils.Err(err)
			return err
		}

		if info.IsDir() {
			return nil
		}

		return processFile(dir)
	}
	return filepath.Walk(dir, walk)
}

// generate a random string to use as a class name
func genRandName(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	b := make([]byte, length)
	for i := range b {
		b[i] = charset[rand.Intn(len(charset))]
	}
	return string(b)
}