package pipeline

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"slices"
	"strings"

	"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/erf"
	"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/gff"
	"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
	"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/validator"
)

type BuildResult struct {
	ModulePath string
	HAKPaths   []string
	Manifest   string
	Resources  int
	HAKAssets  int
}

type BuildManifest struct {
	ModuleHAKs []string           `json:"module_haks"`
	HAKs       []BuildManifestHAK `json:"haks"`
}

type BuildManifestHAK struct {
	Name      string   `json:"name"`
	Group     string   `json:"group"`
	Priority  int      `json:"priority"`
	MaxBytes  int64    `json:"max_bytes"`
	SizeBytes int64    `json:"size_bytes"`
	Assets    []string `json:"assets"`
}

type assetResource struct {
	Rel      string
	Resource erf.Resource
	Size     int64
}

type hakChunk struct {
	Config project.HAKConfig
	Index  int
	Name   string
	Assets []assetResource
	Size   int64
}

type ProgressFunc func(string)

func Build(p *project.Project) (BuildResult, error) {
	moduleResult, err := BuildModule(p)
	if err != nil {
		return BuildResult{}, err
	}
	hakResult, err := BuildHAKs(p)
	if err != nil {
		return BuildResult{}, err
	}
	moduleResult.HAKPaths = hakResult.HAKPaths
	moduleResult.Manifest = hakResult.Manifest
	moduleResult.HAKAssets = hakResult.HAKAssets
	return moduleResult, nil
}

func BuildModule(p *project.Project) (BuildResult, error) {
	return buildModule(p, nil)
}

func BuildModuleWithProgress(p *project.Project, progress ProgressFunc) (BuildResult, error) {
	return buildModule(p, progress)
}

func buildModule(p *project.Project, progress ProgressFunc) (BuildResult, error) {
	progressf(progress, "Validating project...")
	if err := validateForBuild(p); err != nil {
		return BuildResult{}, err
	}

	progressf(progress, "Collecting module resources...")
	moduleResources, err := collectModuleResources(p, nil)
	if err != nil {
		return BuildResult{}, err
	}

	progressf(progress, "Writing module archive...")
	outputPath := filepath.Join(p.BuildDir(), p.Config.Module.ResRef+".mod")
	output, err := os.Create(outputPath)
	if err != nil {
		return BuildResult{}, fmt.Errorf("create module archive: %w", err)
	}
	defer output.Close()

	if err := erf.Write(output, erf.New("MOD ", moduleResources)); err != nil {
		return BuildResult{}, fmt.Errorf("write module archive: %w", err)
	}

	return BuildResult{
		ModulePath: outputPath,
		Resources:  len(moduleResources),
	}, nil
}

func BuildHAKs(p *project.Project) (BuildResult, error) {
	return buildHAKs(p, nil)
}

func BuildHAKsWithProgress(p *project.Project, progress ProgressFunc) (BuildResult, error) {
	return buildHAKs(p, progress)
}

