import SwiftUI
import AppKit
import UniformTypeIdentifiers
import WebKit

struct LauncherRootView: View {
    private enum ExternalPage {
        static let home = "https://sites.google.com/view/apps-launcher/home"
        static let terms = "https://sites.google.com/view/apps-launcher/terms-of-service"
        static let privacy = "https://sites.google.com/view/apps-launcher/privacy-policy"
        static let support = "https://sites.google.com/view/apps-launcher/support"
    }

    enum LayoutMode {
        case grid
        case panel
    }

    @EnvironmentObject private var premiumStore: PremiumStore
    @Environment(\.colorScheme) private var colorScheme
    @State private var query = ""
    @State private var selectedApp: LauncherApp?
    @State private var showingPremiumScreen = false
    @State private var showingCreateAppSheet = false
    @State private var previouslyPremiumUnlocked = false
    @State private var startupPaywallTask: Task<Void, Never>?
    @State private var ratingPromptTask: Task<Void, Never>?
    @State private var customApps: [LauncherApp] = []
    @State private var appOrder: [UUID] = []
    @State private var draggedAppID: UUID?
    @State private var lastDropDestinationAppID: UUID?
    @State private var layoutMode: LayoutMode = .grid
    @AppStorage("customLauncherAppsData") private var customAppsData = ""
    @AppStorage("launcherAppOrderData") private var appOrderData = ""
    @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
    @AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
    @AppStorage("widgetAppIDsData") private var widgetAppIDsData = ""
    @AppStorage("hiddenLauncherAppIDsData") private var hiddenLauncherAppIDsData = ""
    @AppStorage("hasOpenedWidgetsHubFromLauncher") private var hasOpenedWidgetsHubFromLauncher = false

    private var apps: [LauncherApp] {
        let hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
        let visibleApps = (LauncherApp.sampleApps + customApps).filter { !hiddenIDs.contains($0.id) }
        return visibleApps + [LauncherApp.createNewTile]
    }
    private var columns: [GridItem] {
        [GridItem(.adaptive(minimum: 120, maximum: 145), spacing: 22)]
    }

    private var filteredApps: [LauncherApp] {
        let q = query.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !q.isEmpty else { return orderedApps }
        return orderedApps.filter { $0.name.localizedCaseInsensitiveContains(q) }
    }

    private var orderedApps: [LauncherApp] {
        guard !apps.isEmpty else { return [] }

        let defaultIndexByID = Dictionary(
            uniqueKeysWithValues: apps.enumerated().map { ($0.element.id, $0.offset) }
        )
        let rankByID = Dictionary(
            uniqueKeysWithValues: appOrder.enumerated().map { ($0.element, $0.offset) }
        )
        let pinnedIDs = decodeUUIDSet(from: pinnedTileIDsData)

        return apps.sorted { lhs, rhs in
            if lhs.isCreateNew != rhs.isCreateNew {
                return rhs.isCreateNew
            }

            let leftPinned = pinnedIDs.contains(lhs.id)
            let rightPinned = pinnedIDs.contains(rhs.id)
            if leftPinned != rightPinned {
                return leftPinned
            }

            let leftRank = rankByID[lhs.id] ?? Int.max
            let rightRank = rankByID[rhs.id] ?? Int.max

            if leftRank != rightRank {
                return leftRank < rightRank
            }

            return (defaultIndexByID[lhs.id] ?? Int.max) < (defaultIndexByID[rhs.id] ?? Int.max)
        }
    }

    private var hiddenApps: [LauncherApp] {
        let hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
        guard !hiddenIDs.isEmpty else { return [] }
        let allApps = LauncherApp.sampleApps + customApps
        let sortedHiddenApps = allApps
            .filter { hiddenIDs.contains($0.id) }
            .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }

