import SwiftUI

/// Default landing screen showing "continue listening" rows, Netflix-style.
/// Data comes from `AppState.progressCache` (sorted by `updatedAt`) joined
/// with `AppState.recentItemsCache` for the cover/title/author metadata.
/// A row is only shown when the user actually has a library of that media
/// type — pure audiobook users don't see an empty "Podcasts" row and vice versa.
struct HomeView: View {
    @Environment(AppState.self) private var app
    let libraries: [Library]
    /// Shared with the library views via the same `libraryLayout` AppStorage
    /// key — toggling grid/list in the toolbar affects both Home and Library.
    @AppStorage("libraryLayout") private var layoutRaw: String = LibraryLayout.grid.rawValue
    private var layout: LibraryLayout { LibraryLayout(rawValue: layoutRaw) ?? .grid }

    private var hasBookLibrary: Bool {
        libraries.contains { ($0.mediaType ?? "book") != "podcast" }
    }

    private var hasPodcastLibrary: Bool {
        libraries.contains { $0.mediaType == "podcast" }
    }

    /// Anything at 95 % or above is considered "done with the content" —
    /// only credits left. Treats the item as finished even when the server
    /// hasn't flipped `isFinished` yet.
    private static let nearEndFraction: Double = 0.95
    /// Minimum playback position to count as "actually started". Filters out
    /// items the user only previewed for a few seconds or where the server
    /// reset progress without removing the entry.
    private static let minStartedSeconds: Double = 30

    private func isStillListening(_ p: PlaybackProgress) -> Bool {
        if p.isFinished { return false }
        if p.currentTime < Self.minStartedSeconds { return false }
        guard p.duration > 0 else { return true } // unknown duration → keep showing
        return (p.currentTime / p.duration) < Self.nearEndFraction
    }

    private var audiobooksRecent: [LibraryItem] {
        app.progressCache.values
            .filter { isStillListening($0) && $0.episodeId == nil }
            .sorted { $0.updatedAt > $1.updatedAt }
            .prefix(20)
            .compactMap { app.recentItemsCache[$0.syncKey] }
    }

    private var podcastsRecent: [LibraryItem] {
        app.progressCache.values
            .filter { isStillListening($0) && $0.episodeId != nil }
            .sorted { $0.updatedAt > $1.updatedAt }
            .prefix(20)
            .compactMap { app.recentItemsCache[$0.syncKey] }
    }

    private var anyContent: Bool {
        (hasBookLibrary && !audiobooksRecent.isEmpty) || (hasPodcastLibrary && !podcastsRecent.isEmpty)
    }

    var body: some View {
        ScrollView {
            if anyContent {
                VStack(alignment: .leading, spacing: 28) {
                    if hasPodcastLibrary && !podcastsRecent.isEmpty {
                        sectionHeader("Zuletzt gehörte Podcasts")
                        sectionBody(items: podcastsRecent)
                    }
                    if hasBookLibrary && !audiobooksRecent.isEmpty {
                        sectionHeader("Zuletzt gehörte Hörbücher")
                        sectionBody(items: audiobooksRecent)
                    }
                }
                .padding(.vertical, 16)
            } else {
                ContentUnavailableView(
                    "Noch nichts gehört",
                    systemImage: "headphones",
                    description: Text("Sobald du ein Hörbuch oder einen Podcast startest, erscheinen die zuletzt gehörten Folgen hier.")
                )
                .frame(maxWidth: .infinity, minHeight: 400)
            }
        }
        #if os(iOS)
        .refreshable {
            await app.refreshProgressCache()
            await app.fillRecentItemsCache()
        }
        #endif
        .task(id: app.progressCache.count) {
            await app.fillRecentItemsCache()
        }
    }

    @ViewBuilder
    private func sectionHeader(_ title: String) -> some View {
        Text(title)
            .font(.title2.bold())
            .padding(.horizontal, 20)
    }

    @ViewBuilder
    private func sectionBody(items: [LibraryItem]) -> some View {
        switch layout {
        case .grid:
            cardRow(items: items)
        case .list:
            listRows(items: items)
        }
    }

