//
//  GIFPickerView.swift
//  damus
//
//  Created by eric on 12/11/25.
//

import SwiftUI
import Kingfisher

struct GIFPickerView: View {
    @Environment(\.dismiss) var dismiss
    let damus_state: DamusState
    let onGIFSelected: (URL) -> Void

    @StateObject private var viewModel: GIFPickerViewModel
    @State private var searchText: String = ""
    @FocusState private var isSearchFocused: Bool

    /// Creates a GIF picker bound to the current Damus state.
    init(damus_state: DamusState, onGIFSelected: @escaping (URL) -> Void) {
        self.damus_state = damus_state
        self.onGIFSelected = onGIFSelected
        _viewModel = StateObject(wrappedValue: GIFPickerViewModel(purple: damus_state.purple))
    }

    var body: some View {
        NavigationView {
            VStack(spacing: 0) {
                SearchInput
                    .padding(.horizontal)
                    .padding(.vertical, 8)

                Divider()

                if viewModel.isLoading && viewModel.gifs.isEmpty {
                    loadingView
                } else if let error = viewModel.error {
                    errorView(error)
                } else if viewModel.gifs.isEmpty {
                    emptyView
                } else {
                    gifGrid
                }
            }
            .navigationTitle(NSLocalizedString("Select GIF", comment: "Title for GIF picker sheet"))
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button(NSLocalizedString("Cancel", comment: "Button to cancel GIF selection")) {
                        dismiss()
                    }
                }
            }
        }
        .task {
            await viewModel.loadFeatured()
        }
        .onChange(of: searchText) { newValue in
            viewModel.search(query: newValue)
        }
    }

    private var SearchInput: some View {
        HStack {
            HStack {
                Image("search")
                    .foregroundColor(.gray)
                TextField(NSLocalizedString("Search KLIPY", comment: "Placeholder for GIF search field"), text: $searchText)
                    .autocorrectionDisabled(true)
                    .textInputAutocapitalization(.never)
                    .focused($isSearchFocused)

                if !searchText.isEmpty {
                    Button(action: { searchText = "" }) {
                        Image(systemName: "xmark.circle.fill")
                            .foregroundColor(.gray)
                    }
                }
            }
            .padding(10)
            .background(.secondary.opacity(0.2))
            .cornerRadius(20)
        }
    }

    private var gifGrid: some View {
        ScrollView {
            LazyVGrid(columns: [
                GridItem(.flexible(), spacing: 4),
                GridItem(.flexible(), spacing: 4)
            ], spacing: 4) {
                ForEach(viewModel.gifs) { gif in
                    GIFThumbnailView(gif: gif, disable_animation: damus_state.settings.disable_animation)
                        .contentShape(Rectangle())
                        .onTapGesture {
                            if let gifURL = gif.mediumURL ?? gif.fullURL {
                                onGIFSelected(gifURL)
                                dismiss()
                            }
                        }
                        .onAppear {
                            if gif.id == viewModel.gifs.last?.id {
                                Task { await viewModel.loadMore() }
                            }
                        }
                }
            }
            .padding(4)

            if viewModel.isLoading && !viewModel.gifs.isEmpty {
                ProgressView()
                    .padding()
            }
        }
    }

    private var loadingView: some View {
        VStack {
            Spacer()
            ProgressView()
                .scaleEffect(1.5)
            Text("Loading GIFs...", comment: "Loading indicator text for GIF picker")
                .foregroundColor(.secondary)
                .padding(.top)
            Spacer()
        }
    }

    private func errorView(_ error: ErrorView.UserPresentableError) -> some View {
        VStack {
            ErrorView(damus_state: damus_state, error: error)

            Button(NSLocalizedString("Try Again", comment: "Button to retry loading GIFs")) {
                Task {
                    if searchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                        await viewModel.loadFeatured()
                        return
                    }

                    viewModel.search(query: searchText)
                }
            }
            .buttonStyle(.bordered)
            .padding(.bottom, 20)
        }
    }

    private var emptyView: some View {
        VStack {
            Spacer()
            Image(systemName: "photo.on.rectangle.angled")
                .font(.largeTitle)
                .foregroundColor(.secondary)
            Text("No GIFs found", comment: "Message when no GIFs match search")
                .foregroundColor(.secondary)
                .padding(.top)
            Spacer()
        }
    }
}

struct GIFThumbnailView: View {
    let gif: GIFResult
    let disable_animation: Bool