func buildHAKs(p *project.Project, progress ProgressFunc) (BuildResult, error) {
	progressf(progress, "Validating project...")
	if err := validateForBuild(p); err != nil {
		return BuildResult{}, err
	}

	progressf(progress, "Collecting asset resources...")
	assetResources, err := collectAssetResources(p)
	if err != nil {
		return BuildResult{}, err
	}
	progressf(progress, "Cleaning previous generated HAKs...")
	if err := cleanupGeneratedHAKs(p.BuildDir(), p.Config.Module.ResRef); err != nil {
		return BuildResult{}, err
	}

	result := BuildResult{HAKAssets: len(assetResources)}
	if len(assetResources) == 0 {
		return result, nil
	}

	progressf(progress, "Planning HAK chunks...")
	chunks, err := planHAKChunks(p, assetResources)
	if err != nil {
		return BuildResult{}, err
	}
	progressf(progress, "Resolving module HAK order...")
	moduleHakOrder, err := resolveModuleHAKOrder(p, chunks)
	if err != nil {
		return BuildResult{}, err
	}

	manifest := BuildManifest{
		ModuleHAKs: moduleHakOrder,
		HAKs:       make([]BuildManifestHAK, 0, len(chunks)),
	}
	for index, chunk := range chunks {
		progressf(progress, fmt.Sprintf("Writing HAK %d/%d: %s (%d assets)", index+1, len(chunks), chunk.Name, len(chunk.Assets)))
		hakPath := filepath.Join(p.BuildDir(), chunk.Name+".hak")
		hakOutput, err := os.Create(hakPath)
		if err != nil {
			return BuildResult{}, fmt.Errorf("create hak archive: %w", err)
		}
		if err := erf.Write(hakOutput, erf.New("HAK ", resourceSlice(chunk.Assets))); err != nil {
			hakOutput.Close()
			return BuildResult{}, fmt.Errorf("write hak archive: %w", err)
		}
		if err := hakOutput.Close(); err != nil {
			return BuildResult{}, fmt.Errorf("close hak archive: %w", err)
		}

		result.HAKPaths = append(result.HAKPaths, hakPath)
		assets := make([]string, 0, len(chunk.Assets))
		for _, asset := range chunk.Assets {
			assets = append(assets, asset.Rel)
		}
		manifest.HAKs = append(manifest.HAKs, BuildManifestHAK{
			Name:      chunk.Name,
			Group:     chunk.Config.Name,
			Priority:  chunk.Config.Priority,
			MaxBytes:  chunk.Config.MaxBytes,
			SizeBytes: chunk.Size,
			Assets:    assets,
		})
	}

	progressf(progress, "Writing HAK manifest...")
	manifestPath := filepath.Join(p.BuildDir(), "haks.json")
	manifestBytes, err := json.MarshalIndent(manifest, "", "  ")
	if err != nil {
		return BuildResult{}, fmt.Errorf("marshal hak manifest: %w", err)
	}
	manifestBytes = append(manifestBytes, '\n')
	if err := os.WriteFile(manifestPath, manifestBytes, 0o644); err != nil {
		return BuildResult{}, fmt.Errorf("write hak manifest: %w", err)
	}
	result.Manifest = manifestPath
	return result, nil
}

func progressf(progress ProgressFunc, message string) {
	if progress != nil {
		progress(message)
	}
}

func collectModuleResources(p *project.Project, moduleHakOrder []string) ([]erf.Resource, error) {
	var moduleResources []erf.Resource

	for _, rel := range p.Inventory.SourceFiles {
		abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
		resource, err := resourceFromJSON(abs, moduleHakOrder)
		if err != nil {
			return nil, err
		}
		moduleResources = append(moduleResources, resource)
	}

	for _, rel := range p.Inventory.ScriptFiles {
		abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
		resource, err := rawResource(abs)
		if err != nil {
			return nil, err
		}
		moduleResources = append(moduleResources, resource)
	}

	compiledScripts, err := compileReferencedScripts(p)
	if err != nil {
		return nil, err
	}
	moduleResources = append(moduleResources, compiledScripts...)

	sortResources(moduleResources)
	return moduleResources, nil
}

func compileReferencedScripts(p *project.Project) ([]erf.Resource, error) {
	referenced, err := referencedScriptResrefs(p)
	if err != nil {
		return nil, err
	}
	if len(referenced) == 0 {
		return nil, nil
	}

	scriptSources := make(map[string]string, len(p.Inventory.ScriptFiles))
	for _, rel := range p.Inventory.ScriptFiles {
		resref := strings.ToLower(strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel)))
		scriptSources[resref] = filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
	}

	compiler, err := resolveScriptCompiler(p)
	if err != nil {
		return nil, err
	}

	tmpDir, err := os.MkdirTemp("", "sow-script-compile-*")
	if err != nil {
		return nil, fmt.Errorf("create script compiler temp dir: %w", err)
	}
	defer os.RemoveAll(tmpDir)

	resources := make([]erf.Resource, 0, len(referenced))
	scriptsDir := filepath.Join(p.SourceDir(), "scripts")
	for _, resref := range referenced {
		sourcePath, ok := scriptSources[resref]
		if !ok {
			continue
		}
		outputPath := filepath.Join(tmpDir, resref+".ncs")
		cmd := exec.Command(compiler, "--dirs", scriptsDir, "-o", outputPath, sourcePath)
		cmd.Env = os.Environ()
		output, err := cmd.CombinedOutput()
		if err != nil {
			message := strings.TrimSpace(string(output))
			if message == "" {
				message = err.Error()
			}
			return nil, fmt.Errorf("compile script %s: %s", filepath.Base(sourcePath), message)
		}
		resource, err := rawResource(outputPath)
		if err != nil {
			return nil, fmt.Errorf("load compiled script %s: %w", filepath.Base(outputPath), err)
		}
		resources = append(resources, resource)
	}

	return resources, nil
}

