package topdata

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

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

type Generated2DAAsset struct {
	Rel        string
	SourcePath string
}

func BuildGenerated2DAAssets(p *project.Project, progress func(string)) ([]Generated2DAAsset, error) {
	if progress == nil {
		progress = func(string) {}
	}
	configs := p.EffectiveConfig().Generated.TopData2DA
	if len(configs) == 0 {
		return nil, nil
	}

	results := make([]Generated2DAAsset, 0)
	for _, cfg := range configs {
		progress(fmt.Sprintf("Building generated 2DA assets for %s...", cfg.ID))
		generated, err := buildGenerated2DAAssetGroup(p, cfg, progress)
		if err != nil {
			return nil, err
		}
		results = append(results, generated...)
	}
	return results, nil
}

func buildGenerated2DAAssetGroup(p *project.Project, cfg project.GeneratedTopData2DAConfig, progress func(string)) ([]Generated2DAAsset, error) {
	sourceDir := filepath.Join(p.Root, filepath.FromSlash(strings.TrimSpace(cfg.Source)))
	dataDir := filepath.Join(sourceDir, "data")
	outputDir := filepath.Join(p.Root, filepath.FromSlash(strings.TrimSpace(cfg.Output)))

	datasets, err := discoverNativeDatasets(dataDir)
	if err != nil {
		return nil, fmt.Errorf("discover generated topdata 2DA datasets %s: %w", cfg.ID, err)
	}
	selected := make([]nativeDataset, 0, len(datasets))
	for _, dataset := range datasets {
		if generatedDatasetIncluded(dataset.Name, cfg.IncludeDatasets) {
			selected = append(selected, dataset)
		}
	}
	if len(selected) == 0 {
		return nil, fmt.Errorf("generated topdata 2DA config %s matched no datasets under %s", cfg.ID, dataDir)
	}

	collected := make([]nativeCollectedDataset, 0, len(selected))
	for _, dataset := range selected {
		collectedDataset, err := collectNativeDataset(dataset)
		if err != nil {
			return nil, err
		}
		collected = append(collected, collectedDataset)
	}

	consumer := cfg.Autogen
	consumer.LocalOverrideRoot = p.AssetsDir()
	collected, err = applyAutogenConsumersForGeneratedAssets(collected, consumer, progress)
	if err != nil {
		return nil, err
	}
	collected, err = normalizePartsRowsACBonus(collected, consumer.PartsRows)
	if err != nil {
		return nil, err
	}
	collected, err = applyPartOverridesWithConfig(sourceDir, collected, consumer.PartsRows)
	if err != nil {
		return nil, err
	}
	if err := rejectGenerated2DATLKValues(cfg.ID, collected); err != nil {
		return nil, err
	}

	tableRegistry, err := newResolvedTableRegistry(collected)
	if err != nil {
		return nil, err
	}
	keyToID, rowByKey := generated2DAGlobalRows(collected)

	if err := os.RemoveAll(outputDir); err != nil {
		return nil, fmt.Errorf("clean generated topdata 2DA output %s: %w", outputDir, err)
	}
	if err := os.MkdirAll(outputDir, 0o755); err != nil {
		return nil, fmt.Errorf("create generated topdata 2DA output %s: %w", outputDir, err)
	}

	results := make([]Generated2DAAsset, 0, len(collected))
	for _, dataset := range collected {
		compiled, err := resolveNativeDataset(dataset, keyToID, rowByKey, tableRegistry, nil)
		if err != nil {
			return nil, err
		}
		outputPath := filepath.Join(outputDir, dataset.Dataset.OutputName)
		if err := write2DA(compiled, outputPath, dataset.Dataset.Kind == nativeDatasetBase); err != nil {
			return nil, err
		}
		results = append(results, Generated2DAAsset{
			Rel:        filepath.ToSlash(filepath.Join(cfg.PackageRoot, dataset.Dataset.OutputName)),
			SourcePath: outputPath,
		})
	}
	return results, nil
}

func applyAutogenConsumersForGeneratedAssets(collected []nativeCollectedDataset, consumer project.AutogenConsumerConfig, progress func(string)) ([]nativeCollectedDataset, error) {
	if strings.TrimSpace(consumer.Mode) == "" {
		return collected, nil
	}
	if progress == nil {
		progress = func(string) {}
	}
	if !autogenConsumerTargetsCollectedDataset(collected, consumer) {
		return collected, nil
	}
	if progress != nil {
		progress(fmt.Sprintf("Scanning local generated 2DA autogen input for %s from %s...", consumer.ID, consumer.LocalOverrideRoot))
	}
	entries, err := scanLocalAutogenEntries(consumer.LocalOverrideRoot, consumer)
	if err != nil {
		return nil, err
	}
	switch consumer.Mode {
	case "parts_rows":
		return augmentWithAutogeneratedPartsWithConfig(collected, autogenPartsInventory(entries), consumer.PartsRows), nil
	default:
		return nil, fmt.Errorf("unsupported generated topdata 2DA autogen mode %q", consumer.Mode)
	}
}

func generatedDatasetIncluded(name string, patterns []string) bool {
	name = filepath.ToSlash(strings.TrimSpace(name))
	for _, pattern := range patterns {
		pattern = filepath.ToSlash(strings.TrimSpace(pattern))
		if pattern == "" {
			continue
		}
		if pattern == name {
			return true
		}
		if strings.HasSuffix(pattern, "/**") {
			prefix := strings.TrimSuffix(pattern, "/**")
			if name == prefix || strings.HasPrefix(name, prefix+"/") {
				return true
			}
		}
	}
	return false
}

func generated2DAGlobalRows(collected []nativeCollectedDataset) (map[string]int, map[string]map[string]any) {
	keyToID := map[string]int{}
	rowByKey := map[string]map[string]any{}
	for _, dataset := range collected {
		for key, rowID := range dataset.LockData {
			keyToID[key] = rowID
		}
		for _, row := range dataset.Rows {
			key, _ := row["key"].(string)
			if key == "" {
				continue
			}
			if rowID, ok := row["id"].(int); ok {
				keyToID[key] = rowID
				rowByKey[key] = row
			}
		}
	}
	return keyToID, rowByKey
}

func rejectGenerated2DATLKValues(configID string, collected []nativeCollectedDataset) error {
	for _, dataset := range collected {
		for _, row := range dataset.Rows {
			for field, value := range row {
				if field == "id" || field == "key" {
					continue
				}
				allowBare := columnMatchesSpec(dataset.Dataset.Spec, field)
				if _, ok, err := parseTLKPayload(value, allowBare); err != nil {
					return fmt.Errorf("generated topdata 2DA config %s dataset %s field %s has invalid TLK payload: %w", configID, dataset.Dataset.Name, field, err)
				} else if ok {
					return fmt.Errorf("generated topdata 2DA config %s dataset %s field %s uses TLK-backed text; 2DA-only generated asset builds do not generate TLK output", configID, dataset.Dataset.Name, field)
				}
			}
		}
	}
	return nil
}