    var body: some View {
        if let previewURL = gif.previewURL {
            KFAnimatedImage(previewURL)
                .configure { view in
                    view.framePreloadCount = 3
                }
                .placeholder {
                    Rectangle()
                        .fill(Color.secondary.opacity(0.2))
                }
                .imageContext(.note, disable_animation: disable_animation)
                .aspectRatio(contentMode: .fill)
                .frame(height: 120)
                .clipped()
                .cornerRadius(8)
                .kfClickable()
        } else {
            Rectangle()
                .fill(Color.secondary.opacity(0.2))
                .frame(height: 120)
                .cornerRadius(8)
        }
    }
}

@MainActor
class GIFPickerViewModel: ObservableObject {
    @Published var gifs: [GIFResult] = []
    @Published var isLoading: Bool = false
    @Published var error: ErrorView.UserPresentableError? = nil

    private let api: PurpleGIFAPIClient
    private let featuredPageSize = 30
    private let searchPageSize = 30
    private var currentQuery: String?
    private var pendingQuery: String?
    private var nextPos: String?
    private var currentPage = 1
    private var hasMoreSearchResults = false
    private var searchTask: Task<Void, Never>?

    /// Initializes a GIF picker view model backed by Purple.
    init(purple: DamusPurple) {
        self.api = PurpleGIFAPIClient(purple: purple)
    }

    /// Loads featured GIFs for the initial picker state.
    func loadFeatured() async {
        guard !isLoading else { return }

        isLoading = true
        error = nil
        currentQuery = nil
        currentPage = 1
        hasMoreSearchResults = false

        do {
            let response = try await api.fetchFeatured(limit: featuredPageSize)
            gifs = response.results
            nextPos = response.next
        } catch {
            self.error = makePresentableError(from: error, action: "loading featured GIFs")
        }

        isLoading = false
        await runPendingQueryIfNeeded()
    }

    /// Starts a debounced GIF search.
    func search(query: String) {
        searchTask?.cancel()

        let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
        pendingQuery = trimmedQuery.isEmpty ? nil : trimmedQuery

        guard !trimmedQuery.isEmpty else {
            Task { await loadFeatured() }
            return
        }

        searchTask = Task {
            try? await Task.sleep(nanoseconds: 300_000_000)
            guard !Task.isCancelled else { return }
            await performSearch(query: trimmedQuery)
        }
    }

    /// Performs a GIF search request.
    private func performSearch(query: String) async {
        guard !isLoading else {
            pendingQuery = query
            return
        }

        isLoading = true
        error = nil
        currentQuery = query
        pendingQuery = nil
        currentPage = 1
        nextPos = nil
        hasMoreSearchResults = false

        do {
            let response = try await api.search(query: query, page: currentPage, perPage: searchPageSize)
            gifs = response.results
            hasMoreSearchResults = response.results.count >= searchPageSize
        } catch {
            self.error = makePresentableError(from: error, action: "searching GIFs")
        }

        isLoading = false
        await runPendingQueryIfNeeded()
    }

    /// Converts technical GIF loading errors into user-presentable content.
    private func makePresentableError(from error: Error, action: String) -> ErrorView.UserPresentableError {
        if let presentableError = error as? ErrorView.UserPresentableErrorProtocol {
            return presentableError.userPresentableError
        }

        if let gifError = error as? PurpleGIFAPIError {
            return gifError.userPresentableError(action: action, currentQuery: currentQuery)
        }

        return .init(
            user_visible_description: NSLocalizedString("We couldn't load GIFs right now.", comment: "Fallback error shown when the GIF picker fails unexpectedly."),
            tip: NSLocalizedString("Try again in a moment. If the problem keeps happening, copy the technical information and send it to support.", comment: "Fallback advice shown when the GIF picker fails unexpectedly."),
            technical_info: "GIF picker error while \(action): \(String(describing: error))"
        )
    }

    /// Runs the latest queued search after the current load completes.
    private func runPendingQueryIfNeeded() async {
        guard !isLoading else { return }
        guard let pendingQuery, pendingQuery != currentQuery else { return }

        self.pendingQuery = nil
        await performSearch(query: pendingQuery)
    }

    /// Loads the next page of GIF results.
    func loadMore() async {
        guard !isLoading else { return }

        if let query = currentQuery {
            guard hasMoreSearchResults else { return }

            isLoading = true

            do {
                let nextPage = currentPage + 1
                let response = try await api.search(query: query, page: nextPage, perPage: searchPageSize)
                gifs.append(contentsOf: response.results)
                currentPage = nextPage
                hasMoreSearchResults = response.results.count >= searchPageSize
            } catch {
                print("Failed to load more GIFs: \(error)")
            }

            isLoading = false
            return
        }

        guard let nextPos else { return }

        isLoading = true

        do {
            let response = try await api.fetchFeatured(limit: featuredPageSize, pos: nextPos)
            gifs.append(contentsOf: response.results)
            self.nextPos = response.next
        } catch {
            print("Failed to load more GIFs: \(error)")
        }

        isLoading = false
    }
}

