import ForgejoKit
import SwiftUI

struct NotificationsOverviewView: View {
    @State private var authService: AuthenticationService
    @State private var pagination = PaginationState<NotificationThread>()
    @State private var statusFilter: String = "unread"
    @Environment(NavigationState.self) private var navigationState

    private let notificationService: NotificationService?

    init(authService: AuthenticationService) {
        self.authService = authService
        notificationService = authService.client.map { NotificationService(client: $0) }

        let state = PaginationState<NotificationThread>()
        state.cacheName = "notifications"
        state.loadFromCache()
        _pagination = State(initialValue: state)
    }

    private var statusTypes: [String] {
        switch statusFilter {
        case "unread": ["unread"]
        case "read": ["read"]
        default: ["unread", "read"]
        }
    }

    private var emptyStateDescription: String {
        if statusFilter == "all" {
            return "There are no notifications"
        }
        return "There are no \(statusFilter) notifications"
    }

    var body: some View {
        @Bindable var pagination = pagination
        VStack(spacing: 0) {
            SegmentedPickerSection(
                title: "Status",
                selection: $statusFilter,
                options: [("Unread", "unread"), ("Read", "read"), ("All", "all")],
                accessibilityIdentifier: "notification-status-picker",
            )

            List {
                if pagination.isLoading, pagination.items.isEmpty {
                    LoadingListSection()
                } else if pagination.items.isEmpty {
                    ContentUnavailableView {
                        Label("No Notifications", systemImage: "bell.slash")
                            .foregroundStyle(statusFilter == "unread" ? .green : .secondary)
                    } description: {
                        Text(statusFilter == "unread" ? "You're all caught up!" : emptyStateDescription)
                    }
                } else {
                    Section {
                        ForEach(pagination.items) { notification in
                            notificationRow(notification)
                                .swipeActions(edge: .leading) {
                                    if notification.unread {
                                        Button {
                                            Task { await markAsRead(notification) }
                                        } label: {
                                            Label("Mark Read", systemImage: "envelope.open")
                                        }
                                        .tint(.blue)
                                    }
                                }
                        }

                        if pagination.hasMore {
                            ProgressView()
                                .frame(maxWidth: .infinity)
                                .listRowBackground(Color.clear)
                                .accessibilityIdentifier("load-more-indicator")
                                .task {
                                    await loadMoreNotifications()
                                }
                        }
                    }
                }
            }
            .listStyle(.insetGrouped)
        }
        .navigationTitle("Notifications")
        .refreshable {
            await reloadNotifications().value
        }
        .task {
            if !pagination.hasLoaded {
                reloadNotifications()
            }
        }
        .onChange(of: statusFilter) {
            reloadNotifications(clearItems: true)
        }
        .onChange(of: navigationState.notificationsRefreshTrigger) {
            reloadNotifications()
        }
        .errorAlert(message: $pagination.errorMessage, isPresented: $pagination.showError)
    }

    @ViewBuilder
    private func notificationRow(_ notification: NotificationThread) -> some View {
        let destination = navigationDestination(for: notification)
        if let destination {
            NavigationLink {
                destination
                    .onAppear { Task { await markReadOnOpen(notification) } }
            } label: {
                NotificationRow(notification: notification)
            }
        } else {
            NotificationRow(notification: notification)
        }
    }

    @ViewBuilder
    private func navigationDestination(for notification: NotificationThread) -> (some View)? {
        switch notification.subject.type {
        case "Issue":
            if let number = subjectNumber(from: notification) {
                IssueDetailView(
                    repository: notification.repository,
                    issueNumber: number,
                    authService: authService,
                )
            }
        case "Pull":
            if let number = subjectNumber(from: notification) {
                PullRequestDetailView(
                    repository: notification.repository,
                    prNumber: number,
                    authService: authService,
                )
            }
        default:
            nil as EmptyView?
        }
    }

    private func subjectNumber(from notification: NotificationThread) -> Int? {
        notificationSubjectNumber(from: notification.subject.url)
            ?? notificationSubjectNumber(from: notification.subject.htmlUrl)
    }

