package parser

import (
	"esperframework/options"
	"esperframework/server"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/karrick/godirwalk"
)

var templatesMap = make(map[string]TemplateData)

func Parse(opts *options.Options, actionMeta *options.ActionMeta) map[string]TemplateData {
	err := godirwalk.Walk((*opts).AppPath, &godirwalk.Options{
		Callback: handleAppFiles,
		Unsorted: true,
	})
	if err != nil {
		log.Fatalf("\nError: Walk dir function failed with error\n%s\n", err)
	}

	wd, err := os.Getwd()
	if err != nil {
		log.Fatalf("\nFailed to get working directory while parsing app\n%s\n", err)
	}

	if _, err := os.Stat(filepath.Join(opts.AppPath, "__not-found.html")); os.IsNotExist(err) {
		handleAppFiles(filepath.Join(wd, "../", "src", "templates", "__not-found.html"), &godirwalk.Dirent{})
	}

	return templatesMap
}

func handleAppFiles(osPath string, de *godirwalk.Dirent) error {
	filename := de.Name()

	if de.Name() == "" {
		filename = filepath.Base(osPath)
	}

	isLayout := false

	fi, err := os.Stat(osPath)
	if err != nil {
		log.Fatalf("\nFailed to stat file at %s\n%s\n", osPath, err)
	}

	// If the first character is an underscore, ignore it
	// Unless the last character is an underscore. That would denote it is a dynamic route.
	if filename[0] == '_' {
		// len("__layout") == 8
		if len(filename) >= 8 && filename[:8] == "__layout" {
			isLayout = true

			// len("__not-found") == 11
		} else if len(filename) >= 11 && filename[:11] == "__not-found" {
			// Noop - Don't skip
		} else if fi.IsDir() && filename[len(filename)-1] == '_' {
			return nil
		} else {
			return godirwalk.SkipThis
		}
	}

	if fi.IsDir() {
		return nil
	}

	opts := options.Get()

	if filepath.Ext(filepath.Base(filename)) != (*opts).Ext {
		return nil
	}

	file, err := os.ReadFile(osPath)
	if err != nil {
		log.Fatalf("\nEncountered error while walking components. Failed to read file\n%s\n", err)
	}

	l := &TBLexer{
		options: opts,
		name:    filename,
		input:   file,
		state:   extractMarkup,
	}

	if isLayout {
		(*l).templateDataBuf.templateKind = Layout
	} else {
		(*l).templateDataBuf.templateKind = Page
	}

	td := New(l, newArgs{filename, osPath, true, TemplateKind[l.templateDataBuf.templateKind]})

	if td.MetaData.MetaDataPath != "" {
		templatesMap[td.MetaData.MetaDataPath] = td
	}

	parseDependentTemplates(opts, td, templatesMap)

	// TODO: Add plugin system for server adapters
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	actionMeta := options.GetActionMeta()

	if filepath.Ext(filename) == ".go" {
		rel := handlePageRenames(opts, newArgs{filename, osPath, true, TemplateKind[ServerRoute]}, &TemplateData{})

		outPathNoExt := filepath.Join(opts.CachePath, rel)
		metaDataPath := relativeToBuildCacheRoot(opts.CachePath, outPathNoExt) + opts.Ext

		if actionMeta.Ext == "any" || actionMeta.Ext == ".go" || actionMeta.Ext == opts.Ext {
			name := strings.Replace(strings.Replace(filename, "[", "", 1), "]", "", 1)
			server.BuildRoute(opts, osPath, name, metaDataPath, server.GO)
		}
		return nil
	}

	// Build server routes for pages
	if !strings.Contains(filename, "__app") && !strings.Contains(filename, "__layout") && !strings.Contains(filename, "__not-found") {
		rel := handlePageRenames(opts, newArgs{filename, osPath, true, TemplateKind[(*l).templateDataBuf.templateKind]}, &TemplateData{})

		outPathNoExt := filepath.Join(opts.CachePath, rel)
		metaDataPath := relativeToBuildCacheRoot(opts.CachePath, outPathNoExt) + opts.Ext

		server.BuildRoute(opts, osPath, filename, metaDataPath, server.HTML)
	}
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	return nil
}

func parseDependentTemplates(opts *options.Options, parent TemplateData, templatesMap map[string]TemplateData) {
	for _, comp := range parent.Head {
		l := &TBLexer{
			options: opts,
			state:   extractMarkup,
		}

		if comp.Kind.Value == "import" {
			osPath := filepath.Join(l.options.Root, strings.Split(parent.MetaData.Path, filepath.Base(parent.MetaData.Path))[0], comp.Path.RelPath)

			if _, err := os.Stat(osPath); os.IsNotExist(err) {
				log.Fatalf("\nComponent file does not exist at path %s\n%s\n", osPath, err)
			}

			file, err := os.ReadFile(osPath)
			if err != nil {
				log.Fatalf("\nFailed to read file while parsing component blocks\n%s\n", err)
			}

			(*l).name = comp.Attrs["name"]
			(*l).input = file
			(*l).templateDataBuf.templateKind = Component

			td := New(l, newArgs{"", osPath, false, TemplateKind[Component]})

			if td.MetaData.MetaDataPath == "" {
				continue
			}

			templatesMap[td.MetaData.MetaDataPath] = td

			parseDependentTemplates(l.options, td, templatesMap)
		}
	}
}
