package music

import (
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

const (
	MaxStemLen    = 16
	CreditsHeader = "# NWN Music Pack Credits\n\nOriginal rights remain with the respective composers and licensors.\n\n## Processed Files\n\n| Artist | Title | Output File | Original File | Album | Date | License / Copyright | Notes |\n|---|---|---|---|---|---|---|---|\n"
)

var (
	defaultConvertExtensions = []string{
		".flac",
		".m4a",
		".mp3",
		".ogg",
		".wav",
	}
	ConvertExtensions = map[string]struct{}{
		".flac": {},
		".m4a":  {},
		".mp3":  {},
		".ogg":  {},
		".wav":  {},
	}
	PassthroughExtensions = map[string]struct{}{
		".bmu": {},
		".wav": {},
	}
	DropWords = map[string]struct{}{
		"mp3": {}, "wav": {}, "flac": {}, "ogg": {}, "m4a": {},
		"music": {}, "track": {}, "audio": {}, "song": {}, "soundtrack": {},
		"final": {}, "version": {}, "ver": {}, "v": {}, "mix": {}, "master": {}, "remaster": {},
		"free": {}, "royalty": {}, "copyright": {}, "background": {}, "bgm": {},
		"loop": {}, "loops": {}, "loopable": {}, "seamless": {},
		"official": {}, "download": {}, "preview": {},
		"the": {}, "a": {}, "an": {}, "and": {}, "of": {}, "to": {}, "in": {}, "for": {}, "with": {}, "by": {},
	}
	BracketRE = regexp.MustCompile(`\[[^\]]*\]|\([^)]*\)|\{[^}]*\}`)
	SplitRE   = regexp.MustCompile(`[^A-Za-z0-9]+`)
	YearRE    = regexp.MustCompile(`^\d{4,}$`)
	VowelRE   = regexp.MustCompile(`[aeiou]`)
)

func DefaultConvertExtensions() []string {
	return append([]string(nil), defaultConvertExtensions...)
}

type Metadata struct {
	Title     string
	Artist    string
	Album     string
	Date      string
	Rights    string
	Notes     string
	Comment   string
	Copyright string
	License   string
}

type CreditsEntry struct {
	Artist       string `json:"artist"`
	Title        string `json:"title"`
	OutputFile   string `json:"output_file"`
	OriginalFile string `json:"original_file"`
	Album        string `json:"album"`
	Date         string `json:"date"`
	Rights       string `json:"rights"`
	Notes        string `json:"notes"`
}

type CreditsSource struct {
	Path    string         `json:"path"`
	Kind    string         `json:"kind"`
	Entries []CreditsEntry `json:"entries"`
}

type CreditsInventory struct {
	Sources []CreditsSource `json:"sources"`
}

type CreditsOverlay struct {
	ByOriginal map[string]CreditsEntry
	ByOutput   map[string]CreditsEntry
	Entries    []CreditsEntry
}

func ResolveFFmpeg() (string, error) {
	return ResolveFFmpegWithConfig("")
}

func ResolveFFmpegWithConfig(configuredPath string) (string, error) {
	if configured := strings.TrimSpace(configuredPath); configured != "" {
		return configured, nil
	}
	if configured := strings.TrimSpace(os.Getenv("SOW_FFMPEG")); configured != "" {
		return configured, nil
	}
	path, err := exec.LookPath("ffmpeg")
	if err != nil {
		return "", err
	}
	return path, nil
}

func ResolveFFprobe() (string, error) {
	return ResolveFFprobeWithConfig("")
}

func ResolveFFprobeWithConfig(configuredPath string) (string, error) {
	if configured := strings.TrimSpace(configuredPath); configured != "" {
		return configured, nil
	}
	if configured := strings.TrimSpace(os.Getenv("SOW_FFPROBE")); configured != "" {
		return configured, nil
	}
	path, err := exec.LookPath("ffprobe")
	if err != nil {
		return "", err
	}
	return path, nil
}

func IsMusicAssetPath(rel string) bool {
	rel = filepath.ToSlash(strings.TrimSpace(rel))
	return rel == "envi/music" || strings.HasPrefix(rel, "envi/music/")
}

func PathIsUnder(root, rel string) bool {
	root = TrimSlashes(filepath.ToSlash(root))
	rel = TrimSlashes(filepath.ToSlash(rel))
	if root == "" || rel == "" {
		return false
	}
	return rel == root || strings.HasPrefix(rel, root+"/")
}

func PrefixForDir(prefixes map[string]string, dir string) string {
	dir = TrimSlashes(filepath.ToSlash(dir))
	bestPrefix := ""
	bestLen := -1
	keys := make([]string, 0, len(prefixes))
	for key := range prefixes {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		value := prefixes[key]
		normalizedKey := TrimSlashes(filepath.ToSlash(key))
		if normalizedKey == "" {
			continue
		}
		if dir != normalizedKey && !strings.HasPrefix(dir, normalizedKey+"/") {
			continue
		}
		if len(normalizedKey) > bestLen {
			bestLen = len(normalizedKey)
			bestPrefix = SanitizePrefix(value)
		}
	}
	return bestPrefix
}

func SanitizePrefix(prefix string) string {
	prefix = strings.ToLower(strings.TrimSpace(prefix))
	var builder strings.Builder
	for _, r := range prefix {
		switch {
		case r >= 'a' && r <= 'z':
			builder.WriteRune(r)
		case r >= '0' && r <= '9':
			builder.WriteRune(r)
		case r == '_':
			builder.WriteRune(r)
		}
	}
	result := builder.String()
	if strings.TrimSpace(prefix) != "" && strings.HasSuffix(strings.TrimSpace(prefix), "_") && !strings.HasSuffix(result, "_") {
		result += "_"
	}
	return result
}

func TrimSlashes(value string) string {
	return strings.Trim(strings.TrimSpace(value), "/")
}

func Min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func Max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func FirstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func JoinNonEmpty(sep string, values ...string) string {
	parts := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value != "" {
			parts = append(parts, value)
		}
	}
	return strings.Join(parts, sep)
}