func referencedScriptResrefs(p *project.Project) ([]string, error) {
	referenced := map[string]struct{}{}
	for _, rel := range p.Inventory.SourceFiles {
		abs := filepath.Join(p.SourceDir(), filepath.FromSlash(rel))
		document, _, _, err := validatorLoadDocument(abs)
		if err != nil {
			return nil, err
		}
		validatorWalkFields(document.Root, func(fieldLabel, value string) {
			if !validatorIsScriptField(fieldLabel) {
				return
			}
			value = strings.TrimSpace(value)
			if value == "" {
				return
			}
			referenced[strings.ToLower(value)] = struct{}{}
		})
	}

	resrefs := make([]string, 0, len(referenced))
	for resref := range referenced {
		resrefs = append(resrefs, resref)
	}
	slices.Sort(resrefs)
	return resrefs, nil
}

func resolveScriptCompiler(p *project.Project) (string, error) {
	if configured := strings.TrimSpace(os.Getenv("SOW_NWN_SCRIPT_COMPILER")); configured != "" {
		return configured, nil
	}

	candidates := []string{
		filepath.Join(p.Root, "tools", "script-compiler", "nwn_script_comp"),
		filepath.Join(p.Root, "tools", "script-compiler", "nwn_script_comp.exe"),
		filepath.Join(p.Root, "tools", "nwn_script_comp"),
		filepath.Join(p.Root, "tools", "nwn_script_comp.exe"),
	}
	if runtime.GOOS == "windows" {
		candidates = []string{
			filepath.Join(p.Root, "tools", "script-compiler", "nwn_script_comp.exe"),
			filepath.Join(p.Root, "tools", "script-compiler", "nwn_script_comp"),
			filepath.Join(p.Root, "tools", "nwn_script_comp.exe"),
			filepath.Join(p.Root, "tools", "nwn_script_comp"),
		}
	}
	for _, candidate := range candidates {
		if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
			return candidate, nil
		}
	}

	if compiler, err := exec.LookPath("nwn_script_comp"); err == nil {
		return compiler, nil
	}
	if runtime.GOOS == "windows" {
		if compiler, err := exec.LookPath("nwn_script_comp.exe"); err == nil {
			return compiler, nil
		}
	}

	return "", errors.New("script compiler not found; install nwn_script_comp into ./tools or set SOW_NWN_SCRIPT_COMPILER")
}

func validatorLoadDocument(path string) (gff.Document, string, string, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return gff.Document{}, "", "", fmt.Errorf("read %s: %w", path, err)
	}

	stem := strings.TrimSuffix(filepath.Base(path), ".json")
	extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(stem)), ".")
	if extension == "" {
		return gff.Document{}, "", "", fmt.Errorf("missing resource extension before .json: %s", path)
	}
	resref := strings.ToLower(strings.TrimSuffix(stem, "."+extension))

	var document gff.Document
	if err := json.Unmarshal(raw, &document); err != nil {
		return gff.Document{}, "", "", fmt.Errorf("parse %s: %w", path, err)
	}
	return document, resref, extension, nil
}

func validatorWalkFields(root gff.Struct, visit func(string, string)) {
	walkScriptFields(root, visit)
}

func validatorIsScriptField(label string) bool {
	return strings.HasPrefix(label, "On") || strings.HasSuffix(label, "Script") || strings.Contains(label, "Script")
}

func walkScriptFields(s gff.Struct, visit func(string, string)) {
	for _, field := range s.Fields {
		if value, ok := fieldStringValueForScripts(field.Value); ok {
			visit(field.Label, value)
		}
		switch typed := field.Value.(type) {
		case gff.Struct:
			walkScriptFields(typed, visit)
		case gff.ListValue:
			for _, item := range typed {
				walkScriptFields(item, visit)
			}
		}
	}
}

