package anilist

import (
	"seanime/internal/util/comparison"

	"github.com/samber/lo"
)

func (m *BaseAnime) GetTitleSafe() string {
	if m.GetTitle().GetEnglish() != nil {
		return *m.GetTitle().GetEnglish()
	}
	if m.GetTitle().GetRomaji() != nil {
		return *m.GetTitle().GetRomaji()
	}
	return ""
}

func (m *BaseAnime) GetEnglishTitleSafe() string {
	if m.GetTitle().GetEnglish() != nil {
		return *m.GetTitle().GetEnglish()
	}
	return ""
}

func (m *BaseAnime) GetRomajiTitleSafe() string {
	if m.GetTitle().GetRomaji() != nil {
		return *m.GetTitle().GetRomaji()
	}
	if m.GetTitle().GetEnglish() != nil {
		return *m.GetTitle().GetEnglish()
	}
	return ""
}

func (m *BaseAnime) GetPreferredTitle() string {
	if m.GetTitle().GetUserPreferred() != nil {
		return *m.GetTitle().GetUserPreferred()
	}
	return m.GetTitleSafe()
}

func (m *BaseAnime) GetCoverImageSafe() string {
	if m.GetCoverImage().GetExtraLarge() != nil {
		return *m.GetCoverImage().GetExtraLarge()
	}
	if m.GetCoverImage().GetLarge() != nil {
		return *m.GetCoverImage().GetLarge()
	}
	if m.GetBannerImage() != nil {
		return *m.GetBannerImage()
	}
	return ""
}

func (m *BaseAnime) GetBannerImageSafe() string {
	if m.GetBannerImage() != nil {
		return *m.GetBannerImage()
	}
	return m.GetCoverImageSafe()
}

func (m *BaseAnime) IsMovieOrSingleEpisode() bool {
	if m == nil {
		return false
	}
	if m.GetTotalEpisodeCount() == 1 {
		return true
	}
	return false
}

func (m *BaseAnime) GetSynonymsDeref() []string {
	if m.Synonyms == nil {
		return nil
	}
	return lo.Map(m.Synonyms, func(s *string, i int) string { return *s })
}

func (m *BaseAnime) GetSynonymsContainingSeason() []string {
	if m.Synonyms == nil {
		return nil
	}
	return lo.Filter(lo.Map(m.Synonyms, func(s *string, i int) string { return *s }), func(s string, i int) bool { return comparison.ValueContainsSeason(s) })
}

func (m *BaseAnime) GetStartYearSafe() int {
	if m == nil || m.StartDate == nil || m.StartDate.Year == nil {
		return 0
	}
	return *m.StartDate.Year
}

func (m *BaseAnime) IsMovie() bool {
	if m == nil {
		return false
	}
	if m.Format == nil {
		return false
	}

	return *m.Format == MediaFormatMovie
}

func (m *BaseAnime) IsFinished() bool {
	if m == nil {
		return false
	}
	if m.Status == nil {
		return false
	}

	return *m.Status == MediaStatusFinished
}

func (m *BaseAnime) GetAllTitles() []*string {
	titles := make([]*string, 0)
	if m.HasRomajiTitle() {
		titles = append(titles, m.Title.Romaji)
	}
	if m.HasEnglishTitle() {
		titles = append(titles, m.Title.English)
	}
	if m.HasSynonyms() && len(m.Synonyms) > 1 {
		titles = append(titles, lo.Filter(m.Synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })...)
	}
	return titles
}

func (m *BaseAnime) GetAllTitlesDeref() []string {
	titles := make([]string, 0)
	if m.HasRomajiTitle() {
		titles = append(titles, *m.Title.Romaji)
	}
	if m.HasEnglishTitle() {
		titles = append(titles, *m.Title.English)
	}
	if m.HasSynonyms() && len(m.Synonyms) > 1 {
		syn := lo.Filter(m.Synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })
		for _, s := range syn {
			titles = append(titles, *s)
		}
	}
	return titles
}

func (m *BaseAnime) GetMainTitles() []*string {
	titles := make([]*string, 0)
	if m.HasRomajiTitle() {
		titles = append(titles, m.Title.Romaji)
	}
	if m.HasEnglishTitle() {
		titles = append(titles, m.Title.English)
	}
	return titles
}

