//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//

import CompoundDesignTokens
import SFSafeSymbols
import SwiftUI

/// The main label style used in the leading section of `ListRow`.
struct ListRowLabelStyle: LabelStyle {
    let iconAlignment: VerticalAlignment
    
    func makeBody(configuration: Configuration) -> some View {
        HStack(alignment: iconAlignment, spacing: 16) {
            configuration.icon
            configuration.title
        }
    }
}

/// The label style used in `ListRow` for centred buttons.
struct ListRowCenteredLabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack(alignment: .center, spacing: 8) {
            configuration.icon
            configuration.title
        }
    }
}

/// The label style used in the leading section of `ListRow` that show an avatar as the icon.
///
/// Unlike the other styles, this one sizes the avatar internally.
struct ListRowAvatarLabelStyle: LabelStyle {
    @ScaledMetric private var avatarSize = 32.0
    
    func makeBody(configuration: Configuration) -> some View {
        HStack(alignment: .center, spacing: 16) {
            configuration.icon
                .frame(width: avatarSize, height: avatarSize)
                .padding(.vertical, -5) // Don't allow the avatar to size the row.
            configuration.title
        }
    }
}

public struct ListRowLabel<Icon: View>: View {
    @Environment(\.isEnabled) private var isEnabled
    @Environment(\.lineLimit) private var lineLimit
    @ScaledMetric private var iconSize = 30.0
    
    var title: String?
    var status: String?
    var description: String?
    var icon: Icon?
    
    var role: Role?
    public enum Role {
        /// A role that indicates a destructive action.
        case destructive
        /// A role that indicates an error.
        ///
        /// The label should contain a description when using this role.
        case error
    }
    
    var iconAlignment: VerticalAlignment = .top
    var hideIconBackground = false
    
    enum Layout { case `default`, centered, avatar }
    var layout: Layout = .default
    
    var titleColor: Color {
        guard isEnabled else { return .compound.textDisabled }
        return role == .destructive ? .compound.textCriticalPrimary : .compound.textPrimary
    }

    var titleLineLimit: Int? {
        layout == .avatar ? 1 : lineLimit
    }
    
    var statusColor: Color {
        isEnabled ? .compound.textSecondary : .compound.textDisabled
    }
    
    var descriptionColor: Color {
        isEnabled ? .compound.textSecondary : .compound.textDisabled
    }

    var descriptionLineLimit: Int? {
        guard layout == .avatar else { return lineLimit }
        return role != .error ? 1 : lineLimit
    }
    