        var seenLinkKeys = Set<String>()
        return sortedHiddenApps.filter { app in
            guard let webURL = app.webURL else { return true }
            let linkKey = webURL.absoluteString.lowercased()
            guard !seenLinkKeys.contains(linkKey) else { return false }
            seenLinkKeys.insert(linkKey)
            return true
        }
    }

    var body: some View {
        ZStack {
            VisualEffectBlur(
                material: colorScheme == .dark ? .hudWindow : .underWindowBackground,
                blendingMode: .behindWindow
            )
                .ignoresSafeArea()

            (colorScheme == .dark ? Color.black.opacity(0.22) : Color.white.opacity(0.28))
                .ignoresSafeArea()

            VStack(spacing: 16) {
                SearchHeader(
                    query: $query,
                    isPanelMode: layoutMode == .panel,
                    hiddenApps: hiddenApps,
                    onToggleLayout: {
                        layoutMode = layoutMode == .grid ? .panel : .grid
                    },
                    onOpenPremium: {
                        showingPremiumScreen = true
                    },
                    onOpenGoogle: {
                        if let url = URL(string: "https://www.google.com") {
                            InAppBrowserWindowManager.shared.open(url: url, title: "Google")
                        }
                    },
                    onOpenHomePage: { openExternalPage(ExternalPage.home) },
                    onOpenSupportPage: { openExternalPage(ExternalPage.support) },
                    onOpenPrivacyPolicy: { openExternalPage(ExternalPage.privacy) },
                    onOpenTermsOfService: { openExternalPage(ExternalPage.terms) },
                    onOpenWidgets: {
                        openWidgetsHub()
                    },
                    shouldHighlightWidgets: !hasOpenedWidgetsHubFromLauncher,
                    onRestoreApp: { appID in
                        restoreHiddenApp(id: appID)
                    },
                    onRestoreAll: {
                        restoreAllHiddenApps()
                    }
                )
                .padding(.top, 12)

                PromoBanner(
                    isPremiumUnlocked: premiumStore.isPremiumUnlocked,
                    activePremiumProductID: premiumStore.activePremiumProductID,
                    onCloseTap: {
                        if !premiumStore.isPremiumUnlocked {
                            showingPremiumScreen = true
                        }
                    },
                    onUpgradeTap: { showingPremiumScreen = true },
                    onManageSubscriptionTap: { openManageSubscriptions() }
                )
                .padding(.bottom, 8)
                .zIndex(1)

                appsPage
            }
            .padding(.horizontal, 10)
            .padding(.top, 14)
            .padding(.bottom, 8)
            .overlay(alignment: .top) {
                WindowDragHandle()
                    .frame(height: 6)
                    .padding(.horizontal, 12)
            }
        }
        .sheet(item: $selectedApp) { app in
            AppDetailView(app: app)
                .frame(minWidth: 360, minHeight: 220)
        }
        .sheet(isPresented: $showingPremiumScreen) {
            PremiumFeaturesView()
                .environmentObject(premiumStore)
                .frame(minWidth: 560, minHeight: 690)
        }
        .sheet(isPresented: $showingCreateAppSheet) {
            CreateAppSheetView { input in
                addCustomApp(name: input.name, url: input.url)
            }
            .frame(minWidth: 760, minHeight: 420)
        }
        .task {
            await premiumStore.refreshEntitlements()
        }
        .onChange(of: showingPremiumScreen) { isShowing in
            guard !isShowing else { return }
            Task {
                await premiumStore.refreshEntitlements()
            }
        }
        .onChange(of: premiumStore.isPremiumUnlocked) { unlocked in
            if previouslyPremiumUnlocked && !unlocked {
                showingPremiumScreen = true
            }
            previouslyPremiumUnlocked = unlocked
        }
        .onAppear {
            previouslyPremiumUnlocked = premiumStore.isPremiumUnlocked
            loadCustomAppsFromStorage()
            loadAppOrderFromStorage()
            normalizeAppOrderAndPersist()

            DesktopWidgetWindowManager.shared.restore(appProvider: { id in
                orderedApps.first { $0.id == id }
            })

            NotificationCenter.default.addObserver(
                forName: .openWidgetsPage,
                object: nil,
                queue: .main
            ) { notification in
                let id = notification.userInfo?["appID"] as? UUID
                WidgetsWindowManager.shared.open(
                    apps: orderedApps.filter { !$0.isCreateNew },
                    selectedAppID: id
                )
            }

            startupPaywallTask?.cancel()
            startupPaywallTask = Task {
                try? await Task.sleep(nanoseconds: 2_000_000_000)
                guard !Task.isCancelled else { return }
                await premiumStore.refreshEntitlements()
                guard !premiumStore.isPremiumUnlocked else { return }
                guard !showingPremiumScreen else { return }
                showingPremiumScreen = true
            }

            ratingPromptTask?.cancel()
            ratingPromptTask = Task {
                try? await Task.sleep(nanoseconds: 5 * 60 * 1_000_000_000)
                guard !Task.isCancelled else { return }
                await MainActor.run {
                    RatingPromptManager.shared.requestRatingIfNeeded()
                }
            }
        }
        .onDisappear {
            startupPaywallTask?.cancel()
            startupPaywallTask = nil
            ratingPromptTask?.cancel()
            ratingPromptTask = nil
        }
    }

    @ViewBuilder
    private var appsPage: some View {
        if filteredApps.isEmpty {
            VStack(spacing: 10) {
                Image(systemName: "magnifyingglass")
                    .font(.system(size: 28, weight: .semibold))
                    .foregroundStyle(.secondary)
                Text("No apps found")
                    .font(.title3.weight(.semibold))
                    .foregroundStyle(.primary)
                Text("Try a different search term.")
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        } else {
            if layoutMode == .panel {
                IconPanelView(
                    apps: filteredApps,
                    onAppTap: { app in
                        handlePanelAppTap(app)
                    },
                    draggedAppID: $draggedAppID,
                    lastDropDestinationAppID: $lastDropDestinationAppID,
                    onMove: moveApp,
                    onHideApp: hideApp
                )
            } else {
                ScrollView {
                    LazyVGrid(columns: columns, spacing: 20) {
                        ForEach(filteredApps) { app in
                            AppTileView(
                                app: app,
                                onTap: { handleAppTap(app) },
                                onHide: { hideApp(id: app.id) }
                            )
                            .onDrag {
                                draggedAppID = app.id
                                lastDropDestinationAppID = nil
                                return NSItemProvider(object: app.id.uuidString as NSString)
                            }
                            .onDrop(
                                of: [UTType.text],
                                delegate: TileReorderDropDelegate(
                                    destinationAppID: app.id,
                                    draggedAppID: $draggedAppID,
                                    lastDropDestinationAppID: $lastDropDestinationAppID,
                                    onMove: moveApp
                                )
                            )
                        }
                    }
                    .padding(.top, 10)
                    .padding(.horizontal, 14)
                    .padding(.bottom, 18)
                }
            }
        }
    }

    private func handleAppTap(_ app: LauncherApp) {
        if app.isCreateNew {
            showingCreateAppSheet = true
            return
        }

        if let webURL = app.webURL {
            InAppBrowserWindowManager.shared.open(url: webURL, title: app.name)
            return
        }

        selectedApp = app
    }

    /// Second (panel) view: tapping a row opens the Widgets window for that app.
    private func handlePanelAppTap(_ app: LauncherApp) {
        if app.isCreateNew {
            showingCreateAppSheet = true
            return
        }
        openWidgetsPage(for: app)
    }

    private func openWidgetsPage(for app: LauncherApp) {
        hasOpenedWidgetsHubFromLauncher = true
        var updated = decodeUUIDSetGlobal(from: widgetAppIDsData)
        updated.insert(app.id)
        widgetAppIDsData = encodeUUIDSetGlobal(updated)
        NotificationCenter.default.post(
            name: .openWidgetsPage,
            object: nil,
            userInfo: ["appID": app.id]
        )
    }

    private func openWidgetsHub() {
        hasOpenedWidgetsHubFromLauncher = true
        let firstAppID = orderedApps.first(where: { !$0.isCreateNew })?.id
        WidgetsWindowManager.shared.open(
            apps: orderedApps.filter { !$0.isCreateNew },
            selectedAppID: firstAppID
        )
    }

    private func addCustomApp(name: String, url: URL) {
        let newApp = LauncherApp(
            name: name,
            assetIconName: "",
            fallbackSymbolName: "globe",
            description: "Custom app shortcut.",
            isCreateNew: false,
            webURL: url
        )
        customApps.append(newApp)
        saveCustomAppsToStorage()
        normalizeAppOrderAndPersist()
    }

    private func loadCustomAppsFromStorage() {
        guard !customAppsData.isEmpty else {
            customApps = []
            return
        }

        guard let data = customAppsData.data(using: .utf8) else {
            customApps = []
            return
        }

        do {
            let persisted = try JSONDecoder().decode([PersistedCustomApp].self, from: data)
            customApps = persisted.compactMap { item in
                guard let parsedURL = URL(string: item.urlString) else { return nil }
                return LauncherApp(
                    id: item.id,
                    name: item.name,
                    assetIconName: "",
                    fallbackSymbolName: "globe",
                    description: "Custom app shortcut.",
                    isCreateNew: false,
                    webURL: parsedURL
                )
            }
        } catch {
            customApps = []
        }
    }

    private func saveCustomAppsToStorage() {
        let persisted = customApps.compactMap { app -> PersistedCustomApp? in
            guard !app.isCreateNew, let urlString = app.webURL?.absoluteString else { return nil }
            return PersistedCustomApp(id: app.id, name: app.name, urlString: urlString)
        }

        do {
            let encoded = try JSONEncoder().encode(persisted)
            customAppsData = String(decoding: encoded, as: UTF8.self)
        } catch {
            // Keep UI responsive even if persistence fails.
        }
    }

    private func moveApp(from sourceID: UUID, to destinationID: UUID) {
        guard sourceID != destinationID else { return }
        guard let sourceIndex = appOrder.firstIndex(of: sourceID),
              let destinationIndex = appOrder.firstIndex(of: destinationID) else {
            return
        }

        var updated = appOrder
        let movedID = updated.remove(at: sourceIndex)
        let insertionIndex = destinationIndex > sourceIndex ? destinationIndex - 1 : destinationIndex
        updated.insert(movedID, at: insertionIndex)
        appOrder = updated
        saveAppOrderToStorage()
    }

    private func hideApp(id: UUID) {
        var hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
        guard hiddenIDs.insert(id).inserted else { return }
        hiddenLauncherAppIDsData = encodeUUIDSetGlobal(hiddenIDs)
        removeIDFromLinkedCollections(id)
    }

    private func restoreHiddenApp(id: UUID) {
        var hiddenIDs = decodeUUIDSet(from: hiddenLauncherAppIDsData)
        guard hiddenIDs.remove(id) != nil else { return }
        hiddenLauncherAppIDsData = encodeUUIDSetGlobal(hiddenIDs)
    }

    private func restoreAllHiddenApps() {
        hiddenLauncherAppIDsData = ""
    }

    private func removeIDFromLinkedCollections(_ id: UUID) {
        var pinnedIDs = decodeUUIDSet(from: pinnedTileIDsData)
        pinnedIDs.remove(id)
        pinnedTileIDsData = encodeUUIDSetGlobal(pinnedIDs)

        var statusBarIDs = decodeUUIDSet(from: statusBarAppIDsData)
        if statusBarIDs.remove(id) != nil {
            statusBarAppIDsData = encodeUUIDSetGlobal(statusBarIDs)
            LauncherApp.notifyStatusBarShortcutIdentifiersChanged()
        }

        var widgetIDs = decodeUUIDSet(from: widgetAppIDsData)
        widgetIDs.remove(id)
        widgetAppIDsData = encodeUUIDSetGlobal(widgetIDs)
    }

    private func loadAppOrderFromStorage() {
        guard !appOrderData.isEmpty else {
            appOrder = []
            return
        }

        guard let data = appOrderData.data(using: .utf8) else {
            appOrder = []
            return
        }

        do {
            let decoded = try JSONDecoder().decode([String].self, from: data)
            appOrder = decoded.compactMap { UUID(uuidString: $0) }
        } catch {
            appOrder = []
        }
    }

    private func normalizeAppOrderAndPersist() {
        let currentIDs = apps.map(\.id)
        var seen = Set<UUID>()
        var normalized: [UUID] = []

        for id in appOrder where currentIDs.contains(id) {
            if seen.insert(id).inserted {
                normalized.append(id)
            }
        }

        for id in currentIDs where seen.insert(id).inserted {
            normalized.append(id)
        }

        appOrder = normalized
        saveAppOrderToStorage()
    }

    private func saveAppOrderToStorage() {
        do {
            let payload = appOrder.map(\.uuidString)
            let encoded = try JSONEncoder().encode(payload)
            appOrderData = String(decoding: encoded, as: UTF8.self)
        } catch {
            // Ignore persistence failure and keep local order.
        }
    }

    private func decodeUUIDSet(from dataString: String) -> Set<UUID> {
        guard !dataString.isEmpty, let data = dataString.data(using: .utf8) else { return [] }
        do {
            let strings = try JSONDecoder().decode([String].self, from: data)
            return Set(strings.compactMap(UUID.init(uuidString:)))
        } catch {
            return []
        }
    }

    private func openManageSubscriptions() {
        let appStoreURL = URL(string: "macappstore://apps.apple.com/account/subscriptions")!
        if NSWorkspace.shared.open(appStoreURL) {
            return
        }
        let webURL = URL(string: "https://apps.apple.com/account/subscriptions")!
        _ = NSWorkspace.shared.open(webURL)
    }

    private func openExternalPage(_ urlString: String) {
        guard let url = URL(string: urlString) else { return }
        _ = NSWorkspace.shared.open(url)
    }
}

