// Playback profile generation is adapted from Wholphin's DeviceProfileUtils.kt,
// itself derived from Jellyfin Android TV.
//
// Wholphin: https://github.com/damontecres/Wholphin
// Jellyfin Android TV: https://github.com/jellyfin/jellyfin-androidtv
// Moonfin audio profile: https://github.com/Moonfin-Client/Moonfin-Core
//
// Modifications Copyright (C) 2026 Memby contributors
// SPDX-License-Identifier: GPL-2.0-only
package emby

import "strconv"

var alwaysDecodableAudioCodecs = []string{
	"aac", "mp3", "flac", "opus", "vorbis", "pcm_s16le", "pcm_s24le",
}

// PlaybackCapabilities is the Android decoder evidence captured for one TV session.
// Zero values deliberately describe the legacy H.264-safe profile.
type PlaybackCapabilities struct {
	H264Profiles     []string
	H264Level        int
	H264High10Level  int
	H264MaxWidth     int
	H264MaxHeight    int
	HEVC             bool
	HEVCMain         bool
	HEVCMain10       bool
	HEVCMainLevel    int
	HEVCMain10Level  int
	HEVCMaxWidth     int
	HEVCMaxHeight    int
	HEVCHDR10        bool
	HEVCHDR10Plus    bool
	HEVCDolbyVision  bool
	AudioProfileV1   bool
	AudioPassthrough map[string]bool
	AudioDecode      map[string]bool
	AudioMaxChannels int
}

func androidTVDeviceProfile(capabilities PlaybackCapabilities) map[string]any {
	videoCodecs := directPlayVideoCodecs(capabilities.HEVC)
	audioCodecs := directPlayAudioCodecs(capabilities)
	transcodeAudio := transcodeAudioCodecs(capabilities)
	return map[string]any{
		"Name": "Memby Android TV", "SupportedMediaTypes": "Video",
		"DirectPlayProfiles": []map[string]string{
			{
				"Container": "mkv,mp4,m4v,mov,ts,mpegts", "VideoCodec": videoCodecs,
				"AudioCodec": audioCodecs, "Type": "Video",
			},
		},
		"TranscodingProfiles": []map[string]string{
			{
				// AllowVideoStreamCopy lets Emby keep a supported H.264/HEVC track and
				// convert only incompatible audio or subtitles into an HLS stream.
				"Container": "ts", "VideoCodec": videoCodecs, "AudioCodec": transcodeAudio,
				"Protocol": "hls", "Type": "Video", "Context": "Streaming",
			},
		},
		"CodecProfiles":    append(videoCodecProfiles(capabilities), audioCodecProfile(capabilities)),
		"SubtitleProfiles": androidTVSubtitleProfiles(),
	}
}

func directPlayAudioCodecs(capabilities PlaybackCapabilities) string {
	if !capabilities.AudioProfileV1 {
		return "aac,mp3"
	}
	codecs := append([]string{}, alwaysDecodableAudioCodecs...)
	for _, codec := range []string{"ac3", "eac3", "atmos", "dts", "dts_hd", "truehd"} {
		if capabilities.AudioPassthrough[codec] || capabilities.AudioDecode[codec] {
			switch codec {
			case "dts":
				codecs = appendUnique(codecs, "dts", "dca")
			case "dts_hd":
				codecs = appendUnique(codecs, "dts", "dca", "dtshd")
			case "truehd":
				codecs = appendUnique(codecs, "truehd", "mlp")
			case "atmos":
				codecs = appendUnique(codecs, "eac3")
			default:
				codecs = appendUnique(codecs, codec)
			}
		}
	}
	return joinComma(codecs)
}

func transcodeAudioCodecs(capabilities PlaybackCapabilities) string {
	codecs := []string{}
	if capabilities.AudioPassthrough["eac3"] || capabilities.AudioPassthrough["atmos"] {
		codecs = append(codecs, "eac3")
	}
	if capabilities.AudioPassthrough["ac3"] {
		codecs = append(codecs, "ac3")
	}
	return joinComma(appendUnique(codecs, "aac", "mp3"))
}

func audioCodecProfile(capabilities PlaybackCapabilities) map[string]any {
	channels := capabilities.AudioMaxChannels
	if channels < 2 {
		channels = 2
	}
	return map[string]any{
		"Type": "VideoAudio", "Codec": "",
		"Conditions": []map[string]any{
			profileCondition("LessThanEqual", "AudioChannels", strconv.Itoa(channels)),
		},
	}
}

func appendUnique(values []string, additions ...string) []string {
	for _, addition := range additions {
		found := false
		for _, value := range values {
			if value == addition {
				found = true
				break
			}
		}
		if !found {
			values = append(values, addition)
		}
	}
	return values
}

func joinComma(values []string) string {
	result := ""
	for _, value := range values {
		if result != "" {
			result += ","
		}
		result += value
	}
	return result
}

