package topdata

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

func importLegacyDamagetypes(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) {
	_ = legacyTLK

	legacyRoot := filepath.Join(referenceBuilderDir, "data", "damagetypes")
	if _, err := os.Stat(legacyRoot); err != nil {
		if os.IsNotExist(err) {
			return 0, nil
		}
		return 0, err
	}

	targetDir := filepath.Join(dataDir, "damagetypes", "registry")
	targetPath := filepath.Join(targetDir, "types.json")
	if _, err := os.Stat(targetPath); err == nil {
		obj, err := loadJSONObject(targetPath)
		if err == nil && countLegacyTLKRefsInValue(obj) == 0 {
			return 0, nil
		}
	}

	coreBase, err := loadJSONObject(filepath.Join(legacyRoot, "core", "base.json"))
	if err != nil {
		return 0, err
	}
	groupBase, err := loadJSONObject(filepath.Join(legacyRoot, "groups", "base.json"))
	if err != nil {
		return 0, err
	}
	hitvisualBase, err := loadJSONObject(filepath.Join(legacyRoot, "hitvisual", "base.json"))
	if err != nil {
		return 0, err
	}
	coreModule, err := loadJSONObject(filepath.Join(legacyRoot, "core", "modules", "add_eos.json"))
	if err != nil {
		return 0, err
	}
	coreLock, err := loadLockfile(filepath.Join(legacyRoot, "core", "lock.json"))
	if err != nil {
		return 0, err
	}
	groupModule, err := loadJSONObject(filepath.Join(legacyRoot, "groups", "modules", "add_eos.json"))
	if err != nil {
		return 0, err
	}
	groupLock, err := loadLockfile(filepath.Join(legacyRoot, "groups", "lock.json"))
	if err != nil {
		return 0, err
	}
	hitvisualModule, err := loadJSONObject(filepath.Join(legacyRoot, "hitvisual", "modules", "add_eos.json"))
	if err != nil {
		return 0, err
	}
	hitvisualLock, err := loadLockfile(filepath.Join(legacyRoot, "hitvisual", "lock.json"))
	if err != nil {
		return 0, err
	}

	rows, lockData, err := mergeLegacyDamagetypes(coreBase, groupBase, hitvisualBase, coreModule, coreLock, groupModule, groupLock, hitvisualModule, hitvisualLock)
	if err != nil {
		return 0, err
	}

	if err := os.MkdirAll(targetDir, 0o755); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(dataDir, "damagetypes", "core")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(dataDir, "damagetypes", "groups")); err != nil {
		return 0, err
	}
	if err := removePathIfExists(filepath.Join(dataDir, "damagetypes", "hitvisual")); err != nil {
		return 0, err
	}

	typesOut := map[string]any{"rows": rows}
	raw, err := json.MarshalIndent(typesOut, "", "  ")
	if err != nil {
		return 0, err
	}
	raw = append(raw, '\n')
	if err := os.WriteFile(targetPath, raw, 0o644); err != nil {
		return 0, err
	}
	if err := saveLockfile(filepath.Join(targetDir, damagetypesRegistryLock), lockData); err != nil {
		return 0, err
	}
	return 2, nil
}