/// Dedicated drag strip so the window remains movable while text fields stay fully interactive.
private struct WindowDragHandle: NSViewRepresentable {
    func makeNSView(context: Context) -> DragHandleView {
        let view = DragHandleView()
        view.wantsLayer = true
        view.layer?.backgroundColor = NSColor.clear.cgColor
        return view
    }

    func updateNSView(_ nsView: DragHandleView, context: Context) {}
}

private final class DragHandleView: NSView {
    override func mouseDown(with event: NSEvent) {
        window?.performDrag(with: event)
    }
}

private struct TileReorderDropDelegate: DropDelegate {
    let destinationAppID: UUID
    @Binding var draggedAppID: UUID?
    @Binding var lastDropDestinationAppID: UUID?
    let onMove: (UUID, UUID) -> Void

    func dropEntered(info: DropInfo) {
        guard let draggedAppID else { return }
        guard draggedAppID != destinationAppID else { return }
        // SwiftUI may call `dropEntered` multiple times while hovering the same tile.
        // Avoid reordering repeatedly to keep drag-to-reorder stable.
        guard lastDropDestinationAppID != destinationAppID else { return }
        lastDropDestinationAppID = destinationAppID
        onMove(draggedAppID, destinationAppID)
    }

    func performDrop(info: DropInfo) -> Bool {
        draggedAppID = nil
        lastDropDestinationAppID = nil
        return true
    }
}

