import SwiftUI
import AppKit

extension Notification.Name {
    static let openWidgetsPage = Notification.Name("openWidgetsPage")
}

struct WidgetsRootView: View {
    let apps: [LauncherApp]
    @Binding var selectedAppID: UUID?
    @Binding var widgetLibraryIDsData: String

    @ObservedObject private var desktopWidgetManager = DesktopWidgetWindowManager.shared
    @ObservedObject private var premiumStore = PremiumStore.shared
    @Environment(\.colorScheme) private var colorScheme
    @State private var query = ""
    @State private var toastMessage: String?
    enum ToastKind {
        case added
        case removed
    }
    @State private var toastKind: ToastKind?
    @State private var toastTask: Task<Void, Never>?
    @State private var showingPremiumScreen = false

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

    private var selectedApp: LauncherApp? {
        guard let selectedAppID else { return nil }
        return apps.first { $0.id == selectedAppID }
    }

    private var selectedVariants: [WidgetVariant] {
        guard let selectedApp else { return [] }
        return WidgetTemplates.variants(for: selectedApp)
    }

    var body: some View {
        HStack(spacing: 14) {
            sidebar
                .frame(width: 260)

            Divider()
                .overlay(dividerColor)

            mainContent
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
        }
        .padding(.horizontal, 10)
        .padding(.bottom, 12)
        .overlay(alignment: .top) {
            if let toastMessage, let toastKind {
                DesktopWidgetToast(message: toastMessage, kind: toastKind)
                    .padding(.top, 6)
                    .transition(.move(edge: .top).combined(with: .opacity))
            }
        }
        .animation(.spring(response: 0.28, dampingFraction: 0.9), value: toastMessage != nil)
        .sheet(isPresented: $showingPremiumScreen) {
            PremiumFeaturesView()
                .environmentObject(premiumStore)
                .frame(minWidth: 560, minHeight: 690)
        }
    }

    private var sidebar: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("All Widgets")
                .font(.system(size: 16, weight: .bold))
                .foregroundStyle(primaryTextColor)
                .padding(.top, 6)

            HStack(spacing: 10) {
                Image(systemName: "magnifyingglass")
                    .foregroundStyle(secondaryTextColor)
                TextField("Search widgets", text: $query)
                    .textFieldStyle(.plain)
                    .foregroundStyle(primaryTextColor)
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 9)
            .background(
                RoundedRectangle(cornerRadius: 14, style: .continuous)
                    .fill(searchBackgroundColor)
            )
            .overlay(
                RoundedRectangle(cornerRadius: 14, style: .continuous)
                    .stroke(searchStrokeColor, lineWidth: 1)
            )