    @ViewBuilder
    private func cardRow(items: [LibraryItem]) -> some View {
        HomeCardRow(items: items) { tapped in
            Task { await app.playFromRecents(tapped) }
        }
    }

    @ViewBuilder
    private func listRows(items: [LibraryItem]) -> some View {
        // Reuses LibraryListRow so the Home list rows inherit the listened +
        // download indicators automatically. Tapping calls playFromRecents
        // (which handles podcasts correctly by looking up the episode), not
        // the library's default play() path.
        VStack(spacing: 0) {
            ForEach(Array(items.enumerated()), id: \.element.syncKey) { idx, item in
                LibraryListRow(item: item, dimDownloading: false)
                    .contentShape(Rectangle())
                    .onTapGesture {
                        Task { await app.playFromRecents(item) }
                    }
                    #if os(iOS)
                    .padding(.horizontal, 16)
                    .padding(.vertical, 8)
                    #endif
                if idx < items.count - 1 {
                    Divider()
                        #if os(macOS)
                        .padding(.leading, 76)
                        #else
                        .padding(.leading, 84)
                        #endif
                }
            }
        }
        #if os(macOS)
        .padding(.horizontal, 4)
        #endif
    }
}

/// A single card on the home screen: cover + progress bar + title + author.
/// Sized to roughly match the library grid covers so the visual rhythm is
/// consistent across screens.
struct HomeMediaCard: View {
    @Environment(AppState.self) private var app
    let item: LibraryItem
    let onTap: () -> Void

    #if os(iOS)
    private let cardWidth: CGFloat = 150
    #else
    private let cardWidth: CGFloat = 190
    #endif

    var body: some View {
        Button(action: onTap) {
            VStack(alignment: .leading, spacing: 6) {
                ZStack(alignment: .bottom) {
                    cover
                        .overlay(alignment: .topTrailing) {
                            if isListened {
                                Image(systemName: "checkmark.circle.fill")
                                    .foregroundStyle(.white, .green)
                                    .font(.title3)
                                    .shadow(radius: 2)
                                    .padding(4)
                            }
                        }
                        .overlay(alignment: .bottomTrailing) {
                            downloadBadge.padding(.trailing, 4).padding(.bottom, 10)
                        }
                    if !isListened {
                        CoverProgressBar(fraction: app.progressFraction(itemId: item.id, episodeId: item.episodeId))
                            .padding(.horizontal, 3)
                            .padding(.bottom, 3)
                    }
                }
                Text(item.title)
                    .font(.subheadline.bold())
                    .lineLimit(2)
                    .multilineTextAlignment(.leading)
                    .frame(maxWidth: .infinity, alignment: .leading)
                Text(item.author)
                    .font(.caption)
                    .foregroundStyle(.secondary)
                    .lineLimit(1)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            .frame(width: cardWidth)
        }
        .buttonStyle(.plain)
        .contextMenu { mediaItemContextMenu(for: item, app: app) }
    }

    private var isListened: Bool {
        app.progressCache[item.syncKey]?.isFinished ?? false
    }

    @ViewBuilder
    private var downloadBadge: some View {
        let state = app.downloads.state(for: item.downloadKey)
        switch state {
        case .downloaded:
            Image(systemName: "arrow.down.circle.fill")
                .foregroundStyle(.white, .green)
                .font(.title3)
                .shadow(radius: 2)
        case .downloading(let p):
            DownloadProgressRing(progress: p)
                #if os(iOS)
                .frame(width: 22, height: 22)
                #else
                .frame(width: 26, height: 26)
                #endif
        case .failed:
            Image(systemName: "exclamationmark.circle.fill")
                .foregroundStyle(.white, .red)
                .font(.title3)
                .shadow(radius: 2)
        case .notDownloaded:
            EmptyView()
        }
    }

    private var cover: some View {
        Rectangle()
            #if os(iOS)
            .fill(Color(.systemGray6))
            #else
            .fill(AnyShapeStyle(.quaternary))
            #endif
            // Explicit square frame — without this the Rectangle inside a
            // LazyHStack stretches to fill the available vertical space and
            // pushes siblings way off to the right.
            .frame(width: cardWidth, height: cardWidth)
            .overlay {
                if let url = app.client.coverURL(itemId: item.id) {
                    AsyncImage(url: url) { phase in
                        switch phase {
                        case .success(let img):
                            img.resizable().scaledToFit()
                        case .empty:
                            ProgressView()
                                #if os(macOS)
                                .controlSize(.small)
                                #endif
                        case .failure:
                            Image(systemName: "book.closed")
                                .foregroundStyle(.secondary)
                        @unknown default:
                            EmptyView()
                        }
                    }
                } else {
                    Image(systemName: "book.closed")
                        .foregroundStyle(.secondary)
                }
            }
            .clipShape(RoundedRectangle(cornerRadius: 8))
    }
}

/// Horizontally scrollable card row that measures its own content vs. the
/// available viewport. Scrolling is only enabled when items actually overflow,
/// and chevron buttons appear on macOS hover so the user knows there's more
/// off-screen and can page through it without trackpad/scrollwheel.
private struct HomeCardRow: View {
    let items: [LibraryItem]
    let onTap: (LibraryItem) -> Void

