import Foundation

@MainActor
@Observable
final class ReaderViewModel {
    // MARK: - State

    var entries: [ReaderEntry] = []
    var feeds: [ReaderFeed] = []
    var categories: [ReaderCategory] = []
    var counters: ReaderCounters?
    var total = 0
    var isLoading = true
    var isLoadingMore = false
    var isRefreshing = false
    var error: String?

    // MARK: - Filters

    enum ReaderFilter: Hashable {
        case unread
        case starred
        case all
        case feed(Int)
        case category(Int)
    }

    var currentFilter: ReaderFilter = .unread

    func applyFilter(_ filter: ReaderFilter) {
        currentFilter = filter
        Task { await loadEntries(reset: true) }
    }

    // MARK: - Private

    private let api = ReaderAPI()
    private var offset = 0
    private let pageSize = 30
    private var hasMore = true
    private var hasLoadedOnce = false

    // MARK: - Load

    func loadInitial() async {
        guard !hasLoadedOnce else { return }
        hasLoadedOnce = true
        isLoading = true
        error = nil

        do {
            async let feedsResult = api.getFeeds()
            async let categoriesResult = api.getCategories()
            async let countersResult = api.getCounters()
            async let entriesResult = fetchEntries(offset: 0)

            feeds = try await feedsResult
            categories = try await categoriesResult
            counters = try await countersResult
            let list = try await entriesResult
            entries = list.entries
            total = list.total
            offset = list.entries.count
            hasMore = list.entries.count < list.total
        } catch {
            self.error = error.localizedDescription
        }
        isLoading = false
    }

    func loadEntries(reset: Bool = false) async {
        if reset {
            offset = 0
            hasMore = true
            // DO NOT set entries = [] — causes full list teardown + empty flash
        }
        guard !isLoading else { return }
        isLoading = true
        error = nil

        do {
            let list = try await fetchEntries(offset: 0)
            // Atomic swap — SwiftUI diffs by Identifiable.id
            entries = list.entries
            total = list.total
            offset = list.entries.count
            hasMore = list.entries.count < list.total
        } catch {
            self.error = error.localizedDescription
        }
        isLoading = false
    }

    func loadMore() async {
        guard !isLoadingMore, hasMore else { return }
        isLoadingMore = true

        do {
            let list = try await fetchEntries(offset: offset)
            entries.append(contentsOf: list.entries)
            total = list.total
            offset += list.entries.count
            hasMore = offset < list.total
        } catch {
            self.error = error.localizedDescription
        }
        isLoadingMore = false
    }

    func refresh() async {
        isRefreshing = true
        do {
            _ = try await api.refreshAllFeeds()
            try await Task.sleep(for: .seconds(2))
            counters = try await api.getCounters()
            await loadEntries(reset: true)
        } catch {
            self.error = error.localizedDescription
        }
        isRefreshing = false
    }

    // MARK: - Entry Actions (local-first, API in background)

    func markAsRead(_ entry: ReaderEntry) async {
        guard !entry.isRead else { return }

        // Update local state IMMEDIATELY — no API round-trip needed for UI
        if let idx = entries.firstIndex(where: { $0.id == entry.id }) {
            entries[idx].status = "read"
        }

        // Sync with API in background (fire-and-forget)
        Task.detached { [api] in
            try? await api.markEntries(ids: [entry.id], status: "read")
        }

        // Refresh counters in background
        Task {
            counters = try? await api.getCounters()
        }
    }

    func toggleRead(_ entry: ReaderEntry) async {
        let newStatus = entry.isRead ? "unread" : "read"

        if let idx = entries.firstIndex(where: { $0.id == entry.id }) {
            entries[idx].status = newStatus
        }

        Task.detached { [api] in
            try? await api.markEntries(ids: [entry.id], status: newStatus)
        }

        Task {
            counters = try? await api.getCounters()
        }
    }

    func toggleStar(_ entry: ReaderEntry) async {
        if let idx = entries.firstIndex(where: { $0.id == entry.id }) {
            entries[idx].starred.toggle()
        }

        Task.detached { [api] in
            _ = try? await api.toggleBookmark(entryId: entry.id)
        }
    }

    func markAllRead() async {
        do {
            switch currentFilter {
            case .feed(let feedId):
                _ = try await api.markAllRead(feedId: feedId)
            case .category(let catId):
                _ = try await api.markAllRead(categoryId: catId)
            default:
                _ = try await api.markAllRead()
            }

            // Update all local entries to read
            for i in entries.indices {
                entries[i].status = "read"
            }

            counters = try await api.getCounters()
        } catch {}
    }

    func saveToBrain(_ entry: ReaderEntry) async -> Bool {
        guard let url = entry.url else { return false }
        do {
            try await api.saveToBrain(url: url)
            return true
        } catch {
            return false
        }
    }

    // MARK: - Feed Management

    func addFeed(url: String, categoryId: Int? = nil) async throws {
        let feed = try await api.createFeed(url: url, categoryId: categoryId)
        feeds.append(feed)
        feeds.sort { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending }
    }

    func deleteFeed(id: Int) async throws {
        try await api.deleteFeed(id: id)
        feeds.removeAll { $0.id == id }
        entries.removeAll { $0.feed?.id == id }
    }

    func refreshFeed(id: Int) async throws {
        _ = try await api.refreshFeed(id: id)
        try await Task.sleep(for: .seconds(1))
        counters = try await api.getCounters()
    }

    func addCategory(title: String) async throws {
        let cat = try await api.createCategory(title: title)
        categories.append(cat)
    }

    // MARK: - Computed

    var feedsByCategory: [(ReaderCategory?, [ReaderFeed])] {
        var grouped: [Int?: [ReaderFeed]] = [:]
        for feed in feeds {
            let key = feed.category?.id
            grouped[key, default: []].append(feed)
        }

        var result: [(ReaderCategory?, [ReaderFeed])] = []
        if let uncategorized = grouped[nil], !uncategorized.isEmpty {
            result.append((nil, uncategorized))
        }
        for cat in categories {
            if let catFeeds = grouped[cat.id], !catFeeds.isEmpty {
                result.append((cat, catFeeds))
            }
        }
        return result
    }

    var filterTitle: String {
        switch currentFilter {
        case .unread: return "Unread"
        case .starred: return "Starred"
        case .all: return "All Articles"
        case .feed(let id): return feeds.first { $0.id == id }?.title ?? "Feed"
        case .category(let id): return categories.first { $0.id == id }?.title ?? "Category"
        }
    }

    // MARK: - Private Helpers

    private func fetchEntries(offset: Int) async throws -> ReaderEntryList {
        switch currentFilter {
        case .unread:
            return try await api.getEntries(status: "unread", limit: pageSize, offset: offset)
        case .starred:
            return try await api.getEntries(starred: true, limit: pageSize, offset: offset)
        case .all:
            return try await api.getEntries(limit: pageSize, offset: offset)
        case .feed(let feedId):
            return try await api.getEntries(feedId: feedId, limit: pageSize, offset: offset)
        case .category(let catId):
            return try await api.getEntries(categoryId: catId, limit: pageSize, offset: offset)
        }
    }
}