// forceH264TranscodeProfile turns decoder recovery into an actual codec change. Merely
// removing DirectPlayProfiles is insufficient: Emby may otherwise stream-copy HEVC into
// the ordinary HLS transcoding profile and hand the failing decoder the same video again.
func forceH264TranscodeProfile(profile map[string]any) {
	profile["DirectPlayProfiles"] = []map[string]string{}
	profile["TranscodingProfiles"] = []map[string]string{
		{
			"Container": "ts", "VideoCodec": "h264", "AudioCodec": "aac",
			"Protocol": "hls", "Type": "Video", "Context": "Streaming",
		},
	}
	profiles, _ := profile["CodecProfiles"].([]map[string]any)
	h264Profiles := make([]map[string]any, 0, len(profiles))
	for _, codecProfile := range profiles {
		if codec, _ := codecProfile["Codec"].(string); codec == "h264" {
			h264Profiles = append(h264Profiles, codecProfile)
		}
	}
	profile["CodecProfiles"] = h264Profiles
}

func androidTVSubtitleProfiles() []map[string]string {
	return []map[string]string{
		{"Format": "srt", "Method": "External"},
		{"Format": "subrip", "Method": "External"},
		{"Format": "ass", "Method": "External"},
		{"Format": "ssa", "Method": "External"},
		{"Format": "vtt", "Method": "External"},
		{"Format": "webvtt", "Method": "External"},
		{"Format": "mov_text", "Method": "External"},
		{"Format": "tx3g", "Method": "External"},
		{"Format": "pgs", "Method": "Encode"},
		{"Format": "pgssub", "Method": "Encode"},
		{"Format": "sup", "Method": "Encode"},
		{"Format": "vobsub", "Method": "Encode"},
		{"Format": "dvdsub", "Method": "Encode"},
	}
}

func videoCodecProfiles(capabilities PlaybackCapabilities) []map[string]any {
	profiles := []map[string]any{}
	if len(capabilities.H264Profiles) > 0 {
		profiles = append(profiles, codecProfile("h264",
			[]map[string]any{profileCondition("EqualsAny", "VideoProfile", joinProfiles(capabilities.H264Profiles))}, nil))
	}
	profiles = appendLevelProfile(
		profiles, "h264", capabilities.H264Level, "baseline|constrained baseline|main|high",
	)
	profiles = appendLevelProfile(profiles, "h264", capabilities.H264High10Level, "high 10")
	profiles = appendResolutionProfile(
		profiles, "h264", capabilities.H264MaxWidth, capabilities.H264MaxHeight,
	)
	if !capabilities.HEVC {
		return profiles
	}

	hevcProfiles := []string{}
	if capabilities.HEVCMain {
		hevcProfiles = append(hevcProfiles, "main")
	}
	if capabilities.HEVCMain10 {
		hevcProfiles = append(hevcProfiles, "main 10")
	}
	if len(hevcProfiles) > 0 {
		profiles = append(profiles, codecProfile("hevc",
			[]map[string]any{profileCondition("EqualsAny", "VideoProfile", joinProfiles(hevcProfiles))}, nil))
	}
	profiles = appendLevelProfile(profiles, "hevc", capabilities.HEVCMainLevel, "main")
	profiles = appendLevelProfile(profiles, "hevc", capabilities.HEVCMain10Level, "main 10")
	profiles = appendResolutionProfile(
		profiles, "hevc", capabilities.HEVCMaxWidth, capabilities.HEVCMaxHeight,
	)
	return profiles
}

func appendLevelProfile(profiles []map[string]any, codec string, level int, applyTo string) []map[string]any {
	if level <= 0 {
		return profiles
	}
	condition := "Equals"
	if containsPipe(applyTo) {
		condition = "EqualsAny"
	}
	return append(profiles, codecProfile(codec,
		[]map[string]any{profileCondition("LessThanEqual", "VideoLevel", strconv.Itoa(level))},
		[]map[string]any{profileCondition(condition, "VideoProfile", applyTo)}))
}

func appendResolutionProfile(profiles []map[string]any, codec string, width, height int) []map[string]any {
	if width <= 0 || height <= 0 {
		return profiles
	}
	return append(profiles, codecProfile(codec, []map[string]any{
		profileCondition("LessThanEqual", "Width", strconv.Itoa(width)),
		profileCondition("LessThanEqual", "Height", strconv.Itoa(height)),
	}, nil))
}

func codecProfile(codec string, conditions, applyConditions []map[string]any) map[string]any {
	profile := map[string]any{
		"Type": "Video", "Codec": codec, "Conditions": conditions,
	}
	if len(applyConditions) > 0 {
		profile["ApplyConditions"] = applyConditions
	}
	return profile
}

func profileCondition(condition, property, value string) map[string]any {
	return map[string]any{
		"Condition": condition, "Property": property, "Value": value, "IsRequired": false,
	}
}

func joinProfiles(values []string) string {
	result := ""
	for _, value := range values {
		if result != "" {
			result += "|"
		}
		result += value
	}
	return result
}

func containsPipe(value string) bool {
	for _, char := range value {
		if char == '|' {
			return true
		}
	}
	return false
}