func fieldStringValueForScripts(value gff.Value) (string, bool) {
	switch typed := value.(type) {
	case gff.StringValue:
		return string(typed), true
	case gff.ResRefValue:
		return string(typed), true
	default:
		return "", false
	}
}

func collectAssetResources(p *project.Project) ([]assetResource, error) {
	var hakResources []assetResource

	for _, rel := range p.Inventory.AssetFiles {
		abs := filepath.Join(p.AssetsDir(), filepath.FromSlash(rel))
		resource, err := pathResource(abs)
		if err != nil {
			return nil, err
		}
		hakResources = append(hakResources, assetResource{
			Rel:      rel,
			Resource: resource,
			Size:     erf.ArchiveSize([]erf.Resource{resource}),
		})
	}

	slices.SortFunc(hakResources, func(a, b assetResource) int {
		return compareResourceKeys(a.Resource, b.Resource)
	})
	return hakResources, nil
}

func sortResources(resources []erf.Resource) {
	slices.SortFunc(resources, func(a, b erf.Resource) int {
		return compareResourceKeys(a, b)
	})
}

func compareResourceKeys(a, b erf.Resource) int {
	if cmp := strings.Compare(a.Name, b.Name); cmp != 0 {
		return cmp
	}
	if a.Type < b.Type {
		return -1
	}
	if a.Type > b.Type {
		return 1
	}
	return 0
}

func resourceFromJSON(path string, moduleHakOrder []string) (erf.Resource, error) {
	name, extension, err := splitSourceName(path)
	if err != nil {
		return erf.Resource{}, err
	}

	resourceType, ok := erf.ResourceTypeForExtension(extension)
	if !ok {
		return erf.Resource{}, fmt.Errorf("unsupported source extension %q", extension)
	}

	raw, err := os.ReadFile(path)
	if err != nil {
		return erf.Resource{}, fmt.Errorf("read %s: %w", path, err)
	}

	var document gff.Document
	if err := json.Unmarshal(raw, &document); err != nil {
		return erf.Resource{}, fmt.Errorf("parse gff json %s: %w", path, err)
	}
	if document.FileType == "" {
		document.FileType = strings.ToUpper(strings.TrimPrefix(extension, "."))
	}
	if document.FileVersion == "" {
		document.FileVersion = "V3.2"
	}
	if extension == ".ifo" && name == "module" && len(moduleHakOrder) > 0 {
		setModuleHAKList(&document, moduleHakOrder)
	}

	var buf bytes.Buffer
	if err := gff.Write(&buf, document); err != nil {
		return erf.Resource{}, fmt.Errorf("encode gff %s: %w", path, err)
	}

	return erf.Resource{
		Name: strings.ToLower(name),
		Type: resourceType,
		Data: buf.Bytes(),
	}, nil
}

func pathResource(path string) (erf.Resource, error) {
	extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
	resourceType, ok := erf.ResourceTypeForExtension(extension)
	if !ok {
		return erf.Resource{}, fmt.Errorf("unsupported resource extension %q", filepath.Ext(path))
	}
	name := strings.ToLower(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
	info, err := os.Stat(path)
	if err != nil {
		return erf.Resource{}, fmt.Errorf("stat %s: %w", path, err)
	}
	return erf.Resource{
		Name:       name,
		Type:       resourceType,
		SourcePath: path,
		Size:       info.Size(),
	}, nil
}

func rawResource(path string) (erf.Resource, error) {
	extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(path)), ".")
	resourceType, ok := erf.ResourceTypeForExtension(extension)
	if !ok {
		return erf.Resource{}, fmt.Errorf("unsupported resource extension %q", filepath.Ext(path))
	}
	name := strings.ToLower(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
	data, err := os.ReadFile(path)
	if err != nil {
		return erf.Resource{}, fmt.Errorf("read %s: %w", path, err)
	}
	return erf.Resource{
		Name: strings.ToLower(name),
		Type: resourceType,
		Data: data,
	}, nil
}

func splitSourceName(path string) (string, string, error) {
	base := filepath.Base(path)
	if !strings.HasSuffix(base, ".json") {
		return "", "", fmt.Errorf("expected .json source file: %s", path)
	}
	stem := strings.TrimSuffix(base, ".json")
	extension := filepath.Ext(stem)
	if extension == "" {
		return "", "", fmt.Errorf("source file must include target resource extension before .json: %s", path)
	}
	return strings.ToLower(strings.TrimSuffix(stem, extension)), strings.ToLower(extension), nil
}