private struct SearchHeader: View {
    @Binding var query: String
    let isPanelMode: Bool
    let hiddenApps: [LauncherApp]
    let onToggleLayout: () -> Void
    let onOpenPremium: () -> Void
    let onOpenGoogle: () -> Void
    let onOpenHomePage: () -> Void
    let onOpenSupportPage: () -> Void
    let onOpenPrivacyPolicy: () -> Void
    let onOpenTermsOfService: () -> Void
    let onOpenWidgets: () -> Void
    let shouldHighlightWidgets: Bool
    let onRestoreApp: (UUID) -> Void
    let onRestoreAll: () -> Void
    @Environment(\.colorScheme) private var colorScheme
    @State private var pulse = false

    var body: some View {
        HStack {
            HStack(spacing: 10) {
                Image(systemName: "magnifyingglass")
                    .foregroundStyle(secondaryForeground)
                TextField("Search", text: $query)
                    .textFieldStyle(.plain)
                    .foregroundStyle(primaryForeground)
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 8)
            .background(
                RoundedRectangle(cornerRadius: 15, style: .continuous)
                    .fill(searchBackground)
            )
            .overlay(
                RoundedRectangle(cornerRadius: 15, style: .continuous)
                    .stroke(searchStroke, lineWidth: 1)
            )

            Spacer(minLength: 8)

            HStack(spacing: 12) {
                Button(action: onOpenWidgets) {
                    HStack(spacing: 6) {
                        Image(systemName: "square.grid.2x2.fill")
                        Text("Widgets")
                            .font(.system(size: 12, weight: .bold))
                        if shouldHighlightWidgets {
                            Text("NEW")
                                .font(.system(size: 9, weight: .black))
                                .padding(.horizontal, 5)
                                .padding(.vertical, 2)
                                .background(
                                    Capsule(style: .continuous)
                                        .fill(Color.white.opacity(0.92))
                                )
                                .foregroundStyle(Color.blue.opacity(0.95))
                        }
                    }
                    .foregroundStyle(widgetButtonForeground)
                    .padding(.horizontal, 10)
                    .padding(.vertical, 6)
                    .background(
                        Capsule(style: .continuous)
                            .fill(shouldHighlightWidgets ? Color.blue.opacity(0.82) : neutralButtonFill)
                    )
                    .overlay(
                        Capsule(style: .continuous)
                            .stroke(
                                shouldHighlightWidgets
                                ? Color.white.opacity(0.95)
                                : neutralButtonStroke,
                                lineWidth: shouldHighlightWidgets ? 1.4 : 1
                            )
                    )
                    .shadow(
                        color: shouldHighlightWidgets
                        ? Color.blue.opacity(0.55)
                        : Color.clear,
                        radius: pulse ? 18 : 9,
                        x: 0,
                        y: 5
                    )
                    .scaleEffect(shouldHighlightWidgets && pulse ? 1.03 : 1.0)
                    .animation(.easeInOut(duration: 1.2).repeatForever(autoreverses: true), value: pulse)
                }
                .buttonStyle(.plain)

                Button(action: onToggleLayout) {
                    Image(systemName: isPanelMode ? "square.grid.2x2" : "list.bullet")
                }
                .buttonStyle(.plain)

                Menu {
                    Button("Clear Search") {
                        query = ""
                    }
                    Button("Open Premium") {
                        onOpenPremium()
                    }
                    Button("Open Google") {
                        onOpenGoogle()
                    }
                    Divider()
                    Button("Support") {
                        onOpenSupportPage()
                    }
                    Button("Privacy Policy") {
                        onOpenPrivacyPolicy()
                    }
                    Button("Terms of Service") {
                        onOpenTermsOfService()
                    }
                    Button("Home Page") {
                        onOpenHomePage()
                    }
                    if !hiddenApps.isEmpty {
                        Divider()
                        Menu("Manage Hidden Apps") {
                            ForEach(hiddenApps) { app in
                                Button("Add Back \(app.name)") {
                                    onRestoreApp(app.id)
                                }
                            }
                            Divider()
                            Button("Restore All Hidden Apps") {
                                onRestoreAll()
                            }
                        }
                    }
                } label: {
                    Image(systemName: "ellipsis")
                }
                .menuStyle(.borderlessButton)
                .modifier(HideMenuIndicatorIfAvailable())
            }
            .font(.system(size: 15, weight: .semibold))
            .foregroundStyle(secondaryForeground)
            .padding(.trailing, 4)
        }
        .onAppear {
            pulse = shouldHighlightWidgets
        }
        .onChange(of: shouldHighlightWidgets) { shouldHighlight in
            pulse = shouldHighlight
        }
    }

