package bp2build

import (
	"android/soong/android"
	"reflect"
	"sort"
	"strings"

	"github.com/google/blueprint/proptools"
)

type BazelFile struct {
	Dir      string
	Basename string
	Contents string
}

func CreateBazelFiles(
	ruleShims map[string]RuleShim,
	buildToTargets map[string]BazelTargets,
	mode CodegenMode) []BazelFile {

	var files []BazelFile

	if mode == QueryView {
		// Write top level WORKSPACE.
		files = append(files, newFile("", "WORKSPACE", ""))

		// Used to denote that the top level directory is a package.
		files = append(files, newFile("", GeneratedBuildFileName, ""))

		files = append(files, newFile(bazelRulesSubDir, GeneratedBuildFileName, ""))

		// These files are only used for queryview.
		files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl))

		for bzlFileName, ruleShim := range ruleShims {
			files = append(files, newFile(bazelRulesSubDir, bzlFileName+".bzl", ruleShim.content))
		}
		files = append(files, newFile(bazelRulesSubDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims)))
	}

	files = append(files, createBuildFiles(buildToTargets, mode)...)

	return files
}

func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
	files := make([]BazelFile, 0, len(buildToTargets))
	for _, dir := range android.SortedStringKeys(buildToTargets) {
		targets := buildToTargets[dir]
		sort.Slice(targets, func(i, j int) bool {
			// this will cover all bp2build generated targets
			if targets[i].name < targets[j].name {
				return true
			}
			// give a strict ordering to content from hand-crafted targets
			return targets[i].content < targets[j].content
		})
		content := soongModuleLoad
		if mode == Bp2Build {
			content = `# This file was automatically generated by bp2build for the Bazel migration project.
# Feel free to edit or test it, but do *not* check it into your version control system.`
			content += "\n\n"
			content += "package(default_visibility = [\"//visibility:public\"])"
			content += "\n\n"
			content += targets.LoadStatements()
		}
		if content != "" {
			// If there are load statements, add a couple of newlines.
			content += "\n\n"
		}
		content += targets.String()
		files = append(files, newFile(dir, GeneratedBuildFileName, content))
	}
	return files
}

func newFile(dir, basename, content string) BazelFile {
	return BazelFile{
		Dir:      dir,
		Basename: basename,
		Contents: content,
	}
}

const (
	bazelRulesSubDir = "build/bazel/queryview_rules"

	// additional files:
	//  * workspace file
	//  * base BUILD file
	//  * rules BUILD file
	//  * rules providers.bzl file
	//  * rules soong_module.bzl file
	numAdditionalFiles = 5
)

var (
	// Certain module property names are blocklisted/ignored here, for the reasons commented.
	ignoredPropNames = map[string]bool{
		"name":       true, // redundant, since this is explicitly generated for every target
		"from":       true, // reserved keyword
		"in":         true, // reserved keyword
		"size":       true, // reserved for tests
		"arch":       true, // interface prop type is not supported yet.
		"multilib":   true, // interface prop type is not supported yet.
		"target":     true, // interface prop type is not supported yet.
		"visibility": true, // Bazel has native visibility semantics. Handle later.
		"features":   true, // There is already a built-in attribute 'features' which cannot be overridden.
	}
)

func shouldGenerateAttribute(prop string) bool {
	return !ignoredPropNames[prop]
}

func shouldSkipStructField(field reflect.StructField) bool {
	if field.PkgPath != "" {
		// Skip unexported fields. Some properties are
		// internal to Soong only, and these fields do not have PkgPath.
		return true
	}
	// fields with tag `blueprint:"mutated"` are exported to enable modification in mutators, etc
	// but cannot be set in a .bp file
	if proptools.HasTag(field, "blueprint", "mutated") {
		return true
	}
	return false
}

// FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as
// testonly = True, forcing other rules that depend on _test rules to also be
// marked as testonly = True. This semantic constraint is not present in Soong.
// To work around, rename "*_test" rules to "*_test_".
func canonicalizeModuleType(moduleName string) string {
	if strings.HasSuffix(moduleName, "_test") {
		return moduleName + "_"
	}

	return moduleName
}