func planHAKChunks(p *project.Project, assets []assetResource) ([]hakChunk, error) {
	if len(assets) == 0 {
		return nil, nil
	}
	if len(p.Config.HAKs) == 0 {
		chunk := hakChunk{
			Config: project.HAKConfig{
				Name:     p.Config.Module.ResRef,
				Priority: 1,
			},
			Index:  1,
			Name:   p.Config.Module.ResRef,
			Size:   erf.ArchiveSize(resourceSlice(assets)),
			Assets: assets,
		}
		return []hakChunk{chunk}, nil
	}

	configs := make([]project.HAKConfig, len(p.Config.HAKs))
	copy(configs, p.Config.HAKs)
	slices.SortFunc(configs, func(a, b project.HAKConfig) int {
		if a.Priority != b.Priority {
			if a.Priority < b.Priority {
				return -1
			}
			return 1
		}
		return strings.Compare(a.Name, b.Name)
	})

	grouped := make([][]assetResource, len(configs))
	for _, asset := range assets {
		matched := false
		for index, cfg := range configs {
			if matchesAnyPattern(asset.Rel, cfg.Include) {
				grouped[index] = append(grouped[index], asset)
				matched = true
				break
			}
		}
		if !matched {
			return nil, fmt.Errorf("asset %s does not match any hak include pattern", asset.Rel)
		}
	}

	var chunks []hakChunk
	for index, cfg := range configs {
		groupAssets := grouped[index]
		if len(groupAssets) == 0 {
			continue
		}

		groupChunks, err := splitHAKGroup(cfg, groupAssets)
		if err != nil {
			return nil, err
		}
		chunks = append(chunks, groupChunks...)
	}
	return chunks, nil
}

func splitHAKGroup(cfg project.HAKConfig, assets []assetResource) ([]hakChunk, error) {
	maxBytes := cfg.MaxBytes
	if maxBytes == 0 {
		chunk := hakChunk{
			Config: cfg,
			Index:  1,
			Name:   cfg.Name,
			Assets: assets,
			Size:   erf.ArchiveSize(resourceSlice(assets)),
		}
		if err := ensureUniqueChunkResources(chunk); err != nil {
			return nil, err
		}
		return []hakChunk{chunk}, nil
	}

	var chunks []hakChunk
	current := hakChunk{Config: cfg, Index: 1}
	for _, asset := range assets {
		candidateAssets := append(append([]assetResource{}, current.Assets...), asset)
		candidateResources := resourceSlice(candidateAssets)
		candidateSize := erf.ArchiveSize(candidateResources)

		if len(current.Assets) == 0 {
			if candidateSize > maxBytes {
				return nil, fmt.Errorf("asset %s alone exceeds max_bytes for hak %s", asset.Rel, cfg.Name)
			}
			current.Assets = candidateAssets
			current.Size = candidateSize
			continue
		}

		if candidateSize <= maxBytes {
			current.Assets = candidateAssets
			current.Size = candidateSize
			continue
		}

		if !cfg.Split {
			return nil, fmt.Errorf("hak %s exceeds max_bytes and split is disabled", cfg.Name)
		}

		current.Name = chunkName(cfg.Name, current.Index, true)
		if err := ensureUniqueChunkResources(current); err != nil {
			return nil, err
		}
		chunks = append(chunks, current)

		current = hakChunk{
			Config: cfg,
			Index:  current.Index + 1,
			Assets: []assetResource{asset},
			Size:   erf.ArchiveSize([]erf.Resource{asset.Resource}),
		}
		if current.Size > maxBytes {
			return nil, fmt.Errorf("asset %s alone exceeds max_bytes for hak %s", asset.Rel, cfg.Name)
		}
	}

	if len(current.Assets) > 0 {
		current.Name = chunkName(cfg.Name, current.Index, cfg.Split || len(chunks) > 0)
		if err := ensureUniqueChunkResources(current); err != nil {
			return nil, err
		}
		chunks = append(chunks, current)
	}
	return chunks, nil
}