func mergeLegacyDamagetypes(coreBase, groupBase, hitvisualBase, coreModule map[string]any, coreLock map[string]int, groupModule map[string]any, groupLock map[string]int, hitvisualModule map[string]any, hitvisualLock map[string]int) ([]map[string]any, map[string]int, error) {
	coreRows, err := coerceRowMapSlice(coreBase["rows"])
	if err != nil {
		return nil, nil, err
	}
	groupRows, err := coerceRowMapSlice(groupBase["rows"])
	if err != nil {
		return nil, nil, err
	}
	hitRows, err := coerceRowMapSlice(hitvisualBase["rows"])
	if err != nil {
		return nil, nil, err
	}
	groupByID := map[int]map[string]any{}
	for _, row := range groupRows {
		id, err := asInt(row["id"])
		if err != nil {
			return nil, nil, err
		}
		groupByID[id] = row
	}
	hitByID := map[int]map[string]any{}
	for _, row := range hitRows {
		id, err := asInt(row["id"])
		if err != nil {
			return nil, nil, err
		}
		hitByID[id] = row
	}

	outRows := make([]map[string]any, 0, len(coreRows))
	lockData := map[string]int{}
	for _, core := range coreRows {
		id, err := asInt(core["id"])
		if err != nil {
			return nil, nil, err
		}
		groupID, err := asInt(core["DamageTypeGroup"])
		if err != nil {
			return nil, nil, err
		}
		group := groupByID[groupID]
		if group == nil {
			return nil, nil, fmt.Errorf("core id %d references missing group %d", id, groupID)
		}
		hit := hitByID[id]
		if hit == nil {
			return nil, nil, fmt.Errorf("core id %d is missing hitvisual row", id)
		}
		key := "damagetype:" + normalizeDamagetypeKey(core["Label"])
		lockData[key] = id
		outRows = append(outRows, map[string]any{
			"key":                     key,
			"id":                      id,
			"label":                   deepCopyValue(core["Label"]),
			"charsheet_strref":        deepCopyValue(core["CharsheetStrref"]),
			"damage_type_group":       strconvString(groupID),
			"damage_ranged_projectile": deepCopyValue(core["DamageRangedProjectile"]),
			"group_label":             deepCopyValue(group["Label"]),
			"feedback_strref":         deepCopyValue(group["FeedbackStrref"]),
			"color_r":                 deepCopyValue(group["ColorR"]),
			"color_g":                 deepCopyValue(group["ColorG"]),
			"color_b":                 deepCopyValue(group["ColorB"]),
			"visual_effect_id":        deepCopyValue(hit["VisualEffectID"]),
			"ranged_effect_id":        deepCopyValue(hit["RangedEffectID"]),
		})
	}

	coreEntries, err := coerceEntriesMap(coreModule["entries"])
	if err != nil {
		return nil, nil, err
	}
	groupEntries, err := coerceEntriesMap(groupModule["entries"])
	if err != nil {
		return nil, nil, err
	}
	hitEntries, err := coerceEntriesMap(hitvisualModule["entries"])
	if err != nil {
		return nil, nil, err
	}

	keys := make([]string, 0, len(coreEntries))
	for key := range coreEntries {
		keys = append(keys, key)
	}
	sort.Slice(keys, func(i, j int) bool {
		a := strings.TrimPrefix(keys[i], "damagetypes:")
		b := strings.TrimPrefix(keys[j], "damagetypes:")
		return a < b
	})
	for _, coreKey := range keys {
		core := coreEntries[coreKey]
		suffix := strings.TrimPrefix(coreKey, "damagetypes:")
		group := groupEntries["damagetypegroups:"+suffix]
		if group == nil {
			return nil, nil, fmt.Errorf("%s: missing matching group entry", coreKey)
		}
		hit := hitEntries["damagehitvisual:"+suffix]
		if hit == nil {
			return nil, nil, fmt.Errorf("%s: missing matching hitvisual entry", coreKey)
		}
		id, ok := coreLock[coreKey]
		if !ok {
			return nil, nil, fmt.Errorf("%s: missing core lock id", coreKey)
		}
		groupKey := "damagetypegroups:" + suffix
		groupID, ok := groupLock[groupKey]
		if !ok {
			return nil, nil, fmt.Errorf("%s: missing group lock id", groupKey)
		}
		hitKey := "damagehitvisual:" + suffix
		hitID, ok := hitvisualLock[hitKey]
		if !ok {
			return nil, nil, fmt.Errorf("%s: missing hitvisual lock id", hitKey)
		}
		if hitID != id {
			return nil, nil, fmt.Errorf("%s: core id %d does not match hitvisual id %d", suffix, id, hitID)
		}
		key := "damagetype:" + suffix
		lockData[key] = id
		outRows = append(outRows, map[string]any{
			"key":                     key,
			"id":                      id,
			"label":                   deepCopyValue(core["Label"]),
			"charsheet_strref":        deepCopyValue(core["CharsheetStrref"]),
			"damage_type_group":       strconvString(groupID),
			"damage_ranged_projectile": deepCopyValue(core["DamageRangedProjectile"]),
			"group_label":             deepCopyValue(group["Label"]),
			"feedback_strref":         deepCopyValue(group["FeedbackStrref"]),
			"color_r":                 deepCopyValue(group["ColorR"]),
			"color_g":                 deepCopyValue(group["ColorG"]),
			"color_b":                 deepCopyValue(group["ColorB"]),
			"visual_effect_id":        deepCopyValue(hit["VisualEffectID"]),
			"ranged_effect_id":        deepCopyValue(hit["RangedEffectID"]),
		})
	}

	sort.Slice(outRows, func(i, j int) bool {
		return outRows[i]["id"].(int) < outRows[j]["id"].(int)
	})
	return outRows, lockData, nil
}

func coerceRowMapSlice(value any) ([]map[string]any, error) {
	rawRows, ok := value.([]any)
	if !ok {
		return nil, fmt.Errorf("rows must be an array")
	}
	rows := make([]map[string]any, 0, len(rawRows))
	for _, raw := range rawRows {
		row, ok := raw.(map[string]any)
		if !ok {
			return nil, fmt.Errorf("row must be an object")
		}
		rows = append(rows, row)
	}
	return rows, nil
}

func coerceEntriesMap(value any) (map[string]map[string]any, error) {
	rawEntries, ok := value.(map[string]any)
	if !ok {
		return nil, fmt.Errorf("entries must be an object")
	}
	entries := make(map[string]map[string]any, len(rawEntries))
	for key, raw := range rawEntries {
		row, ok := raw.(map[string]any)
		if !ok {
			return nil, fmt.Errorf("entry %s must be an object", key)
		}
		entries[key] = row
	}
	return entries, nil
}

func normalizeDamagetypeKey(value any) string {
	text := strings.TrimSpace(strings.ToLower(format2DAValue(value)))
	text = strings.TrimSuffix(text, "damage")
	text = strings.ReplaceAll(text, " ", "")
	text = strings.ReplaceAll(text, "_", "")
	text = strings.ReplaceAll(text, "-", "")
	return text
}

func strconvString(v int) string {
	return fmt.Sprintf("%d", v)
}