    private var primaryForeground: Color {
        colorScheme == .dark ? .white.opacity(0.94) : .primary.opacity(0.95)
    }

    private var secondaryForeground: Color {
        colorScheme == .dark ? .white.opacity(0.82) : .primary.opacity(0.72)
    }

    private var searchBackground: Color {
        colorScheme == .dark ? .black.opacity(0.28) : .white.opacity(0.72)
    }

    private var searchStroke: Color {
        colorScheme == .dark ? .white.opacity(0.08) : .black.opacity(0.07)
    }

    private var neutralButtonFill: Color {
        colorScheme == .dark ? .white.opacity(0.12) : .black.opacity(0.05)
    }

    private var neutralButtonStroke: Color {
        colorScheme == .dark ? .white.opacity(0.15) : .black.opacity(0.09)
    }

    private var widgetButtonForeground: Color {
        if shouldHighlightWidgets {
            return .white.opacity(0.95)
        }
        return colorScheme == .dark ? .white.opacity(0.95) : .primary.opacity(0.88)
    }
}

private struct IconPanelView: View {
    let apps: [LauncherApp]
    let onAppTap: (LauncherApp) -> Void
    @Binding var draggedAppID: UUID?
    @Binding var lastDropDestinationAppID: UUID?
    let onMove: (UUID, UUID) -> Void
    let onHideApp: (UUID) -> Void

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 12) {
                ForEach(apps) { app in
                    PanelAppRowView(
                        app: app,
                        onAppTap: onAppTap,
                        draggedAppID: $draggedAppID,
                        lastDropDestinationAppID: $lastDropDestinationAppID,
                        onMove: onMove,
                        onHideApp: onHideApp
                    )
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.bottom, 18)
        }
    }
}

private struct PanelAppRowView: View {
    let app: LauncherApp
    let onAppTap: (LauncherApp) -> Void
    @Binding var draggedAppID: UUID?
    @Binding var lastDropDestinationAppID: UUID?
    let onMove: (UUID, UUID) -> Void
    let onHideApp: (UUID) -> Void

    @ObservedObject private var premiumStore = PremiumStore.shared
    @Environment(\.colorScheme) private var colorScheme
    @AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
    @AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
    @AppStorage("widgetAppIDsData") private var widgetAppIDsData = ""

    @State private var isHovering = false

    private var quickActions: [WidgetAction] {
        let variants = WidgetTemplates.variants(for: app)
        let chosen = variants.first(where: { $0.size == .medium }) ??
        variants.first(where: { $0.size == .small }) ??
        variants.first
        guard let chosen else { return [] }

        let maxActions: Int
        switch chosen.size {
        case .small: maxActions = 3
        case .medium: maxActions = 4
        case .large: maxActions = 8
        }
        return Array(chosen.actions.prefix(maxActions))
    }

    private var shouldShowQuickActions: Bool {
        !app.isCreateNew && app.webURL != nil && !quickActions.isEmpty
    }

    private var pinnedIDs: Set<UUID> {
        decodeUUIDSetGlobal(from: pinnedTileIDsData)
    }

    private var isPinned: Bool {
        pinnedIDs.contains(app.id)
    }

    private var statusBarIDs: Set<UUID> {
        decodeUUIDSetGlobal(from: statusBarAppIDsData)
    }

    private var isInStatusBar: Bool {
        statusBarIDs.contains(app.id)
    }