func (m *BaseAnime) GetMainTitlesDeref() []string {
	titles := make([]string, 0)
	if m.HasRomajiTitle() {
		titles = append(titles, *m.Title.Romaji)
	}
	if m.HasEnglishTitle() {
		titles = append(titles, *m.Title.English)
	}
	return titles
}

// GetCurrentEpisodeCount returns the current episode number for that media and -1 if it doesn't have one.
// i.e. -1 is returned if the media has no episodes AND the next airing episode is not set.
func (m *BaseAnime) GetCurrentEpisodeCount() int {
	ceil := -1
	if m.Episodes != nil {
		ceil = *m.Episodes
	}
	if m.NextAiringEpisode != nil {
		if m.NextAiringEpisode.Episode > 0 {
			ceil = m.NextAiringEpisode.Episode - 1
		}
	}

	return ceil
}
func (m *BaseAnime) GetCurrentEpisodeCountOrNil() *int {
	n := m.GetCurrentEpisodeCount()
	if n == -1 {
		return nil
	}
	return &n
}

// GetTotalEpisodeCount returns the total episode number for that media and -1 if it doesn't have one
func (m *BaseAnime) GetTotalEpisodeCount() int {
	ceil := -1
	if m.Episodes != nil {
		ceil = *m.Episodes
	}
	return ceil
}

// GetTotalEpisodeCount returns the total episode number for that media and -1 if it doesn't have one
func (m *BaseAnime) GetTotalEpisodeCountOrNil() *int {
	return m.Episodes
}

// GetPossibleSeasonNumber returns the possible season number for that media and -1 if it doesn't have one.
// It looks at the synonyms and returns the highest season number found.
func (m *BaseAnime) GetPossibleSeasonNumber() int {
	if m == nil || m.Synonyms == nil || len(m.Synonyms) == 0 {
		return -1
	}
	titles := lo.Filter(m.Synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })
	if m.HasEnglishTitle() {
		titles = append(titles, m.Title.English)
	}
	if m.HasRomajiTitle() {
		titles = append(titles, m.Title.Romaji)
	}
	seasons := lo.Map(titles, func(s *string, i int) int { return comparison.ExtractSeasonNumber(*s) })
	return lo.Max(seasons)
}

func (m *BaseAnime) HasEnglishTitle() bool {
	return m.Title.English != nil
}

func (m *BaseAnime) HasRomajiTitle() bool {
	return m.Title.Romaji != nil
}

func (m *BaseAnime) HasSynonyms() bool {
	return m.Synonyms != nil
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func (m *CompleteAnime) GetTitleSafe() string {
	if m.GetTitle().GetEnglish() != nil {
		return *m.GetTitle().GetEnglish()
	}
	if m.GetTitle().GetRomaji() != nil {
		return *m.GetTitle().GetRomaji()
	}
	return "N/A"
}
func (m *CompleteAnime) GetRomajiTitleSafe() string {
	if m.GetTitle().GetRomaji() != nil {
		return *m.GetTitle().GetRomaji()
	}
	if m.GetTitle().GetEnglish() != nil {
		return *m.GetTitle().GetEnglish()
	}
	return "N/A"
}

func (m *CompleteAnime) GetPreferredTitle() string {
	if m.GetTitle().GetUserPreferred() != nil {
		return *m.GetTitle().GetUserPreferred()
	}
	return m.GetTitleSafe()
}

func (m *CompleteAnime) GetCoverImageSafe() string {
	if m.GetCoverImage().GetExtraLarge() != nil {
		return *m.GetCoverImage().GetExtraLarge()
	}
	if m.GetCoverImage().GetLarge() != nil {
		return *m.GetCoverImage().GetLarge()
	}
	if m.GetBannerImage() != nil {
		return *m.GetBannerImage()
	}
	return ""
}

func (m *CompleteAnime) GetBannerImageSafe() string {
	if m.GetBannerImage() != nil {
		return *m.GetBannerImage()
	}
	return m.GetCoverImageSafe()
}

func (m *CompleteAnime) IsMovieOrSingleEpisode() bool {
	if m == nil {
		return false
	}
	if m.GetTotalEpisodeCount() == 1 {
		return true
	}
	return false
}

func (m *CompleteAnime) IsMovie() bool {
	if m == nil {
		return false
	}
	if m.Format == nil {
		return false
	}

	return *m.Format == MediaFormatMovie
}

func (m *CompleteAnime) IsFinished() bool {
	if m == nil {
		return false
	}
	if m.Status == nil {
		return false
	}

	return *m.Status == MediaStatusFinished
}

func (m *CompleteAnime) GetAllTitles() []*string {
	titles := make([]*string, 0)
	if m.HasRomajiTitle() {
		titles = append(titles, m.Title.Romaji)
	}
	if m.HasEnglishTitle() {
		titles = append(titles, m.Title.English)
	}
	if m.HasSynonyms() && len(m.Synonyms) > 1 {
		titles = append(titles, lo.Filter(m.Synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })...)
	}
	return titles
}