private extension PurpleGIFAPIError {
    /// Converts Purple GIF API failures into a reusable user-presentable error.
    func userPresentableError(action: String, currentQuery: String?) -> ErrorView.UserPresentableError {
        let queryContext = currentQuery.map { "query=\($0)" } ?? "featured"
        
        // Build comprehensive technical info including error context
        let baseTechnicalInfo = "GIF picker error while \(action); context=\(queryContext)"
        let fullTechnicalInfo: String
        
        if let errorContext = self.context {
            fullTechnicalInfo = "\(baseTechnicalInfo); \(errorContext.debugDescription)"
        } else {
            fullTechnicalInfo = baseTechnicalInfo
        }

        switch self {
        case .unauthorized(let context):
            // For 401 errors, show the server's error message with context
            let serverMessage = context?.extractedMessage
            
            if let message = serverMessage, !message.isEmpty {
                // Quote and provide context around the server message
                let contextualizedMessage = String(format: NSLocalizedString("Access to the GIF service was denied by the server with the following message: \"%@\"", comment: "Error message format that quotes the server's error message"), message)
                return .init(
                    user_visible_description: contextualizedMessage,
                    tip: NSLocalizedString("If the problem persists, copy the technical information and send it to support.", comment: "Advice shown when GIF picker access is denied with server message."),
                    technical_info: fullTechnicalInfo
                )
            } else {
                // Fallback to generic message if no server message available
                return .init(
                    user_visible_description: NSLocalizedString("Access to the GIF service was denied.", comment: "Generic error shown when the user is not authorized to use the GIF picker and no server message is available."),
                    tip: NSLocalizedString("This could be due to an expired subscription, authentication issue, or network problem. Copy the technical information and send it to support for help.", comment: "Advice shown when GIF picker access is denied without specific details."),
                    technical_info: fullTechnicalInfo
                )
            }
        case .invalidURL:
            return .init(
                user_visible_description: NSLocalizedString("The GIF service is misconfigured.", comment: "Error shown when the GIF picker generated an invalid URL."),
                tip: NSLocalizedString("Try again later. If this keeps happening, copy the technical information and send it to support.", comment: "Advice shown when the GIF picker URL is invalid."),
                technical_info: fullTechnicalInfo
            )
        case .invalidResponse:
            return .init(
                user_visible_description: NSLocalizedString("The GIF service returned an unexpected response.", comment: "Error shown when the GIF picker receives an invalid server response."),
                tip: NSLocalizedString("Try again in a moment. If it keeps happening, copy the technical information and send it to support.", comment: "Advice shown when the GIF picker receives an invalid server response."),
                technical_info: fullTechnicalInfo
            )
        case .decodingError(let decodingError, let rawResponse, _):
            // Include decoding error details for legacy compatibility
            let responseText = rawResponse ?? "<unavailable>"
            let detailedInfo = "\(baseTechnicalInfo); error=\(String(describing: decodingError)); response=\(responseText)"
            let withContext = self.context.map { "; \($0.debugDescription)" } ?? ""
            return .init(
                user_visible_description: NSLocalizedString("We couldn't understand the GIF data from the server.", comment: "Error shown when GIF response parsing fails."),
                tip: NSLocalizedString("Try again in a moment. If the problem continues, copy the technical information and send it to support.", comment: "Advice shown when GIF response parsing fails."),
                technical_info: detailedInfo + withContext
            )
        case .networkError(let networkError, _):
            return .init(
                user_visible_description: NSLocalizedString("We couldn't reach the GIF service.", comment: "Error shown when GIF loading fails because of a network issue."),
                tip: NSLocalizedString("Check your internet connection and try again.", comment: "Advice shown when GIF loading fails because of a network issue."),
                technical_info: "\(baseTechnicalInfo); error=\(String(describing: networkError))" + (self.context.map { "; \($0.debugDescription)" } ?? "")
            )
        case .upstreamError(let statusCode, let message, _):
            return .init(
                user_visible_description: NSLocalizedString("The GIF service is temporarily unavailable.", comment: "Error shown when the upstream GIF service fails."),
                tip: NSLocalizedString("Try again in a moment. If the problem keeps happening, copy the technical information and send it to support.", comment: "Advice shown when the upstream GIF service fails."),
                technical_info: "\(baseTechnicalInfo); status=\(statusCode); message=\(message ?? "none")" + (self.context.map { "; \($0.debugDescription)" } ?? "")
            )
        }
    }
}

#Preview {
    GIFPickerView(damus_state: test_damus_state) { url in
        print("Selected GIF: \(url)")
    }
}
