package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"go/parser"
	"go/token"
	"io/fs"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/gohugoio/hugo/parser/pageparser"
	"golang.org/x/xerrors"

	"github.com/coder/coder/v2/codersdk"
)

const (
	examplesDir = "examples"
	examplesSrc = "examples.go"
)

func main() {
	if err := run(); err != nil {
		panic(err)
	}
}

func run() error {
	fset := token.NewFileSet()
	src, err := parser.ParseFile(fset, filepath.Join(examplesDir, examplesSrc), nil, parser.ParseComments)
	if err != nil {
		return err
	}

	var paths []string
	for _, comment := range src.Comments {
		for _, line := range comment.List {
			if s, ok := parseEmbedTag(line.Text); ok && !strings.HasSuffix(s, ".json") {
				paths = append(paths, s)
			}
		}
	}

	var examples []codersdk.TemplateExample
	files := os.DirFS(examplesDir)
	for _, name := range paths {
		dir, err := fs.Stat(files, name)
		if err != nil {
			return err
		}
		if !dir.IsDir() {
			continue
		}
		exampleID := dir.Name()
		// Each one of these is a example!
		readme, err := fs.ReadFile(files, path.Join(name, "README.md"))
		if err != nil {
			return xerrors.Errorf("example %q does not contain README.md", exampleID)
		}

		frontMatter, err := pageparser.ParseFrontMatterAndContent(bytes.NewReader(readme))
		if err != nil {
			return xerrors.Errorf("parse example %q front matter: %w", exampleID, err)
		}

		nameRaw, exists := frontMatter.FrontMatter["display_name"]
		if !exists {
			return xerrors.Errorf("example %q front matter does not contain name", exampleID)
		}

		name, valid := nameRaw.(string)
		if !valid {
			return xerrors.Errorf("example %q name isn't a string", exampleID)
		}

		descriptionRaw, exists := frontMatter.FrontMatter["description"]
		if !exists {
			return xerrors.Errorf("example %q front matter does not contain name", exampleID)
		}

		description, valid := descriptionRaw.(string)
		if !valid {
			return xerrors.Errorf("example %q description isn't a string", exampleID)
		}

		tags := []string{}
		tagsRaw, exists := frontMatter.FrontMatter["tags"]
		if exists {
			tagsI, valid := tagsRaw.([]interface{})
			if !valid {
				return xerrors.Errorf("example %q tags isn't a slice: type %T", exampleID, tagsRaw)
			}
			for _, tagI := range tagsI {
				tag, valid := tagI.(string)
				if !valid {
					return xerrors.Errorf("example %q tag isn't a string: type %T", exampleID, tagI)
				}
				tags = append(tags, tag)
			}
		}

		var icon string
		iconRaw, exists := frontMatter.FrontMatter["icon"]
		if exists {
			icon, valid = iconRaw.(string)
			if !valid {
				return xerrors.Errorf("example %q icon isn't a string", exampleID)
			}
			icon, err = filepath.Rel("../site/static/", filepath.Join(examplesDir, name, icon))
			if err != nil {
				return xerrors.Errorf("example %q icon is not in site/static: %w", exampleID, err)
			}
			// The FE needs a static path!
			icon = "/" + icon
		}

		examples = append(examples, codersdk.TemplateExample{
			ID:          exampleID,
			Name:        name,
			Description: description,
			Icon:        icon,
			Tags:        tags,
			Markdown:    string(frontMatter.Content),

			// URL is set by examples/examples.go.
		})
	}

	w := os.Stdout

	_, err = fmt.Fprint(w, "// Code generated by examplegen. DO NOT EDIT.\n")
	if err != nil {
		return err
	}

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	return enc.Encode(examples)
}

func parseEmbedTag(s string) (string, bool) {
	if !strings.HasPrefix(s, "//go:embed") {
		return "", false
	}
	return strings.TrimSpace(strings.TrimPrefix(s, "//go:embed")), true
}