func (m *CompleteAnime) GetAllTitlesDeref() []string {
	titles := make([]string, 0)
	if m.HasRomajiTitle() {
		titles = append(titles, *m.Title.Romaji)
	}
	if m.HasEnglishTitle() {
		titles = append(titles, *m.Title.English)
	}
	if m.HasSynonyms() && len(m.Synonyms) > 1 {
		syn := lo.Filter(m.Synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })
		for _, s := range syn {
			titles = append(titles, *s)
		}
	}
	return titles
}

// GetCurrentEpisodeCount returns the current episode number for that media and -1 if it doesn't have one.
// i.e. -1 is returned if the media has no episodes AND the next airing episode is not set.
func (m *CompleteAnime) GetCurrentEpisodeCount() int {
	ceil := -1
	if m.Episodes != nil {
		ceil = *m.Episodes
	}
	if m.NextAiringEpisode != nil {
		if m.NextAiringEpisode.Episode > 0 {
			ceil = m.NextAiringEpisode.Episode - 1
		}
	}
	return ceil
}

// GetTotalEpisodeCount returns the total episode number for that media and -1 if it doesn't have one
func (m *CompleteAnime) GetTotalEpisodeCount() int {
	ceil := -1
	if m.Episodes != nil {
		ceil = *m.Episodes
	}
	return ceil
}

// GetPossibleSeasonNumber returns the possible season number for that media and -1 if it doesn't have one.
// It looks at the synonyms and returns the highest season number found.
func (m *CompleteAnime) GetPossibleSeasonNumber() int {
	if m == nil || m.Synonyms == nil || len(m.Synonyms) == 0 {
		return -1
	}
	titles := lo.Filter(m.Synonyms, func(s *string, i int) bool { return comparison.ValueContainsSeason(*s) })
	if m.HasEnglishTitle() {
		titles = append(titles, m.Title.English)
	}
	if m.HasRomajiTitle() {
		titles = append(titles, m.Title.Romaji)
	}
	seasons := lo.Map(titles, func(s *string, i int) int { return comparison.ExtractSeasonNumber(*s) })
	return lo.Max(seasons)
}

func (m *CompleteAnime) HasEnglishTitle() bool {
	return m.Title.English != nil
}

func (m *CompleteAnime) HasRomajiTitle() bool {
	return m.Title.Romaji != nil
}

