import ForgejoKit
import SwiftUI

struct MergedRepositoryListView: View {
    let manager: MultiInstanceManager

    @State private var pagination: PaginationState<TaggedItem<Repository>>
    @AppStorage("repoFilterShowArchived") private var showArchived = true
    @AppStorage("repoFilterShowMirrors") private var showMirrors = true
    @State private var searchText = ""
    @State private var searchTask: Task<Void, Never>?

    init(manager: MultiInstanceManager) {
        self.manager = manager
        let state = PaginationState<TaggedItem<Repository>>()
        state.cacheName = "merged_repos"
        state.dedupeKey = { $0.id }
        state.loadFromCache()
        state.rehydrate(from: manager)
        _pagination = State(initialValue: state)
    }

    private var hasNonDefaultFilters: Bool {
        !showArchived || !showMirrors
    }

    private var filteredItems: [TaggedItem<Repository>] {
        pagination.items.filter { tagged in
            if !showArchived, tagged.item.archived ?? false { return false }
            if !showMirrors, tagged.item.mirror ?? false { return false }
            return true
        }
    }

    var body: some View {
        @Bindable var pagination = pagination
        VStack(spacing: 0) {
            if !manager.failedInstances.isEmpty {
                MergedConnectionWarning(failedInstances: manager.failedInstances)
            }

            List {
                if pagination.isLoading, pagination.items.isEmpty {
                    Section {
                        HStack {
                            Spacer()
                            ProgressView()
                            Spacer()
                        }
                        .listRowBackground(Color.clear)
                    }
                } else if filteredItems.isEmpty {
                    ContentUnavailableView {
                        Label("No Repositories", systemImage: "folder.badge.questionmark")
                            .foregroundStyle(.secondary)
                    } description: {
                        if hasNonDefaultFilters, !pagination.items.isEmpty {
                            Text("All repositories are hidden by your filters.")
                        } else {
                            Text(
                                searchText.isEmpty
                                    ? "No repositories yet."
                                    : "No repositories matching your search.",
                            )
                        }
                    }
                } else {
                    Section {
                        ForEach(filteredItems) { tagged in
                            NavigationLink {
                                RepositoryDetailView(repository: tagged.item, authService: tagged.authService)
                            } label: {
                                MergedRepositoryRow(repository: tagged.item, instanceName: tagged.instanceName)
                            }
                        }

                        if pagination.hasMore {
                            ProgressView()
                                .frame(maxWidth: .infinity)
                                .listRowBackground(Color.clear)
                                .accessibilityIdentifier("load-more-indicator")
                                .task {
                                    await loadMore()
                                }
                        }
                    }
                }
            }
            .listStyle(.insetGrouped)
            .accessibilityIdentifier("repo-list")
            .refreshable {
                await reloadItems().value
            }
        }
        .navigationTitle("Repositories")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Menu {
                    Section("Visibility") {
                        Toggle(isOn: $showArchived) {
                            Label("Archived", systemImage: "archivebox.fill")
                        }
                        Toggle(isOn: $showMirrors) {
                            Label("Mirrors", systemImage: "arrow.triangle.2.circlepath")
                        }
                    }
                } label: {
                    Image(
                        systemName: hasNonDefaultFilters
                            ? "line.3.horizontal.decrease.circle.fill"
                            : "line.3.horizontal.decrease.circle",
                    )
                }
                .accessibilityIdentifier("filter-menu-button")
            }
        }
        .searchable(text: $searchText, prompt: "Search repositories")
        .task {
            if !pagination.hasLoaded {
                reloadItems()
            }
        }
        .debouncedSearch(text: $searchText, task: $searchTask) {
            await reloadItems().value
        }
        .errorAlert(message: $pagination.errorMessage, isPresented: $pagination.showError)
    }

    @discardableResult
    private func reloadItems(clearItems: Bool = false) -> Task<Void, Never> {
        pagination.reloadAndCache(clearItems: clearItems) { page, limit in
            try await fetchFromAllInstances(page: page, limit: limit)
        }
    }

    private func loadMore() async {
        await pagination.loadMoreAndCache { page, limit in
            try await fetchFromAllInstances(page: page, limit: limit)
        }
    }

    private func fetchFromAllInstances(page: Int, limit: Int) async throws -> [TaggedItem<Repository>] {
        let query = searchText
        let sources = manager.connectionSources()

        let batches = try await withThrowingTaskGroup(of: (Int, [Repository]).self) { group in
            for (index, source) in sources.enumerated() {
                let client = source.client
                group.addTask {
                    let service = RepositoryService(client: client)
                    return try await (index, resilientFetch {
                        if query.isEmpty {
                            try await service.fetchUserRepositories(page: page, limit: limit)
                        } else {
                            try await service.searchRepositories(query: query, page: page, limit: limit)
                        }
                    })
                }
            }
            return try await group.reduce(into: [(Int, [Repository])]()) { $0.append($1) }
        }

        return TaggedItem.mergeAndDeduplicate(batches: batches, sources: sources)
            .sorted { ($0.item.updatedAt ?? .distantPast) > ($1.item.updatedAt ?? .distantPast) }
    }
}

private struct MergedRepositoryRow: View {
    let repository: Repository
    var instanceName: String?

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    HStack(spacing: 6) {
                        Text(repository.name)
                            .font(.title3.bold())
                        if let instanceName {
                            InstanceBadge(name: instanceName)
                        }
                    }

                    if let description = repository.description {
                        Text(description)
                            .font(.subheadline)
                            .foregroundStyle(.secondary)
                            .lineLimit(2)
                    }
                }

                Spacer()

                if repository.private ?? false {
                    RepoBadge(title: "Private", systemImage: "lock.fill")
                }

                if repository.archived ?? false {
                    RepoBadge(title: "Archived", systemImage: "archivebox.fill")
                }

                if repository.mirror ?? false {
                    RepoBadge(title: "Mirror", systemImage: "arrow.triangle.2.circlepath")
                }
            }

            HStack(spacing: 12) {
                if let language = repository.language {
                    Text(language)
                        .padding(.horizontal, 6)
                        .padding(.vertical, 2)
                        .glassEffect(.regular.tint(colorForLanguage(language)))
                }

                Label("\(repository.starsCount ?? 0)", systemImage: "star.fill")
                Label("\(repository.forksCount ?? 0)", systemImage: "tuningfork")
            }
            .font(.caption)
            .foregroundStyle(.secondary)
            .lineLimit(1)

            if let updatedAt = repository.updatedAt {
                Text(formatRelativeDate(updatedAt))
                    .font(.caption2)
                    .foregroundStyle(.tertiary)
            }
        }
        .padding(.vertical, 4)
    }
}
