//
//  AnimeBehindWidget.swift
//  AniHyou WidgetExtension
//
//  Created by Axel Lopez on 19/09/2023.
//

import WidgetKit
import SwiftUI
import AniListAPI

struct AnimeBehindProvider: TimelineProvider {
    
    func placeholder(in context: Context) -> AnimeBehindEntry {
        AnimeBehindEntry(
            animeList: [],
            date: Date(),
            placeholderText: "Anime with unwatched episodes appear here",
            widgetSize: context.displaySize
        )
    }
    
    func getSnapshot(in context: Context, completion: @escaping (AnimeBehindEntry) -> Void) {
        getAnimeTimeline(context: context, completion: {
            if let entry = $0.entries.first {
                completion(entry)
            } else {
                completion(placeholder(in: context))
            }
        })
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<AnimeBehindEntry>) -> Void) {
        getAnimeTimeline(context: context, completion: completion)
    }
    
    func getAnimeTimeline(context: Context, completion: @escaping (Timeline<AnimeBehindEntry>) -> Void) {
        let now = Date.now
        
        let userId = UserDefaults(suiteName: ANIHYOU_GROUP)?.integer(forKey: USER_ID_KEY) ?? 0

        if userId == 0 {
            let entry = AnimeBehindEntry(
                animeList: [],
                date: now,
                placeholderText: "Login to use this widget",
                widgetSize: context.displaySize
            )
            completion(Timeline(entries: [entry], policy: .never))
        }
        
        var nextUpdateDate = Calendar.current.date(byAdding: .hour, value: 12, to: now)!
        
        Task {
            do {
                let result = try await Network.shared.apollo.fetch(
                    query: UserCurrentAnimeListQuery(
                        userId: .some(Int32(userId)),
                        sort: .some([.case(.updatedTimeDesc)])
                    ),
                    cachePolicy: .networkOnly
                )
                if let mediaList = result.data?.page?.mediaList {
                    var tempList = transformToAnimeBehindList(mediaList)
                    
                    let maxItems = context.family.maxMediaListItems
                    tempList = Array(tempList.prefix(maxItems))
                    
                    let entry = AnimeBehindEntry(
                        animeList: tempList,
                        date: nextUpdateDate,
                        placeholderText: nil,
                        widgetSize: context.displaySize
                    )
                    completion(Timeline(entries: [entry], policy: .after(nextUpdateDate)))
                }
            } catch {
                print(error)
                nextUpdateDate = Calendar.current.date(byAdding: .hour, value: 1, to: now)!
                let entry = AnimeBehindEntry(
                    animeList: [],
                    date: nextUpdateDate,
                    placeholderText: "Error updating:\n\(error)",
                    widgetSize: context.displaySize
                )
                completion(Timeline(entries: [entry], policy: .after(nextUpdateDate)))
            }
        }
    }
    
    func transformToAnimeBehindList(_ list: [UserCurrentAnimeListQuery.Data.Page.MediaList?]
    ) -> [UserCurrentAnimeListQuery.Data.Page.MediaList?] {
        list
            .filter {
                // filter only airing anime that has episodes behind
                if let airing = $0?.media?.nextAiringEpisode {
                    return ($0?.progress ?? 0) < airing.episode - 1
                }
                return false
            }
            .sorted {
                //sort by number of episodes behind
                let diff1 = ($0?.media?.nextAiringEpisode?.episode ?? 0) - ($0?.progress ?? 0)
                let diff2 = ($1?.media?.nextAiringEpisode?.episode ?? 0) - ($1?.progress ?? 0)
                
                return diff1 > diff2
            }
    }
}

struct AnimeBehindEntry: BaseEntry {
    let animeList: [UserCurrentAnimeListQuery.Data.Page.MediaList?]
    var date: Date
    var placeholderText: String?
    var widgetSize: CGSize
}

struct AnimeBehindWidgetEntryView: View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    @AppStorage(ACCENT_COLOR_KEY, store: UserDefaults(suiteName: ANIHYOU_GROUP)) private var accentColor = ANIHYOU_COLOR
    var tintColor: Color {
        Color(hex: accentColor) ?? .accentColor
    }
    
    let entry: AnimeBehindProvider.Entry
    
    var aligment: Alignment {
        if entry.placeholderText != nil || entry.animeList.isEmpty {
            return .center
        } else {
            if entry.animeList.count >= family.maxMediaListItems {
                return .center
            } else {
                return .top
            }
        }
    }
    var paddingLenght: CGFloat? {
        if aligment == .top {
            return nil
        } else {
            return 0
        }
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            content
        }//:VStack
        .containerBackground(.background, for: .widget)
        .padding(.vertical, paddingLenght)
        .frame(
            width: entry.widgetSize.width,
            height: entry.widgetSize.height,
            alignment: aligment
        )
        .tint(tintColor)
    }
    
    @ViewBuilder
    var content: some View {
        if let placeholder = entry.placeholderText {
            Text(placeholder)
                .multilineTextAlignment(.center)
        } else if entry.animeList.isEmpty {
            Text("Congratulations, you are up to date!\n(๑>◡<๑)")
                .multilineTextAlignment(.center)
        } else {
            ForEach(Array(entry.animeList.enumerated()), id: \.element?.id) { index, item in
                if 
                    let item,
                    let nextAiringEpisode = item.media?.nextAiringEpisode
                {
                    let episodesBehind = (nextAiringEpisode.episode - 1) - (item.progress ?? 0)
                    if episodesBehind > 0 {
                        Link(destination: URL(string: "anihyou://media/\(item.mediaId)")!) {
                            HStack(spacing: 0) {
                                VStack(alignment: .leading, spacing: 0) {
                                    Text(item.media?.title?.userPreferred ?? "")
                                        .font(.system(size: 14))
                                        .lineLimit(1)
                                        .padding(.horizontal)
                                    
                                    Text("^[\(episodesBehind) \("episode")](inflect: true) behind")
                                        .font(.system(size: 12))
                                        .lineLimit(1)
                                        .foregroundStyle(tintColor)
                                        .padding(.horizontal)
                                        .invalidatableContent()
                                }
                                Spacer()
                                Button("+1", intent: SetProgressIntent(
                                    mediaId: item.mediaId,
                                    entryId: item.id,
                                    progress: (item.progress ?? 0) + 1
                                ))
                                .padding(.horizontal)
                            }
                            if (index + 1) < entry.animeList.count {
                                Divider()
                                    .padding(.leading)
                            }
                        }//:Link
                    }
                }
            }//:ForEach
        }
    }
}

struct AnimeBehindWidget: Widget {
    let kind: String = ANIME_BEHIND_WIDGET_KIND

    var body: some WidgetConfiguration {

        StaticConfiguration(kind: kind, provider: AnimeBehindProvider()) { entry in
            AnimeBehindWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Anime Behind")
        .description("Shows the animes with behind episodes in your list.")
        .supportedFamilies([.systemMedium, .systemLarge])
    }
}

#Preview(
    as: .systemMedium,
    widget: { AnimeBehindWidget() },
    timeline: {
        AnimeBehindEntry(
            animeList: [],
            date: Date(),
            placeholderText: "Anime with unwatched episodes appear here",
            widgetSize: CGSize(width: 291, height: 141)
        )
    }
)
