//
//  AVAvatar.swift
//  Allovoisins
//
//  Created by Florian Baudin on 15/03/2023.
//  Copyright © 2023 AlloVoisins. All rights reserved.
//

import SwiftUI

enum AVOnlineState {
    case online
    case recently
    case offline
    case hourlyDispo
}

class AVOnlineStatus: ObservableObject {
    @Published var state: AVOnlineState!
    @Published var lastConnection: Int?

    init(state: AVOnlineState, lastConnection: Int? = nil) {
        self.state = state
        self.lastConnection = lastConnection
    }

    init(from lastOnlineMinutes: Int?, hourlyDispo: Bool = false) {
        guard let lastOnlineMinutes else {
            self.state = .offline
            self.lastConnection = nil
            return
        }

        switch lastOnlineMinutes {
        case 0 ... 5:
            self.state = .online
            self.lastConnection = lastOnlineMinutes
        case 5 ..< 60:
            self.state = .recently
            self.lastConnection = lastOnlineMinutes
        default:
            self.state = .offline
            self.lastConnection = nil
        }

        guard hourlyDispo else { return }
        self.state = .hourlyDispo
    }

    static func online() -> AVOnlineStatus {
        return AVOnlineStatus(state: .online, lastConnection: .zero)
    }
}

public class AVAvatarModel: ObservableObject {
    enum Source {
        case profile
        case other
    }

    @Published var onlineStatus: AVOnlineStatus
    @Published var avatarString: String?

    var isNewUser: Bool
    var isPro: Bool
    var sizing: AVAvatarSizing
    var bordered: Bool
    var source: Source

    init(avatarString: String?, isNew: Bool = false, isPro: Bool = false, onlineStatus: AVOnlineStatus, sizing: AVAvatarSizing, bordered: Bool = false, source: Source = .other) {
        self.avatarString = avatarString
        self.isNewUser = isNew
        self.isPro = isPro
        self.onlineStatus = onlineStatus
        self.sizing = sizing
        self.bordered = bordered
        self.source = source
    }

    static func empty() -> AVAvatarModel {
        return AVAvatarModel(avatarString: nil,
                             isNew: false,
                             isPro: false,
                             onlineStatus: AVOnlineStatus(state: .offline),
                             sizing: .M)
    }
}

struct AVAvatar: View {
    @ObservedObject var model: AVAvatarModel

    var body: (some View)? {
        ZStack {
            Color.clear
            self.avatarImage
                .overlay(avatarBorder)
                .overlay(newUserOverlay, alignment: .topLeading)
                .overlay(onlineCircle, alignment: .bottomTrailing)
                .frame(width: self.model.sizing.size,
                       height: self.model.sizing.size)
        }
    }

    private var avatarBorder: (some View)? {
        Group {
            if self.model.onlineStatus.state == .hourlyDispo && self.model.source != .profile {
                Circle()
                    .stroke(Color.yellow, lineWidth: 3)
            } else if self.model.bordered {
                if self.model.source == .profile {
                    Circle()
                        .stroke(.white, lineWidth: 6)
                } else {
                    Circle()
                        .stroke(.white, lineWidth: 4)
                }

            } else {
                EmptyView()
            }
        }
    }

    private var avatarImage: (some View)? {
        if #available(iOS 15.0, *) {
            return AsyncImage(
                url: URL(string: self.model.avatarString ?? "")
            ) { image in
                image
                    .resizable()
                    .clipShape(Circle())
            } placeholder: {
                Image("AVAsset.Icon.DEFAULT_AVATAR")
                    .resizable()
                    .clipShape(Circle())
            }
        } else {
            return Image("")
                .resizable()
                .clipShape(Circle())
        }
    }

    private var newUserOverlay: (some View)? {
        Group {
            if self.model.isNewUser && [.online, .offline].contains(self.model.onlineStatus.state) {
                Image("")
                    .resizable()
                    .clipShape(Circle())
                    .rotationEffect(Angle(degrees: 30))
            } else {
                EmptyView()
            }
        }
    }

    private var onlineCircle: (some View)? {
        Group {
            switch self.model.onlineStatus.state {
            case .online:
                let sizing = self.model.sizing.onlineSize
                Circle()
                    .strokeBorder(Color.white, lineWidth: sizing.borderWidth)
                    .background(Circle().foregroundColor(Color.white))
                    .frame(width: sizing.height, height: sizing.height)
                    .offset(x: sizing.padding.trailing, y: sizing.padding.bottom)

            case .recently:
                let recentlyText = "\(self.model.onlineStatus.lastConnection ?? 1) min"
                let sizing = self.model.sizing.recentlySize
                SQText(recentlyText)
                    .padding(EdgeInsets(top: 0, leading: 3, bottom: 0, trailing: 2))
                    .foregroundColor(Color.white)
                    .background(Color.white)
                    .cornerRadius(sizing.height / 2)
                    .overlay(
                        RoundedRectangle(cornerRadius: sizing.height / 2)
                            .stroke(.white, lineWidth: sizing.borderWidth)
                    )
                    .frame(height: sizing.height)

            case .hourlyDispo:
                let sizing = self.model.sizing.onlineSize
                Image("")
                    .resizable()
                    .overlay(RoundedRectangle(cornerRadius: sizing.height / 2)
                        .stroke(.white, lineWidth: sizing.borderWidth))
                    .background(Circle().foregroundColor(Color(.yellow)))
                    .frame(width: sizing.height, height: sizing.height)
                    .offset(x: sizing.padding.trailing, y: sizing.padding.bottom)

            default:
                EmptyView()
            }
        }
    }
}

