package regionmedic

import (
	"fmt"
	"strconv"
	"strings"
)

// floorMod returns the mathematical (floored) modulo, always in [0, m).
func floorMod(a, m int) int { return ((a % m) + m) % m }

// floorDiv returns the floored integer division (rounds toward -inf).
func floorDiv(a, m int) int {
	q := a / m
	if (a%m != 0) && ((a < 0) != (m < 0)) {
		q--
	}
	return q
}

// RegionCoord identifies a region file r.<X>.<Z>.7rg in the 32×32-chunk grid.
type RegionCoord struct {
	X int `json:"x"`
	Z int `json:"z"`
}

// FileName returns the on-disk region filename, e.g. "r.-2.0.7rg".
func (rc RegionCoord) FileName() string { return fmt.Sprintf("r.%d.%d.7rg", rc.X, rc.Z) }

// String returns the canonical region id, e.g. "r.-2.0".
func (rc RegionCoord) String() string { return fmt.Sprintf("r.%d.%d", rc.X, rc.Z) }

// RegionForChunk maps a global chunk coordinate to its region (floored /32).
func RegionForChunk(cx, cz int) RegionCoord {
	return RegionCoord{X: floorDiv(cx, RegionSide), Z: floorDiv(cz, RegionSide)}
}

// ParseRegionFileName parses "r.<X>.<Z>.7rg" -> RegionCoord. ok=false otherwise.
// Handles negative coordinates, e.g. "r.-2.0.7rg" -> {X:-2, Z:0}.
func ParseRegionFileName(name string) (RegionCoord, bool) {
	if !strings.HasPrefix(name, "r.") || !strings.HasSuffix(name, ".7rg") {
		return RegionCoord{}, false
	}
	mid := strings.TrimSuffix(strings.TrimPrefix(name, "r."), ".7rg")
	// mid is "<X>.<Z>" where X and Z may be negative. Split on the dot that
	// separates them: there is exactly one '.' between two integers.
	x, z, ok := splitTwoInts(mid)
	if !ok {
		return RegionCoord{}, false
	}
	return RegionCoord{X: x, Z: z}, true
}

// ParseErrorBackup parses 7DTD chunk-salvage filenames written on a failed load:
//
//	error_backup_<cx>_<cz>.comp.bak
//	error_backup_<cx>_<cz>.uncomp.bak
//
// returning the global chunk coordinates. ok=false if name is not such a file.
func ParseErrorBackup(name string) (cx, cz int, ok bool) {
	const pfx = "error_backup_"
	if !strings.HasPrefix(name, pfx) {
		return 0, 0, false
	}
	rest := name[len(pfx):]
	// Strip the known suffixes.
	switch {
	case strings.HasSuffix(rest, ".comp.bak"):
		rest = strings.TrimSuffix(rest, ".comp.bak")
	case strings.HasSuffix(rest, ".uncomp.bak"):
		rest = strings.TrimSuffix(rest, ".uncomp.bak")
	default:
		return 0, 0, false
	}
	// rest is "<cx>_<cz>" with possibly-negative ints separated by '_'.
	i := strings.LastIndex(rest, "_")
	if i <= 0 || i >= len(rest)-1 {
		return 0, 0, false
	}
	a, err1 := strconv.Atoi(rest[:i])
	b, err2 := strconv.Atoi(rest[i+1:])
	if err1 != nil || err2 != nil {
		return 0, 0, false
	}
	return a, b, true
}

// splitTwoInts splits "<a>.<b>" into two ints where each may be negative.
// The separator is the first '.' that is not a leading minus sign position.
func splitTwoInts(s string) (int, int, bool) {
	// Find a '.' that splits into two parseable ints. Scan candidate dots.
	for i := 1; i < len(s); i++ {
		if s[i] != '.' {
			continue
		}
		a, err1 := strconv.Atoi(s[:i])
		b, err2 := strconv.Atoi(s[i+1:])
		if err1 == nil && err2 == nil {
			return a, b, true
		}
	}
	return 0, 0, false
}