    @discardableResult
    private func reloadNotifications(clearItems: Bool = false) -> Task<Void, Never> {
        guard let notificationService else { return Task {} }
        return pagination.reloadAndCache(clearItems: clearItems) { [self] page, limit in
            try await notificationService.fetchNotifications(
                statusTypes: statusTypes,
                page: page,
                limit: limit,
            )
        }
    }

    private func loadMoreNotifications() async {
        guard let notificationService else { return }
        await pagination.loadMoreAndCache { page, limit in
            try await notificationService.fetchNotifications(
                statusTypes: statusTypes,
                page: page,
                limit: limit,
            )
        }
    }

    private func markAsRead(_ notification: NotificationThread) async {
        await setNotificationRead(notification)
    }

    /// Marks a notification read when the user opens it, mirroring Mail and News.
    /// The row is updated in place rather than removed so navigation into the
    /// detail view is not interrupted while the list is mutating.
    private func markReadOnOpen(_ notification: NotificationThread) async {
        guard notification.unread else { return }
        await setNotificationRead(notification, removeFromUnread: false)
    }

    private func setNotificationRead(_ notification: NotificationThread, removeFromUnread: Bool = true) async {
        guard let notificationService else { return }
        do {
            try await notificationService.markAsRead(id: notification.id)
            withAnimation {
                if removeFromUnread, statusFilter == "unread" {
                    pagination.items.removeAll { $0.id == notification.id }
                } else if let index = pagination.items.firstIndex(where: { $0.id == notification.id }) {
                    pagination.items[index] = NotificationThread(
                        id: notification.id,
                        unread: false,
                        pinned: notification.pinned,
                        updatedAt: notification.updatedAt,
                        url: notification.url,
                        subject: notification.subject,
                        repository: notification.repository,
                    )
                }
            }
            NotificationCenter.default.post(name: .notificationsDidChange, object: nil)
        } catch {
            pagination.errorMessage = error.localizedDescription
            pagination.showError = true
        }
    }

    #if DEBUG
        init(preview _: Void, authService: AuthenticationService, notifications: [NotificationThread]) {
            self.authService = authService
            notificationService = nil
            _pagination = State(initialValue: PaginationState(items: notifications))
        }
    #endif
}

private struct NotificationRow: View {
    let notification: NotificationThread

    private var typeIcon: String {
        switch notification.subject.type {
        case "Issue": "exclamationmark.circle"
        case "Pull": "arrow.triangle.pull"
        case "Commit": "circle.dotted.circle"
        case "Repository": "folder"
        default: "bell"
        }
    }

    private var stateColor: Color {
        switch notification.subject.stateValue {
        case .open: .green
        case .closed, .merged: .purple
        default: .secondary
        }
    }

    var body: some View {
        HStack(alignment: .top, spacing: 10) {
            Image(systemName: typeIcon)
                .foregroundStyle(stateColor)
                .font(.body)
                .padding(.top, 2)

            VStack(alignment: .leading, spacing: 4) {
                Text(notification.subject.title)
                    .font(.body)
                    .lineLimit(2)

                HStack(spacing: 4) {
                    Text(notification.repository.fullName)
                        .lineLimit(1)
                        .truncationMode(.middle)
                    Text("·")
                        .foregroundStyle(.tertiary)
                    Text(formatRelativeDate(notification.updatedAt))
                        .fixedSize()
                }
                .font(.caption)
                .foregroundStyle(.secondary)
            }

            Spacer()

            if notification.unread {
                Circle()
                    .fill(.blue)
                    .frame(width: 10, height: 10)
                    .padding(.top, 6)
            }
        }
        .padding(.vertical, 2)
    }
}

#if DEBUG
    #Preview {
        NavigationStack {
            NotificationsOverviewView(
                preview: (),
                authService: .previewDefault,
                notifications: NotificationThread.previewList,
            )
        }
    }
#endif