    var body: some View {
        ZStack(alignment: .trailing) {
            HStack(spacing: 14) {
                AppIconGlyph(app: app)
                    .scaleEffect(isHovering ? 1.05 : 1.0, anchor: .leading)
                    .animation(.spring(response: 0.22, dampingFraction: 0.82), value: isHovering)
                Text(app.name)
                    .font(.system(size: 18, weight: .medium))
                    .foregroundStyle(titleColor)
                    .lineLimit(1)
                    .truncationMode(.tail)
                Spacer(minLength: 0)
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .contentShape(Rectangle())
            .onTapGesture { onAppTap(app) }

            if isPinned && !app.isCreateNew {
                Image(systemName: "pin.fill")
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundStyle(pinForegroundColor)
                    .frame(width: 26, height: 26)
                    .background(
                        Circle()
                            .fill(controlBackgroundColor)
                    )
                    .overlay(
                        Circle()
                            .stroke(controlStrokeColor, lineWidth: 1)
                    )
                    .shadow(color: controlShadowColor, radius: 10, x: 0, y: 6)
                    .padding(8)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                    .allowsHitTesting(false)
            }

            if shouldShowQuickActions {
                HStack(spacing: 10) {
                    ForEach(quickActions) { action in
                        PanelQuickActionButton(
                            action: action,
                            onTap: { openWidgetAction(action) }
                        )
                    }
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
                .padding(.trailing, 34)
            }

            if !app.isCreateNew {
                Menu {
                    Button(isPinned ? "Unpin" : "Pin") { togglePin() }
                    Divider()
                    if isInStatusBar {
                        Button("Remove from StatusBar") { removeFromStatusBar() }
                    } else {
                        Button("Add to StatusBar") { addToStatusBar() }
                            .disabled(!premiumStore.isPremiumUnlocked)
                    }
                    Button("Add to desktop") { addToDesktop() }
                        .disabled(!premiumStore.isPremiumUnlocked)
                    Button("Add Widget") { addWidget() }
                    Divider()
                    Button("Remove from View") { onHideApp(app.id) }
                } label: {
                    Image(systemName: "ellipsis")
                        .font(.system(size: 13, weight: .semibold))
                        .rotationEffect(.degrees(90))
                        .foregroundStyle(controlForegroundColor)
                        .frame(width: 26, height: 26)
                        .background(
                            Circle()
                                .fill(controlBackgroundColor)
                        )
                        .overlay(
                            Circle()
                                .stroke(controlStrokeColor, lineWidth: 1)
                        )
                        .shadow(color: controlShadowColor, radius: 10, x: 0, y: 6)
                }
                .menuStyle(.borderlessButton)
                .modifier(HideMenuIndicatorIfAvailable())
                .padding(8)
                .opacity(isHovering ? 1 : 0.0)
                .animation(.easeOut(duration: 0.12), value: isHovering)
                .frame(maxHeight: .infinity, alignment: .center)
            }
        }
        .padding(.vertical, 4)
        .padding(.horizontal, 10)
        .frame(maxWidth: .infinity, minHeight: 60, alignment: .leading)
        .background {
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(rowFillColor)
                .overlay(
                    RoundedRectangle(cornerRadius: 12, style: .continuous)
                        .stroke(
                            LinearGradient(
                                colors: [
                                    rowGradientColorTop,
                                    rowGradientColorBottom,
                                ],
                                startPoint: .topLeading,
                                endPoint: .bottomTrailing
                            ),
                            lineWidth: 1
                        )
                )
                .shadow(color: rowShadowColor, radius: isHovering ? 14 : 0, x: 0, y: 4)
                .animation(.easeOut(duration: 0.14), value: isHovering)
        }
        .contentShape(Rectangle())
        .onHover { hovering in
            isHovering = hovering
        }
        .contextMenu {
            if !app.isCreateNew {
                Button(isPinned ? "Unpin" : "Pin") { togglePin() }
                Divider()
                if isInStatusBar {
                    Button("Remove from StatusBar") { removeFromStatusBar() }
                } else {
                    Button("Add to StatusBar") { addToStatusBar() }
                        .disabled(!premiumStore.isPremiumUnlocked)
                }
                Button("Add to desktop") { addToDesktop() }
                    .disabled(!premiumStore.isPremiumUnlocked)
                Button("Add Widget") { addWidget() }
                Divider()
                Button("Remove from View") { onHideApp(app.id) }
            }
        }
        .padding(.vertical, 3)
        .onDrag {
            draggedAppID = app.id
            lastDropDestinationAppID = nil
            return NSItemProvider(object: app.id.uuidString as NSString)
        }
        .onDrop(
            of: [UTType.text],
            delegate: TileReorderDropDelegate(
                destinationAppID: app.id,
                draggedAppID: $draggedAppID,
                lastDropDestinationAppID: $lastDropDestinationAppID,
                onMove: onMove
            )
        )
    }

    private func togglePin() {
        var updated = pinnedIDs
        if updated.contains(app.id) {
            updated.remove(app.id)
        } else {
            updated.insert(app.id)
        }
        pinnedTileIDsData = encodeUUIDSetGlobal(updated)
    }

    private func addToStatusBar() {
        var updated = decodeUUIDSetGlobal(from: statusBarAppIDsData)
        guard updated.insert(app.id).inserted else {
            showAlert(title: "Already added", message: "“\(app.name)” is already in the menu bar.")
            return
        }
        statusBarAppIDsData = encodeUUIDSetGlobal(updated)
        LauncherApp.notifyStatusBarShortcutIdentifiersChanged()
        showAlert(
            title: "Added to Status Bar",
            message: "“\(app.name)” now has its own icon in the menu bar. Click it to open, or right‑click to remove."
        )
    }

    private func removeFromStatusBar() {
        var updated = decodeUUIDSetGlobal(from: statusBarAppIDsData)
        guard updated.remove(app.id) != nil else { return }
        statusBarAppIDsData = encodeUUIDSetGlobal(updated)
        LauncherApp.notifyStatusBarShortcutIdentifiersChanged()
    }

    private func addWidget() {
        var updated = decodeUUIDSetGlobal(from: widgetAppIDsData)
        updated.insert(app.id)
        widgetAppIDsData = encodeUUIDSetGlobal(updated)
        NotificationCenter.default.post(
            name: .openWidgetsPage,
            object: nil,
            userInfo: ["appID": app.id]
        )
    }

    private func openWidgetAction(_ action: WidgetAction) {
        guard let base = app.webURL else { return }
        let target = resolvedURL(base: base, actionPath: action.urlPath)
        InAppBrowserWindowManager.shared.open(url: target, title: app.name)
    }

    private func resolvedURL(base: URL, actionPath: String?) -> URL {
        guard let actionPath, !actionPath.isEmpty else { return base }
        if actionPath.hasPrefix("http://") || actionPath.hasPrefix("https://") {
            return URL(string: actionPath) ?? base
        }
        if actionPath.hasPrefix("/") {
            var c = URLComponents(url: base, resolvingAgainstBaseURL: false)
            c?.path = actionPath
            return c?.url ?? base
        }
        return URL(string: actionPath, relativeTo: base)?.absoluteURL ?? base
    }

    private func addToDesktop() {
        guard let webURL = app.webURL else {
            showAlert(title: "Unavailable", message: "Desktop shortcuts need a web address.")
            return
        }
        DesktopWeblocShortcutPresenter.beginSavePanel(forAppNamed: app.name, webURL: webURL)
    }

    private func showAlert(title: String, message: String) {
        let alert = NSAlert()
        alert.messageText = title
        alert.informativeText = message
        alert.addButton(withTitle: "OK")
        alert.alertStyle = .informational
        alert.runModal()
    }

    private var titleColor: Color {
        colorScheme == .dark ? .white.opacity(0.94) : .primary.opacity(0.94)
    }

    private var pinForegroundColor: Color {
        colorScheme == .dark ? .white.opacity(0.92) : .primary.opacity(0.88)
    }

    private var controlForegroundColor: Color {
        colorScheme == .dark
        ? .white.opacity(isHovering ? 0.92 : 0.75)
        : .primary.opacity(isHovering ? 0.90 : 0.68)
    }

    private var controlBackgroundColor: Color {
        colorScheme == .dark
        ? .black.opacity(isHovering ? 0.35 : 0.2)
        : .black.opacity(isHovering ? 0.08 : 0.04)
    }

    private var controlStrokeColor: Color {
        colorScheme == .dark
        ? .white.opacity(isHovering ? 0.18 : 0.12)
        : .black.opacity(isHovering ? 0.12 : 0.07)
    }

    private var controlShadowColor: Color {
        .black.opacity(colorScheme == .dark ? 0.22 : 0.05)
    }

    private var rowFillColor: Color {
        colorScheme == .dark
        ? .white.opacity(isHovering ? 0.18 : 0)
        : .white.opacity(isHovering ? 0.60 : 0)
    }

    private var rowGradientColorTop: Color {
        colorScheme == .dark
        ? .white.opacity(isHovering ? 0.38 : 0)
        : .black.opacity(isHovering ? 0.10 : 0)
    }

    private var rowGradientColorBottom: Color {
        colorScheme == .dark
        ? .white.opacity(isHovering ? 0.14 : 0)
        : .black.opacity(isHovering ? 0.05 : 0)
    }

    private var rowShadowColor: Color {
        .black.opacity(colorScheme == .dark ? (isHovering ? 0.35 : 0) : (isHovering ? 0.06 : 0))
    }
}

private struct PanelQuickActionButton: View {
    private static let iconPointSize: CGFloat = 15
    private static let circleDiameter: CGFloat = 32

    let action: WidgetAction
    let onTap: () -> Void

    @Environment(\.colorScheme) private var colorScheme
    @State private var isHovering = false

    private var highlight: Bool { isHovering }

    var body: some View {
        Button(action: onTap) {
            Image(systemName: action.systemImage)
                .font(.system(size: Self.iconPointSize, weight: .semibold))
                .foregroundStyle(buttonIconColor)
                .frame(width: Self.circleDiameter, height: Self.circleDiameter)
                .background(
                    Circle()
                        .fill(buttonBackgroundColor)
                )
                .overlay(
                    Circle()
                        .stroke(buttonStrokeColor, lineWidth: 1)
                )
                .shadow(color: buttonShadowColor, radius: highlight ? 12 : 8, x: 0, y: 4)
        }
        .buttonStyle(.plain)
        .animation(.spring(response: 0.28, dampingFraction: 0.85), value: highlight)
        .overlay(alignment: .top) {
            Text(action.title)
                .font(.system(size: 10, weight: .semibold))
                .foregroundStyle(tooltipForeground)
                .lineLimit(1)
                .fixedSize(horizontal: true, vertical: false)
                .padding(.horizontal, 7)
                .padding(.vertical, 3)
                .background(
                    Capsule(style: .continuous)
                        .fill(tooltipBackground)
                )
                .overlay(
                    Capsule(style: .continuous)
                        .stroke(tooltipStroke, lineWidth: 1)
                )
                .shadow(color: tooltipShadow, radius: 7, x: 0, y: 3)
                .offset(y: -(Self.circleDiameter / 2 + 14))
                .opacity(isHovering ? 1 : 0)
                .scaleEffect(isHovering ? 1 : 0.88, anchor: .bottom)
                .offset(y: isHovering ? 0 : 5)
                .animation(.spring(response: 0.28, dampingFraction: 0.82), value: isHovering)
                .allowsHitTesting(false)
        }
        .onHover { isHovering = $0 }
        .accessibilityLabel(action.title)
    }

    private var buttonIconColor: Color {
        colorScheme == .dark ? .white.opacity(0.82) : .primary.opacity(0.82)
    }

    private var buttonBackgroundColor: Color {
        colorScheme == .dark
        ? .black.opacity(highlight ? 0.48 : 0.22)
        : .black.opacity(highlight ? 0.11 : 0.05)
    }

    private var buttonStrokeColor: Color {
        colorScheme == .dark
        ? .white.opacity(highlight ? 0.18 : 0.10)
        : .black.opacity(highlight ? 0.14 : 0.08)
    }

    private var buttonShadowColor: Color {
        .black.opacity(colorScheme == .dark ? (highlight ? 0.28 : 0.20) : (highlight ? 0.10 : 0.04))
    }

    private var tooltipForeground: Color {
        colorScheme == .dark ? .white.opacity(0.96) : .primary.opacity(0.92)
    }

    private var tooltipBackground: Color {
        colorScheme == .dark ? .black.opacity(0.78) : .white.opacity(0.97)
    }

    private var tooltipStroke: Color {
        colorScheme == .dark ? .white.opacity(0.12) : .black.opacity(0.08)
    }

    private var tooltipShadow: Color {
        .black.opacity(colorScheme == .dark ? 0.32 : 0.06)
    }
}

private struct AppIconGlyph: View {
    let app: LauncherApp
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(colorScheme == .dark ? Color.white.opacity(0.08) : Color.black.opacity(0.06))
                .frame(width: 48, height: 48)

            if let webURL = app.webURL {
                WebSiteFaviconView(
                    webURL: webURL,
                    size: 32,
                    assetIconName: app.assetIconName,
                    fallbackSymbolName: app.fallbackSymbolName,
                    iconPaddingFactor: 0.08
                )
            } else if let icon = NSImage(named: app.assetIconName) {
                Image(nsImage: icon)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 32, height: 32)
            } else {
                Image(systemName: app.fallbackSymbolName)
                    .font(.system(size: 20, weight: .semibold))
                    .foregroundStyle(colorScheme == .dark ? .white : .primary)
            }
        }
    }
}