            ScrollView {
                VStack(alignment: .leading, spacing: 6) {
                    ForEach(filteredApps) { app in
                        WidgetSidebarRow(
                            app: app,
                            isSelected: app.id == selectedAppID,
                            onSelect: {
                                selectedAppID = app.id
                                ensureInWidgetLibrary(appID: app.id)
                            }
                        )
                    }
                }
                .padding(.vertical, 4)
            }
        }
        .padding(.horizontal, 10)
        .padding(.vertical, 10)
        .background(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .fill(panelFillColor)
        )
        .overlay(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .stroke(panelStrokeColor, lineWidth: 1)
        )
    }

    private var mainContent: some View {
        VStack(alignment: .leading, spacing: 14) {
            if let selectedApp {
                header(for: selectedApp)

                Text("Widget")
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(secondaryTextColor)
                    .padding(.top, 4)

                widgetPreviewPickerRow(app: selectedApp, variants: selectedVariants)
            } else {
                emptyState
            }

            Spacer(minLength: 0)
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 10)
        .background(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .fill(panelSecondaryFillColor)
        )
        .overlay(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .stroke(panelStrokeColor, lineWidth: 1)
        )
    }

    @ViewBuilder
    private func widgetPreviewPickerRow(app: LauncherApp, variants: [WidgetVariant]) -> some View {
        if variants.isEmpty {
            Color.clear.frame(height: 0)
        } else {
            let rowHeightAtFullScale = variants.map(\.previewCardSize.height).max() ?? 220
            GeometryReader { geo in
                let spacing: CGFloat = 18
                /// Space for floating + / − controls that sit slightly outside the card clip.
                let horizontalChrome: CGFloat = 28
                let count = variants.count
                let sumWidths = variants.reduce(CGFloat(0)) { $0 + $1.previewCardSize.width }
                let rawRowWidth = sumWidths + CGFloat(max(count - 1, 0)) * spacing
                let available = max(geo.size.width - horizontalChrome, 50)
                let unclampedScale = available / max(rawRowWidth, 1)
                let minLayoutScale: CGFloat = 0.38
                let scale = max(minLayoutScale, min(1, unclampedScale))

                HStack(alignment: .top, spacing: spacing) {
                    ForEach(variants) { variant in
                        WidgetPreviewAddCard(
                            app: app,
                            variant: variant,
                            layoutScale: scale,
                            isAdded: isWidgetAdded(appID: app.id, variantID: variant.id),
                            isPremiumUnlocked: premiumStore.isPremiumUnlocked,
                            onToggle: { handleWidgetTap(app: app, variant: variant) }
                        )
                    }
                    Spacer(minLength: 0)
                }
                .frame(width: geo.size.width, height: geo.size.height, alignment: .topLeading)
            }
            .frame(height: rowHeightAtFullScale)
        }
    }

    private func header(for app: LauncherApp) -> some View {
        HStack(spacing: 12) {
            AppIconView(app: app, size: 42, showAppBackground: false, iconPaddingFactor: 0.1)
                .shadow(color: .black.opacity(0.2), radius: 10, x: 0, y: 6)
            VStack(alignment: .leading, spacing: 3) {
                Text(app.name)
                    .font(.system(size: 20, weight: .bold))
                    .foregroundStyle(primaryTextColor)
                Text("Click a widget card to add it. Click again to remove it.")
                    .font(.system(size: 12.5, weight: .medium))
                    .foregroundStyle(tertiaryTextColor)
            }
            Spacer()
        }
    }

    private var emptyState: some View {
        VStack(spacing: 10) {
            Image(systemName: "widget.small")
                .font(.system(size: 30, weight: .semibold))
                .foregroundStyle(secondaryTextColor)
            Text("Select an app to preview widgets")
                .font(.title3.weight(.semibold))
                .foregroundStyle(primaryTextColor)
            Text("Pick any app from the left to customize its widget.")
                .font(.subheadline)
                .foregroundStyle(secondaryTextColor)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }

    private func ensureInWidgetLibrary(appID: UUID) {
        var updated = decodeUUIDSet(from: widgetLibraryIDsData)
        updated.insert(appID)
        widgetLibraryIDsData = encodeUUIDSet(updated)
    }

    private func addToDesktop(app: LauncherApp, variant: WidgetVariant) {
        guard !desktopWidgetManager.hasInstance(appID: app.id, variantID: variant.id) else {
            return
        }
        ensureInWidgetLibrary(appID: app.id)
        let instance = WidgetInstance(
            appID: app.id,
            variantID: variant.id,
            size: variant.size,
            origin: nil
        )
        desktopWidgetManager.show(instance: instance, appProvider: { id in
            apps.first { $0.id == id }
        })
        showAddedToast(for: app, variant: variant)
    }

    private func isWidgetAdded(appID: UUID, variantID: String) -> Bool {
        desktopWidgetManager.hasInstance(appID: appID, variantID: variantID)
    }

    private func handleWidgetTap(app: LauncherApp, variant: WidgetVariant) {
        if isWidgetAdded(appID: app.id, variantID: variant.id) {
            removeFromDesktop(app: app, variant: variant)
        } else {
            guard premiumStore.isPremiumUnlocked else {
                showingPremiumScreen = true
                return
            }
            addToDesktop(app: app, variant: variant)
        }
    }

    private func removeFromDesktop(app: LauncherApp, variant: WidgetVariant) {
        let removedCount = desktopWidgetManager.removeInstances(appID: app.id, variantID: variant.id)
        guard removedCount > 0 else { return }
        showRemovedToast(for: app)
    }

    private func showAddedToast(for app: LauncherApp, variant: WidgetVariant) {
        toastTask?.cancel()
        toastKind = .added
        toastMessage = "The \"\(app.name)\" \"\(variant.title)\" widget has been added to the desktop."
        toastTask = Task { @MainActor in
            try? await Task.sleep(nanoseconds: 2_000_000_000)
            guard !Task.isCancelled else { return }
            toastMessage = nil
            toastKind = nil
        }
    }

    private func showRemovedToast(for app: LauncherApp) {
        toastTask?.cancel()
        toastKind = .removed
        toastMessage = "The \"\(app.name)\" widget has been removed from the desktop."
        toastTask = Task { @MainActor in
            try? await Task.sleep(nanoseconds: 2_000_000_000)
            guard !Task.isCancelled else { return }
            toastMessage = nil
            toastKind = nil
        }
    }

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

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

    private var tertiaryTextColor: Color {
        colorScheme == .dark ? .white.opacity(0.65) : .primary.opacity(0.60)
    }

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

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

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

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

    private var panelSecondaryFillColor: Color {
        colorScheme == .dark ? .white.opacity(0.02) : .white.opacity(0.72)
    }

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

private struct WidgetSidebarRow: View {
    let app: LauncherApp
    let isSelected: Bool
    let onSelect: () -> Void

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

    var body: some View {
        Button(action: onSelect) {
            HStack(spacing: 10) {
                AppIconView(app: app, size: 30, showAppBackground: false, iconPaddingFactor: 0.08)
                Text(app.name)
                    .font(.system(size: 13.5, weight: .semibold))
                    .foregroundStyle(colorScheme == .dark ? .white.opacity(0.92) : .primary.opacity(0.92))
                    .lineLimit(1)
                Spacer(minLength: 0)
            }
            .padding(.horizontal, 10)
            .padding(.vertical, 8)
            .background(
                RoundedRectangle(cornerRadius: 12, style: .continuous)
                    .fill(backgroundFill)
            )
            .overlay(
                RoundedRectangle(cornerRadius: 12, style: .continuous)
                    .stroke(
                        colorScheme == .dark
                        ? Color.white.opacity(isSelected ? 0.18 : 0.06)
                        : Color.black.opacity(isSelected ? 0.16 : 0.06),
                        lineWidth: 1
                    )
            )
        }
        .buttonStyle(.plain)
        .onHover { hovering = $0 }
    }

    private var backgroundFill: Color {
        if isSelected { return Color.blue.opacity(colorScheme == .dark ? 0.18 : 0.12) }
        if hovering { return colorScheme == .dark ? Color.white.opacity(0.06) : Color.black.opacity(0.04) }
        return colorScheme == .dark ? Color.white.opacity(0.02) : Color.black.opacity(0.015)
    }
}

private struct WidgetPreviewAddCard: View {
    let app: LauncherApp
    let variant: WidgetVariant
    var layoutScale: CGFloat = 1
    let isAdded: Bool
    let isPremiumUnlocked: Bool
    let onToggle: () -> Void

    @State private var hovering = false

    var body: some View {
        ZStack(alignment: .topTrailing) {
            Button(action: onToggle) {
                WidgetPreviewCard(app: app, variant: variant, layoutScale: layoutScale)
                    .contentShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
            }
            .buttonStyle(.plain)

            // Separate control so taps on the floating + work: the card is clip-shaped, so the
            // offset circle was outside the label’s hit region when it was only an overlay.
            Button(action: onToggle) {
                Image(systemName: isAdded ? "minus" : "plus")
                    .font(.system(size: 12, weight: .bold))
                    .foregroundStyle(iconForegroundStyle)
                    .frame(width: 26, height: 26)
                    .background(
                        Circle()
                            .fill(iconBackgroundFill)
                    )
                    .shadow(color: .black.opacity(0.28), radius: 10, x: 0, y: 6)
                    .contentShape(Circle())
            }
            .buttonStyle(.plain)
            .offset(x: 10 * layoutScale, y: -10 * layoutScale)
            .opacity(isAdded ? 1 : (hovering ? 1 : 0))
            .animation(.easeOut(duration: 0.12), value: hovering)
        }
        .onHover { hovering = $0 }
    }

    private var iconForegroundStyle: some ShapeStyle {
        if isAdded { return .white.opacity(0.92) }
        return isPremiumUnlocked ? .black.opacity(0.85) : .white.opacity(0.82)
    }

    private var iconBackgroundFill: Color {
        if isAdded { return Color.red.opacity(0.92) }
        return isPremiumUnlocked ? Color.green.opacity(0.95) : Color.gray.opacity(0.7)
    }
}

private struct DesktopWidgetToast: View {
    let message: String
    let kind: WidgetsRootView.ToastKind
    @Environment(\.colorScheme) private var colorScheme

    private var iconAndTint: (String, Color) {
        switch kind {
        case .added:
            return ("checkmark.circle", Color.green.opacity(0.92))
        case .removed:
            return ("minus.circle", Color.red.opacity(0.92))
        }
    }

    var body: some View {
        let (systemImage, tint) = iconAndTint
        HStack(spacing: 12) {
            Image(systemName: systemImage)
                .font(.system(size: 18, weight: .semibold))
                .foregroundStyle(tint)

            Text(message)
                .font(.system(size: 14, weight: .semibold))
                .foregroundStyle(colorScheme == .dark ? .white.opacity(0.95) : .primary.opacity(0.95))
                .lineLimit(1)
                .multilineTextAlignment(.leading)

            Spacer(minLength: 0)
        }
        .padding(.horizontal, 14)
        .padding(.vertical, 10)
        .frame(maxWidth: 560)
        .background(
            RoundedRectangle(cornerRadius: 16, style: .continuous)
                .fill(colorScheme == .dark ? Color.black.opacity(0.82) : Color.white.opacity(0.95))
        )
        .overlay(
            RoundedRectangle(cornerRadius: 16, style: .continuous)
                .stroke(colorScheme == .dark ? Color.white.opacity(0.08) : Color.black.opacity(0.08), lineWidth: 1)
        )
    }
}

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 encodeUUIDSet(_ 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 ""
    }
}

#if DEBUG
#Preview {
    ZStack {
        Color.black
        WidgetsRootView(
            apps: LauncherApp.sampleApps,
            selectedAppID: .constant(LauncherApp.sampleApps.first?.id),
            widgetLibraryIDsData: .constant("")
        )
        .frame(width: 980, height: 650)
        .padding()
    }
}
#endif