func (m *CompleteAnime) HasSynonyms() bool {
	return m.Synonyms != nil
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var EdgeNarrowFormats = []MediaFormat{MediaFormatTv, MediaFormatTvShort}
var EdgeBroaderFormats = []MediaFormat{MediaFormatTv, MediaFormatTvShort, MediaFormatOna, MediaFormatOva, MediaFormatMovie, MediaFormatSpecial}

func (m *CompleteAnime) FindEdge(relation string, formats []MediaFormat) (*BaseAnime, bool) {
	if m.GetRelations() == nil {
		return nil, false
	}

	edges := m.GetRelations().GetEdges()

	for _, edge := range edges {

		if edge.GetRelationType().String() == relation {
			for _, fm := range formats {
				if fm.String() == edge.GetNode().GetFormat().String() {
					return edge.GetNode(), true
				}
			}
		}

	}
	return nil, false
}

func (e *CompleteAnime_Relations_Edges) IsBroadRelationFormat() bool {
	if e.GetNode() == nil {
		return false
	}
	if e.GetNode().GetFormat() == nil {
		return false
	}
	for _, fm := range EdgeBroaderFormats {
		if fm.String() == e.GetNode().GetFormat().String() {
			return true
		}
	}
	return false
}
func (e *CompleteAnime_Relations_Edges) IsNarrowRelationFormat() bool {
	if e.GetNode() == nil {
		return false
	}
	if e.GetNode().GetFormat() == nil {
		return false
	}
	for _, fm := range EdgeNarrowFormats {
		if fm.String() == e.GetNode().GetFormat().String() {
			return true
		}
	}
	return false
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func (m *CompleteAnime) ToBaseAnime() *BaseAnime {
	if m == nil {
		return nil
	}

	var trailer *BaseAnime_Trailer
	if m.GetTrailer() != nil {
		trailer = &BaseAnime_Trailer{
			ID:        m.GetTrailer().GetID(),
			Site:      m.GetTrailer().GetSite(),
			Thumbnail: m.GetTrailer().GetThumbnail(),
		}
	}

	var nextAiringEpisode *BaseAnime_NextAiringEpisode
	if m.GetNextAiringEpisode() != nil {
		nextAiringEpisode = &BaseAnime_NextAiringEpisode{
			AiringAt:        m.GetNextAiringEpisode().GetAiringAt(),
			TimeUntilAiring: m.GetNextAiringEpisode().GetTimeUntilAiring(),
			Episode:         m.GetNextAiringEpisode().GetEpisode(),
		}
	}

	var startDate *BaseAnime_StartDate
	if m.GetStartDate() != nil {
		startDate = &BaseAnime_StartDate{
			Year:  m.GetStartDate().GetYear(),
			Month: m.GetStartDate().GetMonth(),
			Day:   m.GetStartDate().GetDay(),
		}
	}

	var endDate *BaseAnime_EndDate
	if m.GetEndDate() != nil {
		endDate = &BaseAnime_EndDate{
			Year:  m.GetEndDate().GetYear(),
			Month: m.GetEndDate().GetMonth(),
			Day:   m.GetEndDate().GetDay(),
		}
	}

	return &BaseAnime{
		ID:              m.GetID(),
		IDMal:           m.GetIDMal(),
		SiteURL:         m.GetSiteURL(),
		Format:          m.GetFormat(),
		Episodes:        m.GetEpisodes(),
		Status:          m.GetStatus(),
		Synonyms:        m.GetSynonyms(),
		BannerImage:     m.GetBannerImage(),
		Season:          m.GetSeason(),
		SeasonYear:      m.GetSeasonYear(),
		Type:            m.GetType(),
		IsAdult:         m.GetIsAdult(),
		CountryOfOrigin: m.GetCountryOfOrigin(),
		Genres:          m.GetGenres(),
		Duration:        m.GetDuration(),
		Description:     m.GetDescription(),
		MeanScore:       m.GetMeanScore(),
		Trailer:         trailer,
		Title: &BaseAnime_Title{
			UserPreferred: m.GetTitle().GetUserPreferred(),
			Romaji:        m.GetTitle().GetRomaji(),
			English:       m.GetTitle().GetEnglish(),
			Native:        m.GetTitle().GetNative(),
		},
		CoverImage: &BaseAnime_CoverImage{
			ExtraLarge: m.GetCoverImage().GetExtraLarge(),
			Large:      m.GetCoverImage().GetLarge(),
			Medium:     m.GetCoverImage().GetMedium(),
			Color:      m.GetCoverImage().GetColor(),
		},
		StartDate:         startDate,
		EndDate:           endDate,
		NextAiringEpisode: nextAiringEpisode,
	}
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func (m *AnimeListEntry) GetProgressSafe() int {
	if m == nil {
		return 0
	}
	if m.Progress == nil {
		return 0
	}
	return *m.Progress
}

func (m *AnimeListEntry) GetScoreSafe() float64 {
	if m == nil {
		return 0
	}
	if m.Score == nil {
		return 0
	}
	return *m.Score
}

func (m *AnimeListEntry) GetRepeatSafe() int {
	if m == nil {
		return 0
	}
	if m.Repeat == nil {
		return 0
	}
	return *m.Repeat
}

func (m *AnimeListEntry) GetStatusSafe() MediaListStatus {
	if m == nil {
		return ""
	}
	if m.Status == nil {
		return ""
	}
	return *m.Status
}