// MARK: - Pinned app persistence helpers (panel mode)
private func decodeUUIDSetGlobal(from dataString: String) -> Set<UUID> {
    guard !dataString.isEmpty, let data = dataString.data(using: .utf8) else { return [] }
    do {
        let strings = try JSONDecoder().decode([String].self, from: data)
        return Set(strings.compactMap(UUID.init(uuidString:)))
    } catch {
        return []
    }
}

private func encodeUUIDSetGlobal(_ set: Set<UUID>) -> String {
    do {
        let strings = set.map(\.uuidString).sorted()
        let data = try JSONEncoder().encode(strings)
        return String(decoding: data, as: UTF8.self)
    } catch {
        return ""
    }
}

private struct PromoBanner: View {
    let isPremiumUnlocked: Bool
    let activePremiumProductID: PremiumProductID?
    let onCloseTap: () -> Void
    let onUpgradeTap: () -> Void
    let onManageSubscriptionTap: () -> Void
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        HStack(spacing: 10) {
            Button(action: onCloseTap) {
                Image(systemName: "xmark")
                    .foregroundStyle(.blue.opacity(0.95))
                    .font(.system(size: 14, weight: .semibold))
            }
            .buttonStyle(.plain)

            Text(isPremiumUnlocked ? "Premium is active. Manage your subscription any time." : "Update to Premium to unlock all features and remove ads")
                .font(.system(size: 13, weight: .medium))
                .foregroundStyle(.blue.opacity(0.95))
                .lineLimit(1)

            Spacer()

            HStack(spacing: 8) {
                if isPremiumUnlocked && activePremiumProductID == .yearly {
                    Button(action: onUpgradeTap) {
                        Text("Upgrade Plan")
                            .font(.system(size: 13, weight: .semibold))
                            .foregroundStyle(.white)
                            .padding(.horizontal, 12)
                            .padding(.vertical, 6)
                            .background(
                                Capsule(style: .continuous)
                                    .fill(Color(red: 1.0, green: 0.67, blue: 0.2).opacity(0.95))
                            )
                    }
                    .buttonStyle(.plain)

                    Button(action: onManageSubscriptionTap) {
                        Text("Manage Subscription")
                            .font(.system(size: 13, weight: .semibold))
                            .foregroundStyle(.white)
                            .padding(.horizontal, 12)
                            .padding(.vertical, 6)
                            .background(
                                Capsule(style: .continuous)
                                    .fill(Color.blue.opacity(0.92))
                            )
                    }
                    .buttonStyle(.plain)
                } else {
                    Button(action: isPremiumUnlocked ? onManageSubscriptionTap : onUpgradeTap) {
                        Text(isPremiumUnlocked ? "Manage Subscription" : "Upgrade")
                            .font(.system(size: 13, weight: .semibold))
                            .foregroundStyle(.white)
                            .padding(.horizontal, 12)
                            .padding(.vertical, 6)
                            .background(
                                Capsule(style: .continuous)
                                    .fill(Color.blue.opacity(0.92))
                            )
                    }
                    .buttonStyle(.plain)
                }
            }
        }
        .padding(.horizontal, 11)
        .padding(.vertical, 8)
        .background(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(colorScheme == .dark ? Color.white.opacity(0.03) : Color.white.opacity(0.72))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .stroke(colorScheme == .dark ? Color.white.opacity(0.05) : Color.black.opacity(0.06), lineWidth: 1)
        )
    }
}