    #if os(iOS)
    private let cardWidth: CGFloat = 150
    #else
    private let cardWidth: CGFloat = 190
    #endif
    private let spacing: CGFloat = 14
    private let horizontalPadding: CGFloat = 20

    @State private var viewportWidth: CGFloat = 0
    @State private var scrollIndex: Int = 0
    #if os(macOS)
    @State private var isHovering = false
    #endif

    private var contentWidth: CGFloat {
        guard !items.isEmpty else { return 0 }
        return CGFloat(items.count) * cardWidth
            + CGFloat(items.count - 1) * spacing
            + horizontalPadding * 2
    }

    private var canScroll: Bool { contentWidth > viewportWidth + 1 }

    var body: some View {
        GeometryReader { geo in
            ScrollViewReader { proxy in
                ScrollView(.horizontal, showsIndicators: false) {
                    LazyHStack(spacing: spacing) {
                        ForEach(items, id: \.syncKey) { item in
                            HomeMediaCard(item: item) { onTap(item) }
                                .id(item.syncKey)
                        }
                    }
                    .padding(.horizontal, horizontalPadding)
                }
                .scrollDisabled(!canScroll)
                .onAppear { viewportWidth = geo.size.width }
                .onChange(of: geo.size.width) { _, newValue in viewportWidth = newValue }
                #if os(macOS)
                .overlay(alignment: .leading) {
                    chevron(systemName: "chevron.left", visible: isHovering && canScroll) {
                        scrollBy(-1, proxy: proxy)
                    }
                }
                .overlay(alignment: .trailing) {
                    chevron(systemName: "chevron.right", visible: isHovering && canScroll) {
                        scrollBy(1, proxy: proxy)
                    }
                }
                .onHover { isHovering = $0 }
                #endif
            }
        }
        // GeometryReader has no intrinsic size — give the row an explicit height
        // matching cardWidth (square cover) + ~50pt for title/author.
        .frame(height: cardWidth + 52)
    }

    #if os(macOS)
    @ViewBuilder
    private func chevron(systemName: String, visible: Bool, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Image(systemName: systemName)
                .font(.title3.weight(.semibold))
                .foregroundStyle(.white)
                .frame(width: 36, height: 36)
                .background(Circle().fill(.black.opacity(0.55)))
                .shadow(color: .black.opacity(0.3), radius: 4, y: 2)
        }
        .buttonStyle(.plain)
        .padding(.horizontal, 8)
        .opacity(visible ? 1 : 0)
        .animation(.easeInOut(duration: 0.15), value: visible)
    }

    /// Pages by roughly one viewport in the requested direction. The actual
    /// scroll position is tracked via `scrollIndex` — the index of the card we
    /// want pinned to the leading edge after the scroll.
    private func scrollBy(_ direction: Int, proxy: ScrollViewProxy) {
        let cardsPerView = max(1, Int(viewportWidth / (cardWidth + spacing)))
        let next = max(0, min(items.count - 1, scrollIndex + direction * cardsPerView))
        scrollIndex = next
        withAnimation(.easeInOut(duration: 0.3)) {
            proxy.scrollTo(items[next].syncKey, anchor: .leading)
        }
    }
    #endif
}