struct AVAvatar_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: true,
                onlineStatus: AVOnlineStatus(state: .offline), sizing: AVAvatarSizing.XS
            ))
            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: false,
                onlineStatus: AVOnlineStatus(state: .offline, lastConnection: 59), sizing: AVAvatarSizing.XS
            ))

            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: true,
                onlineStatus: AVOnlineStatus(state: .online), sizing: AVAvatarSizing.S
            ))
            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: false,
                onlineStatus: AVOnlineStatus(state: .recently, lastConnection: 59), sizing: AVAvatarSizing.S
            ))

            AVAvatar(model: AVAvatarModel(
                avatarString: nil,
                isNew: true,
                onlineStatus: AVOnlineStatus(state: .online), sizing: AVAvatarSizing.M
            ))
            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: false,
                isPro: true,
                onlineStatus: AVOnlineStatus(state: .recently,
                                             lastConnection: 59), sizing: AVAvatarSizing.M, bordered: true
            ))

            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: true,
                onlineStatus: AVOnlineStatus(state: .hourlyDispo), sizing: AVAvatarSizing.L, bordered: true
            ))
            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: false,
                onlineStatus: AVOnlineStatus(state: .recently, lastConnection: 59), sizing: AVAvatarSizing.L
            ))

            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: true,
                onlineStatus: AVOnlineStatus(state: .online), sizing: AVAvatarSizing.XL
            ))
            AVAvatar(model: AVAvatarModel(
                avatarString: "https://preprod.allovoisins.com//uploads/u/avatars/9/b/9/9b93e04d6d_4681618_l.jpg",
                isNew: false,
                isPro: true,
                onlineStatus: AVOnlineStatus(state: .recently,
                                             lastConnection: 59), sizing: AVAvatarSizing.XL
            ))
        }
    }
}

enum AVAvatarSizing {
    case XXS
    case XS
    case S
    case M
    case L
    case XL
    case XXL

    var size: CGFloat {
        switch self {
        case .XXS:
            return 26
        case .XS:
            return 32
        case .S:
            return 40
        case .M:
            return 48
        case .L:
            return 64
        case .XL:
            return 90
        case .XXL:
            return 160
        }
    }

    struct OnlineStateSize {
        var height: CGFloat
        var fontSize: CGFloat? = 0
        var borderWidth: CGFloat
        var padding: EdgeSpace

        struct EdgeSpace {
            var bottom: CGFloat
            var trailing: CGFloat
        }
    }

    var onlineSize: OnlineStateSize {
        switch self {
        case .XXS, .XS:
            return OnlineStateSize(height: .zero,
                                   borderWidth: 0,
                                   padding: OnlineStateSize.EdgeSpace(bottom: 0,
                                                                      trailing: 0))
        case .S, .M:
            return OnlineStateSize(height: 14,
                                   borderWidth: 2,
                                   padding: OnlineStateSize.EdgeSpace(bottom: 0,
                                                                      trailing: 0))
        case .L:
            return OnlineStateSize(height: 18,
                                   borderWidth: 3,
                                   padding: OnlineStateSize.EdgeSpace(bottom: 0,
                                                                      trailing: 0))
        case .XL:
            return OnlineStateSize(height: 18,
                                   borderWidth: 3,
                                   padding: OnlineStateSize.EdgeSpace(bottom: -3,
                                                                      trailing: -3))
        case .XXL:
            return OnlineStateSize(height: 30,
                                   borderWidth: 4,
                                   padding: OnlineStateSize.EdgeSpace(bottom: -8,
                                                                      trailing: -8))
        }
    }

    var recentlySize: OnlineStateSize {
        switch self {
        case .XXS, .XS:
            return OnlineStateSize(height: .zero,
                                   borderWidth: 0,
                                   padding: OnlineStateSize.EdgeSpace(bottom: 0,
                                                                      trailing: 0))
        case .S:
            return OnlineStateSize(height: 13,
                                   fontSize: 9,
                                   borderWidth: 2,
                                   padding: OnlineStateSize.EdgeSpace(bottom: 2,
                                                                      trailing: 2))
        case .M:
            return OnlineStateSize(height: 17,
                                   fontSize: 11,
                                   borderWidth: 2,
                                   padding: OnlineStateSize.EdgeSpace(bottom: 2,
                                                                      trailing: 2))
        case .L, .XL:
            return OnlineStateSize(height: 17,
                                   fontSize: 11,
                                   borderWidth: 3,
                                   padding: OnlineStateSize.EdgeSpace(bottom: -1,
                                                                      trailing: 3))
        case .XXL:
            return OnlineStateSize(height: 30,
                                   fontSize: 18,
                                   borderWidth: 4,
                                   padding: OnlineStateSize.EdgeSpace(bottom: -3,
                                                                      trailing: 0))
        }
    }
}