private struct ManageSubscriptionSheet: View {
    @Environment(\.dismiss) private var dismiss
    let activePremiumProductID: PremiumProductID?
    let onUpgradeTap: () -> Void
    private let url = URL(string: "https://apps.apple.com/account/subscriptions")!

    var body: some View {
        VStack(spacing: 0) {
            HStack {
                Text("Manage Subscription")
                    .font(.system(size: 15, weight: .semibold))
                    .foregroundStyle(.white.opacity(0.92))
                Spacer()
                Button("Done") { dismiss() }
                    .buttonStyle(.borderedProminent)
                    .controlSize(.small)
            }
            .padding(12)
            .background(Color.black.opacity(0.25))

            if activePremiumProductID == .yearly {
                HStack(spacing: 10) {
                    Text("You are on the yearly plan. Upgrade to Lifetime for permanent access.")
                        .font(.system(size: 12.5, weight: .medium))
                        .foregroundStyle(.white.opacity(0.9))
                    Spacer()
                    Button("Upgrade Package") {
                        onUpgradeTap()
                    }
                    .buttonStyle(.borderedProminent)
                    .controlSize(.small)
                }
                .padding(.horizontal, 12)
                .padding(.vertical, 10)
                .background(Color.blue.opacity(0.18))
            }

            SubscriptionWebView(url: url)
        }
        .background(Color.black.opacity(0.3))
    }
}

private struct SubscriptionWebView: NSViewRepresentable {
    let url: URL

    func makeNSView(context: Context) -> WKWebView {
        let webView = WKWebView()
        webView.allowsBackForwardNavigationGestures = true
        webView.load(URLRequest(url: url))
        return webView
    }

    func updateNSView(_ webView: WKWebView, context: Context) {}
}

private struct HideMenuIndicatorIfAvailable: ViewModifier {
    func body(content: Content) -> some View {
        if #available(macOS 13.0, *) {
            content.menuIndicator(.hidden)
        } else {
            content
        }
    }
}

#if DEBUG
#Preview {
    LauncherRootView()
        .environmentObject(PremiumStore.shared)
}
#endif
