package docs

import (
	"fmt"
	"sort"

	"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
)

type entryType string

const (
	entryTypeModule   entryType = "module"
	entryTypeResource entryType = "resource"
	entryTypeFunction entryType = "function"
)

// PackageTreeItem is a type for representing a package in a
// navigable tree format starting from the top-level/index/root
// of a package.
type PackageTreeItem struct {
	Name     string            `json:"name"`
	Type     entryType         `json:"type"`
	Link     string            `json:"link"`
	Children []PackageTreeItem `json:"children,omitempty"`
}

func generatePackageTree(rootMod modContext) ([]PackageTreeItem, error) {
	numResources := len(rootMod.resources)
	numFunctions := len(rootMod.functions)
	// +1 to add the module itself as an entry.
	size := numResources + numFunctions + 1
	packageTree := slice.Prealloc[PackageTreeItem](size)

	conflictResolver := rootMod.docGenContext.newModuleConflictResolver()

	for _, m := range rootMod.children {
		modName := m.getModuleFileName()
		displayName := modFilenameToDisplayName(modName)

		children, err := generatePackageTree(*m)
		if err != nil {
			return nil, fmt.Errorf("generating children for module %s (mod token: %s): %w", displayName, m.mod, err)
		}

		safeName := conflictResolver.getSafeName(displayName, m)
		if safeName == "" {
			continue // unresolved conflict
		}
		ti := PackageTreeItem{
			Name:     displayName,
			Type:     entryTypeModule,
			Link:     getModuleLink(safeName),
			Children: children,
		}

		packageTree = append(packageTree, ti)
	}
	sort.Slice(packageTree, func(i, j int) bool {
		return packageTree[i].Name < packageTree[j].Name
	})

	for _, r := range rootMod.resources {
		name := resourceName(r)
		safeName := conflictResolver.getSafeName(name, r)
		if safeName == "" {
			continue // unresolved conflict
		}
		ti := PackageTreeItem{
			Name:     name,
			Type:     entryTypeResource,
			Link:     getResourceLink(safeName),
			Children: nil,
		}

		packageTree = append(packageTree, ti)
	}
	sort.SliceStable(packageTree, func(i, j int) bool {
		pti, ptj := packageTree[i], packageTree[j]
		switch {
		case pti.Type != ptj.Type:
			return pti.Type == entryTypeModule && ptj.Type != entryTypeModule
		default:
			return pti.Name < ptj.Name
		}
	})

	for _, f := range rootMod.functions {
		name := tokenToName(f.Token)
		safeName := conflictResolver.getSafeName(name, f)
		if safeName == "" {
			continue // unresolved conflict
		}
		ti := PackageTreeItem{
			Name:     name,
			Type:     entryTypeFunction,
			Link:     getFunctionLink(safeName),
			Children: nil,
		}

		packageTree = append(packageTree, ti)
	}
	sort.SliceStable(packageTree, func(i, j int) bool {
		pti, ptj := packageTree[i], packageTree[j]
		switch {
		case pti.Type != ptj.Type:
			return (pti.Type == entryTypeModule || pti.Type == entryTypeResource) &&
				(ptj.Type != entryTypeModule && ptj.Type != entryTypeResource)
		default:
			return pti.Name < ptj.Name
		}
	})
	return packageTree, nil
}