    var iconForegroundColor: Color {
        guard isEnabled else { return .compound.iconTertiaryAlpha }
        if role == .destructive { return .compound.iconCriticalPrimary }
        if hideIconBackground {
            return .compound.iconTertiaryAlpha
        } else {
            if #available(iOS 26, *) {
                return .compound.iconSecondary
            } else {
                return .compound.iconPrimary
            }
        }
    }
    
    var iconBackgroundColor: Color {
        if hideIconBackground { return .clear }
        if #available(iOS 26, *) {
            return .clear
        } else {
            guard isEnabled else { return .compound._bgSubtleSecondaryAlpha }
            return role == .destructive ? .compound._bgCriticalSubtleAlpha : .compound._bgSubtleSecondaryAlpha
        }
    }
    
    public var body: some View {
        Group {
            switch layout {
            case .default:
                defaultBody
            case .centered:
                centeredBody
            case .avatar:
                avatarBody
            }
        }
        .padding(.leading, ListRowPadding.horizontal)
        .padding(.vertical, ListRowPadding.vertical)
    }
    
    var defaultBody: some View {
        Label {
            titleAndDescription
        } icon: {
            icon
                .foregroundColor(iconForegroundColor)
                .frame(width: iconSize, height: iconSize)
                .background(iconBackgroundColor)
                .clipShape(RoundedRectangle(cornerRadius: 8))
                .padding(.vertical, -4) // Don't allow the background to size the row.
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
        .labelStyle(ListRowLabelStyle(iconAlignment: iconAlignment))
    }
    
    var centeredBody: some View {
        Label {
            if let title {
                Text(title)
                    .font(.compound.bodyLG)
                    .foregroundColor(titleColor)
            }
        } icon: {
            icon
                .foregroundColor(iconForegroundColor)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .labelStyle(ListRowCenteredLabelStyle())
        .alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
    }
    
    var avatarBody: some View {
        Label {
            titleAndDescription
        } icon: {
            icon // Layout handled by the style.
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
        .labelStyle(ListRowAvatarLabelStyle())
    }
    
    var titleAndDescription: some View {
        VStack(alignment: .leading, spacing: 2) {
            if let title {
                HStack(alignment: .firstTextBaseline, spacing: 8) {
                    Text(title)
                        .font(.compound.bodyLG)
                        .foregroundColor(titleColor)
                        .lineLimit(titleLineLimit)
                    
                    // Status is only available in the avatar init which requires a title,
                    // so no need to worry about the outer `if let` being nil in this instance.
                    if let status {
                        Text(status)
                            .font(.compound.bodySM)
                            .foregroundColor(statusColor)
                            .lineLimit(1)
                    }
                }
            }
            
            if let description {
                HStack(alignment: .top, spacing: 4) {
                    if role == .error {
                        CompoundIcon(\.errorSolid, size: .xSmall, relativeTo: .compound.bodySM)
                            .foregroundStyle(.compound.iconCriticalPrimary)
                    }
                    
                    Text(description)
                        .font(.compound.bodySM)
                        .foregroundColor(descriptionColor)
                        .lineLimit(descriptionLineLimit)
                }
            }
        }
        .accessibilityElement(children: .combine)
    }
    
    // MARK: - Initialisers
    
    public static func `default`(title: String,
                                 description: String? = nil,
                                 icon: Icon,
                                 role: ListRowLabel.Role? = nil,
                                 iconAlignment: VerticalAlignment = .top) -> ListRowLabel {
        ListRowLabel(title: title,
                     description: description,
                     icon: icon,
                     role: role,
                     iconAlignment: iconAlignment)
    }
    
    public static func `default`(title: String,
                                 description: String? = nil,
                                 icon: KeyPath<CompoundIcons, Image>,
                                 role: ListRowLabel.Role? = nil,
                                 iconAlignment: VerticalAlignment = .top) -> ListRowLabel where Icon == CompoundIcon {
        .default(title: title,
                 description: description,
                 icon: CompoundIcon(icon),
                 role: role,
                 iconAlignment: iconAlignment)
    }
    
    public static func `default`(title: String,
                                 description: String? = nil,
                                 systemIcon: SFSymbol,
                                 role: ListRowLabel.Role? = nil,
                                 iconAlignment: VerticalAlignment = .top) -> ListRowLabel where Icon == Image {
        .default(title: title,
                 description: description,
                 icon: Image(systemSymbol: systemIcon),
                 role: role,
                 iconAlignment: iconAlignment)
    }
    
    public static func action(title: String,
                              icon: Icon,
                              role: ListRowLabel.Role? = nil) -> ListRowLabel {
        ListRowLabel(title: title,
                     icon: icon,
                     role: role,
                     hideIconBackground: true)
    }
    
    public static func action(title: String,
                              icon: KeyPath<CompoundIcons, Image>,
                              role: ListRowLabel.Role? = nil) -> ListRowLabel where Icon == CompoundIcon {
        .action(title: title, icon: CompoundIcon(icon), role: role)
    }
    
    public static func action(title: String,
                              systemIcon: SFSymbol,
                              role: ListRowLabel.Role? = nil) -> ListRowLabel where Icon == Image {
        .action(title: title, icon: Image(systemSymbol: systemIcon), role: role)
    }
    
    public static func centeredAction(title: String,
                                      icon: Icon,
                                      role: ListRowLabel.Role? = nil) -> ListRowLabel {
        ListRowLabel(title: title,
                     icon: icon,
                     role: role,
                     hideIconBackground: true,
                     layout: .centered)
    }
    
    public static func centeredAction(title: String,
                                      icon: KeyPath<CompoundIcons, Image>,
                                      role: ListRowLabel.Role? = nil) -> ListRowLabel where Icon == CompoundIcon {
        .centeredAction(title: title, icon: CompoundIcon(icon), role: role)
    }
    
    public static func centeredAction(title: String,
                                      systemIcon: SFSymbol,
                                      role: ListRowLabel.Role? = nil) -> ListRowLabel where Icon == Image {
        .centeredAction(title: title, icon: Image(systemSymbol: systemIcon), role: role)
    }
    
    public static func plain(title: String,
                             description: String? = nil,
                             role: ListRowLabel.Role? = nil) -> ListRowLabel where Icon == EmptyView {
        ListRowLabel(title: title, description: description, role: role, hideIconBackground: true)
    }
    
    public static func description(_ description: String) -> ListRowLabel where Icon == EmptyView {
        ListRowLabel(description: description)
    }
    
    /// A label that displays an avatar as it's icon, such as a user profile row or for a room picker.
    public static func avatar(title: String,
                              status: String? = nil,
                              description: String? = nil,
                              icon: Icon,
                              role: ListRowLabel.Role? = nil) -> ListRowLabel {
        ListRowLabel(title: title,
                     status: status,
                     description: description,
                     icon: icon,
                     role: role,
                     iconAlignment: .center,
                     layout: .avatar)
    }
}

// MARK: - Previews

struct ListRowLabel_Previews: PreviewProvider, TestablePreview {
    static var previews: some View {
        Form {
            Section {
                Group {
                    ListRowLabel.default(title: "Person", icon: Image(systemName: "person"))
                    
                    ListRowLabel.default(title: "Help",
                                         description: "Supporting text",
                                         systemIcon: .questionmarkCircle)
                    
                    ListRowLabel.default(title: "Trash",
                                         icon: Image(systemName: "trash"),
                                         role: .destructive)
                }
                
                Group {
                    ListRowLabel.action(title: "Camera",
                                        icon: Image(systemName: "camera"))
                    
                    ListRowLabel.action(title: "Remove",
                                        icon: Image(systemName: "person.badge.minus"),
                                        role: .destructive)
                    
                    ListRowLabel.centeredAction(title: "Person",
                                                icon: Image(systemName: "person"))
                    ListRowLabel.centeredAction(title: "Remove",
                                                systemIcon: .personBadgeMinus,
                                                role: .destructive)
                }
                
                Group {
                    ListRowLabel.plain(title: "Person")
                    ListRowLabel.plain(title: "Remove",
                                       role: .destructive)
                    ListRowLabel.plain(title: "Plain", description: "Description")
                }
                
                ListRowLabel.description("This is a row in the list, that only contains a description and doesn't have either an icon or a title.")
            }
            .listRowInsets(EdgeInsets())
            
            Section {
                ListRowLabel.avatar(title: "Alice",
                                    description: "@alice:example.com",
                                    icon: Circle().foregroundStyle(.compound.decorativeColors[0].background))
                ListRowLabel.avatar(title: "Alice",
                                    status: "Pending",
                                    description: "@alice:example.com",
                                    icon: Circle().foregroundStyle(.compound.decorativeColors[0].background))
                ListRowLabel.avatar(title: "@bob:idontexist.com",
                                    description: "This user can't be found, so the invite may not be received.",
                                    icon: Circle().foregroundStyle(.compound.decorativeColors[0].background),
                                    role: .error)
            }
            .listRowInsets(EdgeInsets())
            
            Section {
                ListRow(label: .description("This is a row in the list, with a multiline description but it doesn't have either an icon or a title, just this text here."),
                        kind: .label)
            }
        }
        .compoundList()
        .frame(idealHeight: 1000) // Snapshot height
        .previewLayout(.sizeThatFits)
    }
}