func chunkName(base string, index int, split bool) string {
	if !split {
		return base
	}
	return fmt.Sprintf("%s_%02d", base, index)
}

func resourceSlice(assets []assetResource) []erf.Resource {
	out := make([]erf.Resource, 0, len(assets))
	for _, asset := range assets {
		out = append(out, asset.Resource)
	}
	return out
}

func matchesAnyPattern(path string, patterns []string) bool {
	for _, pattern := range patterns {
		if matchPathPattern(filepath.ToSlash(path), filepath.ToSlash(pattern)) {
			return true
		}
	}
	return false
}

func matchPathPattern(path, pattern string) bool {
	pathSegs := strings.Split(strings.Trim(path, "/"), "/")
	patternSegs := strings.Split(strings.Trim(pattern, "/"), "/")
	return matchSegments(pathSegs, patternSegs)
}

func cleanupGeneratedHAKs(buildDir, moduleResRef string) error {
	paths, err := manifestHAKPaths(buildDir)
	if err != nil {
		return err
	}
	for _, path := range paths {
		if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
			return fmt.Errorf("remove previous generated hak %s: %w", path, err)
		}
	}

	legacy := filepath.Join(buildDir, moduleResRef+".hak")
	if err := os.Remove(legacy); err != nil && !errors.Is(err, os.ErrNotExist) {
		return fmt.Errorf("remove legacy generated hak %s: %w", legacy, err)
	}

	manifest := filepath.Join(buildDir, "haks.json")
	if err := os.Remove(manifest); err != nil && !errors.Is(err, os.ErrNotExist) {
		return fmt.Errorf("remove previous hak manifest: %w", err)
	}
	return nil
}

func resolveModuleHAKOrder(p *project.Project, chunks []hakChunk) ([]string, error) {
	if len(p.Config.Module.HAKOrder) == 0 {
		return nil, nil
	}

	byGroup := map[string][]string{}
	for _, chunk := range chunks {
		byGroup[chunk.Config.Name] = append(byGroup[chunk.Config.Name], chunk.Name)
	}

	seen := map[string]struct{}{}
	var ordered []string
	for _, entry := range p.Config.Module.HAKOrder {
		if strings.HasPrefix(entry, "group:") {
			groupName := strings.TrimPrefix(entry, "group:")
			names, ok := byGroup[groupName]
			if !ok {
				continue
			}
			for _, name := range names {
				if _, exists := seen[name]; exists {
					continue
				}
				ordered = append(ordered, name)
				seen[name] = struct{}{}
			}
			continue
		}

		if _, exists := seen[entry]; exists {
			continue
		}
		ordered = append(ordered, entry)
		seen[entry] = struct{}{}
	}

	return ordered, nil
}

func setModuleHAKList(document *gff.Document, hakNames []string) {
	list := make(gff.ListValue, 0, len(hakNames))
	for _, name := range hakNames {
		list = append(list, gff.Struct{
			Type: 8,
			Fields: []gff.Field{
				gff.NewField("Mod_Hak", gff.StringValue(name)),
			},
		})
	}

	for index, field := range document.Root.Fields {
		if field.Label == "Mod_HakList" {
			document.Root.Fields[index] = gff.NewField("Mod_HakList", list)
			return
		}
	}

	document.Root.Fields = append(document.Root.Fields, gff.NewField("Mod_HakList", list))
}

func validateForBuild(p *project.Project) error {
	report := validator.ValidateProject(p)
	if report.HasErrors() {
		return fmt.Errorf("validation failed with %d error(s)", len(report.Diagnostics))
	}
	return nil
}

func matchSegments(pathSegs, patternSegs []string) bool {
	if len(patternSegs) == 0 {
		return len(pathSegs) == 0
	}
	if patternSegs[0] == "**" {
		if matchSegments(pathSegs, patternSegs[1:]) {
			return true
		}
		if len(pathSegs) > 0 {
			return matchSegments(pathSegs[1:], patternSegs)
		}
		return false
	}
	if len(pathSegs) == 0 {
		return false
	}
	ok, err := filepath.Match(patternSegs[0], pathSegs[0])
	if err != nil || !ok {
		return false
	}
	return matchSegments(pathSegs[1:], patternSegs[1:])
}
