package metadata

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

	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/consts"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/id"
	"github.com/navidrome/navidrome/utils"
	"github.com/navidrome/navidrome/utils/slice"
	"github.com/navidrome/navidrome/utils/str"
)

type hashFunc = func(...string) string

// createGetPID returns a function that calculates the persistent ID for a given spec, getting the referenced values from the metadata
// The spec is a pipe-separated list of fields, where each field is a comma-separated list of attributes
// Attributes can be either tags or some processed values like folder, albumid, albumartistid, etc.
// For each field, it gets all its attributes values and concatenates them, then hashes the result.
// If a field is empty, it is skipped and the function looks for the next field.
type getPIDFunc = func(mf model.MediaFile, md Metadata, spec string, prependLibId bool) string

func createGetPID(hash hashFunc) getPIDFunc {
	var getPID getPIDFunc
	getAttr := func(mf model.MediaFile, md Metadata, attr string, prependLibId bool, spec string) string {
		attr = strings.TrimSpace(strings.ToLower(attr))
		switch attr {
		case "albumid":
			if spec == conf.Server.PID.Album {
				log.Error("Recursive PID definition detected, ignoring `albumid`", "spec", spec)
				return ""
			}
			return getPID(mf, md, conf.Server.PID.Album, prependLibId)
		case "folder":
			return filepath.Dir(mf.Path)
		case "albumartistid":
			return hash(str.Clear(strings.ToLower(mf.AlbumArtist)))
		case "title":
			return mf.Title
		case "album":
			return str.Clear(strings.ToLower(md.String(model.TagAlbum)))
		}
		return md.String(model.TagName(attr))
	}
	getPID = func(mf model.MediaFile, md Metadata, spec string, prependLibId bool) string {
		pid := ""
		fields := strings.SplitSeq(spec, "|")
		for field := range fields {
			attributes := strings.Split(field, ",")
			hasValue := false
			values := slice.Map(attributes, func(attr string) string {
				v := getAttr(mf, md, attr, prependLibId, spec)
				if v != "" {
					hasValue = true
				}
				return v
			})
			if hasValue {
				pid += strings.Join(values, "\\")
				break
			}
		}
		if prependLibId {
			pid = fmt.Sprintf("%d\\%s", mf.LibraryID, pid)
		}
		return hash(pid)
	}

	return func(mf model.MediaFile, md Metadata, spec string, prependLibId bool) string {
		switch spec {
		case "track_legacy":
			return legacyTrackID(mf, prependLibId)
		case "album_legacy":
			return legacyAlbumID(mf, md, prependLibId)
		}
		return getPID(mf, md, spec, prependLibId)
	}
}

func (md Metadata) trackPID(mf model.MediaFile) string {
	return createGetPID(id.NewHash)(mf, md, conf.Server.PID.Track, true)
}

func (md Metadata) albumID(mf model.MediaFile, pidConf string) string {
	return createGetPID(id.NewHash)(mf, md, pidConf, true)
}

// BFR Must be configurable?
func (md Metadata) artistID(name string) string {
	mf := model.MediaFile{AlbumArtist: name}
	return createGetPID(id.NewHash)(mf, md, "albumartistid", false)
}

func (md Metadata) mapTrackTitle() string {
	if title := md.String(model.TagTitle); title != "" {
		return title
	}
	return utils.BaseName(md.FilePath())
}

func (md Metadata) mapAlbumName() string {
	return cmp.Or(
		md.String(model.TagAlbum),
		consts.UnknownAlbum,
	)
}
