import ForgejoKit
import SwiftUI

struct RepositoryListView: View {
    @State private var authService: AuthenticationService
    @State private var pagination = PaginationState<Repository>()
    @State private var starredRepoIds: Set<Int> = []
    @State private var selectedFilter: RepositoryFilter = .all
    @AppStorage("repoFilterShowArchived") private var showArchived = true
    @AppStorage("repoFilterShowMirrors") private var showMirrors = true
    @State private var searchText = ""
    @State private var searchTask: Task<Void, Never>?
    @State private var starringInFlight: Set<Int> = []

    private let repositoryService: RepositoryService?

    init(authService: AuthenticationService) {
        self.authService = authService
        repositoryService = authService.client.map { RepositoryService(client: $0) }

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

    enum RepositoryFilter: String, CaseIterable {
        case all = "All"
        case starred = "Starred"

        var icon: String {
            switch self {
            case .all: "folder.fill"
            case .starred: "star.fill"
            }
        }
    }

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

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

    var body: some View {
        @Bindable var pagination = pagination
        VStack(spacing: 0) {
            SegmentedPickerSection(
                title: "Filter",
                selection: $selectedFilter,
                options: RepositoryFilter.allCases.map { ($0.rawValue, $0) },
                accessibilityIdentifier: "repo-filter-picker",
            )

            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) { repo in
                            NavigationLink {
                                RepositoryDetailView(repository: repo, authService: authService)
                            } label: {
                                RepositoryRow(
                                    repository: repo,
                                    isStarred: starredRepoIds.contains(repo.id),
                                    isStarring: starringInFlight.contains(repo.id),
                                ) {
                                    await toggleStar(for: repo)
                                }
                            }
                        }

                        if pagination.hasMore {
                            ProgressView()
                                .frame(maxWidth: .infinity)
                                .listRowBackground(Color.clear)
                                .accessibilityIdentifier("load-more-indicator")
                                .task {
                                    await loadMore()
                                }
                        }
                    }
                }
            }
            .listStyle(.insetGrouped)
            .accessibilityIdentifier("repo-list")
            .refreshable {
                await reloadRepositories().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 {
                reloadRepositories()
            }
        }
        .onChange(of: selectedFilter) { _, _ in
            reloadRepositories(clearItems: true)
        }
        .debouncedSearch(text: $searchText, task: $searchTask) {
            await reloadRepositories().value
        }
        .errorAlert(message: $pagination.errorMessage, isPresented: $pagination.showError)
    }

    @discardableResult
    private func reloadRepositories(clearItems: Bool = false) -> Task<Void, Never> {
        guard let repositoryService else { return Task {} }
        return pagination.reloadAndCache(clearItems: clearItems) { [self] page, limit in
            async let repos = fetchRepositories(service: repositoryService, page: page, limit: limit)
            async let _ = loadStarredIds()
            return try await repos
        }
    }

    private func loadMore() async {
        guard let repositoryService else { return }
        await pagination.loadMoreAndCache { [self] page, limit in
            try await fetchRepositories(service: repositoryService, page: page, limit: limit)
        }
    }

    private func fetchRepositories(service: RepositoryService, page: Int, limit: Int) async throws -> [Repository] {
        if searchText.isEmpty {
            switch selectedFilter {
            case .all:
                return try await service.fetchUserRepositories(page: page, limit: limit)
            case .starred:
                return try await service.fetchStarredRepositories(page: page, limit: limit)
            }
        } else {
            let results = try await service.searchRepositories(query: searchText, page: page, limit: limit)
            if selectedFilter == .starred {
                return results.filter { starredRepoIds.contains($0.id) }
            }
            return results
        }
    }

    private func loadStarredIds() async {
        guard let repositoryService else { return }
        if let ids = try? await repositoryService.fetchAllStarredRepoIds() {
            starredRepoIds = ids
        }
    }

    private func toggleStar(for repo: Repository) async {
        guard let repositoryService else { return }
        guard !starringInFlight.contains(repo.id) else { return }
        let parts = repo.fullName.split(separator: "/")
        guard parts.count == 2 else { return }
        let owner = String(parts[0])
        let repoName = String(parts[1])

        starringInFlight.insert(repo.id)
        let isCurrentlyStarred = starredRepoIds.contains(repo.id)

        // Optimistic update
        if isCurrentlyStarred {
            starredRepoIds.remove(repo.id)
        } else {
            starredRepoIds.insert(repo.id)
        }

        do {
            if isCurrentlyStarred {
                try await repositoryService.unstarRepository(owner: owner, repo: repoName)
            } else {
                try await repositoryService.starRepository(owner: owner, repo: repoName)
            }
            if selectedFilter == .starred {
                reloadRepositories(clearItems: true)
            }
        } catch {
            // Revert optimistic update
            if isCurrentlyStarred {
                starredRepoIds.insert(repo.id)
            } else {
                starredRepoIds.remove(repo.id)
            }
            pagination.errorMessage = error.localizedDescription
            pagination.showError = true
        }
        starringInFlight.remove(repo.id)
    }
}

struct RepositoryRow: View {
    let repository: Repository
    let isStarred: Bool
    var isStarring: Bool = false
    let onToggleStar: () async -> Void

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    Text(repository.name)
                        .font(.headline)

                    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")
                }

                Button {
                    Task { await onToggleStar() }
                } label: {
                    Image(systemName: isStarred ? "star.fill" : "star")
                        .foregroundStyle(isStarred ? .yellow : .secondary)
                        .font(.body)
                }
                .buttonStyle(.borderless)
                .disabled(isStarring)
                .accessibilityLabel(isStarred ? "Unstar repository" : "Star repository")
                .accessibilityIdentifier("star-button")
            }

            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")

                if (repository.openIssuesCount ?? 0) > 0 {
                    Label("\(repository.openIssuesCount ?? 0)", systemImage: "exclamationmark.circle")
                }
            }
            .font(.caption)
            .foregroundStyle(.secondary)
            .lineLimit(1)

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

#if DEBUG
    #Preview {
        RepositoryListView(authService: .previewDefault)
    }
#endif
