import CoreGraphics
import Foundation

struct WidgetAction: Identifiable, Hashable {
    let id: String
    let title: String
    let systemImage: String
    /// If nil, opens the app base URL.
    let urlPath: String?

    init(id: String, title: String, systemImage: String, urlPath: String? = nil) {
        self.id = id
        self.title = title
        self.systemImage = systemImage
        self.urlPath = urlPath
    }
}

enum GmailWidgetMode: Hashable, Sendable {
    /// Unread count (local placeholder) and quick compose.
    case compact
    /// Embedded Gmail (`WKWebView`) with search, category chips, and folder shortcuts.
    case interactive
}

enum DriveWidgetMode: Hashable, Sendable {
    /// Sample recent files and shortcuts to Drive recent.
    case recentQuick
    /// Recent + starred sample rows with open / share.
    case filesPreview
    /// Shortcut hub: search, folders, quick find, create, and tools — open in app browser (no embedded Drive UI).
    case interactive
}

enum KeepWidgetMode: Hashable, Sendable {
    /// Recent note snippets and new-note shortcut.
    case compact
    /// Embedded Google Keep (`WKWebView`) with search, labels, and create options.
    case interactive
}

enum TranslateWidgetMode: Hashable, Sendable {
    /// Embedded Google Translate (`WKWebView`): text, voice, conversation, history.
    case interactive
}

enum EarthWidgetMode: Hashable, Sendable {
    /// Embedded Google Earth Web (`WKWebView`): globe, layers, Voyager, projects.
    case interactive
}

enum GoogleSearchWidgetMode: Hashable, Sendable {
    /// Embedded google.com (`WKWebView`): search, voice, trends, recents.
    case interactive
}

enum CalendarWidgetMode: Hashable, Sendable {
    /// Today’s date and the next upcoming event (from Apple Calendar / synced accounts).
    case compact
    /// Multi-day agenda with quick links to create events in Google Calendar.
    case agenda
}

enum BooksWidgetMode: Hashable, Sendable {
    /// Embedded Google Books (`WKWebView`): library, reading progress, bookmarks, recommendations, search.
    case interactive
}

enum TravelWidgetMode: Hashable, Sendable {
    /// Toolbar search plus Explore / Flights / Hotels / Vacation rentals shortcuts; in-app browser (no embedded trip UI).
    case interactive
}

enum ShoppingWidgetMode: Hashable, Sendable {
    /// Toolbar search plus Shop / Deals / Lists / Browse shortcuts; in-app browser.
    case interactive
}

enum YoutubeWidgetMode: Hashable, Sendable {
    /// Search field opens YouTube results in the in-app browser.
    case searchBar
    /// Home, Shorts, Subscriptions, and extra feed shortcuts in the in-app browser.
    case interactiveNav
}

enum MeetWidgetMode: Hashable, Sendable {
    /// Quick join next or last Meet link from Calendar.
    case compact
    /// Upcoming Meet-backed meetings with join buttons.
    case agenda
    /// Hub: details, schedule, recent links, shortcuts (opens Meet/Calendar in-app).
    case hub
}

enum ContactsWidgetMode: Hashable, Sendable {
    /// Medium only: search field (opens Google Contacts with query) + Favorites / Other shortcuts.
    case searchHub
}

enum NewsWidgetMode: Hashable, Sendable {
    /// Search bar plus top headlines (local sample; opens Google News in-app).
    case headlines
    /// Search, topic shortcuts, and “Picks for You” with tappable titles.
    case picksHub
}

enum FormsWidgetMode: Hashable, Sendable {
    /// Mini dashboard: recent/drafts, create shortcuts, response summary, share, deadlines (sample data; opens Forms in-app).
    case dashboard
}

enum DocsWidgetMode: Hashable, Sendable {
    /// Find + create shortcuts (docs.new, templates, home); opens in-app browser.
    case dashboard
}

enum SheetsWidgetMode: Hashable, Sendable {
    /// Find + create shortcuts (sheets.new, templates, home); opens in-app browser.
    case dashboard
}

enum SlidesWidgetMode: Hashable, Sendable {
    /// Find + create shortcuts (slides.new, templates, home); opens in-app browser.
    case dashboard
}

enum GooglePlayWidgetMode: Hashable, Sendable {
    /// Medium: app search + shortcut to open the Play Store in-app browser.
    case searchBar
}

enum GeminiWidgetMode: Hashable, Sendable {
    /// Large: prompt panel; opens gemini.google.com in-app.
    case assistant
}

enum WidgetLayoutMode: Hashable {
    case iconOnly(title: String)
    case actionsRow
    case actionsGrid(columns: Int)
    /// Embedded Google Maps (`WKWebView`) with search, traffic, and navigation shortcuts.
    case interactiveMap
    /// Tiered Gmail: compact preview or full embedded inbox.
    case gmail(GmailWidgetMode)
    /// Tiered Google Drive: recent files, starred preview, or large shortcut hub.
    case drive(DriveWidgetMode)
    /// Tiered Google Keep: recent notes or full embedded Keep.
    case keep(KeepWidgetMode)
    /// Embedded Google Translate: full translator UI in-panel.
    case translate(TranslateWidgetMode)
    /// Embedded Google Earth Web: full globe UI in-panel.
    case earth(EarthWidgetMode)
    /// Embedded Google Search (`WKWebView`): full search experience in-panel.
    case googleSearch(GoogleSearchWidgetMode)
    /// Today + next event (small) or multi-day agenda + quick add (medium); uses EventKit + Google Calendar links.
    case calendar(CalendarWidgetMode)
    /// Embedded Google Books (`WKWebView`): library, reading, bookmarks, discover, search.
    case books(BooksWidgetMode)
    /// Google Travel: search, toolbar, and category shortcuts; full site in browser (no trip panel).
    case travel(TravelWidgetMode)
    /// Google Shopping: search, toolbar, and category shortcuts; full site in browser.
    case shopping(ShoppingWidgetMode)
    /// YouTube: medium search bar or large navigation shortcuts; opens in-app browser.
    case youtube(YoutubeWidgetMode)
    /// Google Meet: Calendar-backed Meet links; join/create via in-app browser.
    case meet(MeetWidgetMode)
    /// Google Contacts: local interactive list with search + call/message/email actions.
    case contacts(ContactsWidgetMode)
    /// Google News: search + headlines (medium) or topics + picks (large); opens in-app browser.
    case news(NewsWidgetMode)
    /// Google Forms: large interactive dashboard; small/medium use default grids.
    case forms(FormsWidgetMode)
    /// Google Docs: large dashboard like Forms.
    case docs(DocsWidgetMode)
    /// Google Sheets: large dashboard like Forms.
    case sheets(SheetsWidgetMode)
    /// Google Slides: large dashboard like Forms.
    case slides(SlidesWidgetMode)
    /// Google Play: medium search bar + open store.
    case googlePlay(GooglePlayWidgetMode)
    /// Google Gemini: large mini-assistant panel.
    case gemini(GeminiWidgetMode)
    /// Google Photos: medium embedded recent gallery + shortcuts (single variant).
    case photos
}

struct WidgetVariant: Identifiable, Hashable {
    let id: String
    let title: String
    let size: WidgetSize
    let showHeader: Bool
    let layoutMode: WidgetLayoutMode
    let actions: [WidgetAction]
}

enum WidgetTemplates {
    static func variants(for app: LauncherApp) -> [WidgetVariant] {
        func v(_ id: String, _ title: String, _ size: WidgetSize, _ showHeader: Bool, _ layout: WidgetLayoutMode, _ actions: [WidgetAction]) -> WidgetVariant {
            WidgetVariant(id: id, title: title, size: size, showHeader: showHeader, layoutMode: layout, actions: actions)
        }

        let profile = WidgetAppProfile.from(app: app)
        switch profile {
        case .photos:
            return [
                v("photos_medium", "Recent gallery", .medium, false, .photos, []),
            ]
        case .gmail:
            return [
                v("gmail_small", "Unread & compose", .small, false, .gmail(.compact), []),
                v("gmail_large", "Full inbox", .large, false, .gmail(.interactive), []),
            ]
        case .drive:
            return [
                v("drive_small", "Recent files", .small, false, .drive(.recentQuick), []),
                v("drive_medium", "Files & sharing", .medium, false, .drive(.filesPreview), []),
                v("drive_large", "Full Drive", .large, false, .drive(.interactive), []),
            ]
        case .docs:
            return [
                v("docs_small", "Docs", .small, false, .iconOnly(title: "Docs"), [a("home", "Open", "doc.text.fill", "/document/u/0/")]),
                v("docs_large", "Docs dashboard", .large, false, .docs(.dashboard), []),
            ]
        case .sheets:
            return [
                v("sheets_small", "Sheets", .small, false, .iconOnly(title: "Sheets"), [a("home", "Open", "tablecells.fill", "/spreadsheets/u/0/")]),
                v("sheets_large", "Sheets dashboard", .large, false, .sheets(.dashboard), []),
            ]
        case .slides:
            return [
                v("slides_small", "Slides", .small, false, .iconOnly(title: "Slides"), [a("home", "Open", "rectangle.on.rectangle.fill", "/presentation/u/0/")]),
                v("slides_large", "Slides dashboard", .large, false, .slides(.dashboard), []),
            ]
        case .forms:
            return [
                v("forms_small", "Forms", .small, false, .iconOnly(title: "Forms"), [a("home", "Open", "list.bullet.rectangle.fill", "/forms/u/0/")]),
                v("forms_large", "Forms dashboard", .large, false, .forms(.dashboard), []),
            ]
        case .calendar:
            return [
                v("calendar_small", "Today & next", .small, false, .calendar(.compact), []),
                v("calendar_medium", "Agenda", .medium, false, .calendar(.agenda), []),
            ]
        case .maps:
            // Live map: `MapsInteractiveWidgetView` (embedded `WKWebView`); actions unused.
            return [
                v("maps_small", "Live map", .small, true, .interactiveMap, []),
                v("maps_medium", "Live map", .medium, true, .interactiveMap, []),
                v("maps_large", "Live map", .large, true, .interactiveMap, []),
            ]
        case .youtube:
            return [
                v("youtube_medium", "Search", .medium, false, .youtube(.searchBar), []),
                v("youtube_large", "Navigate", .large, false, .youtube(.interactiveNav), []),
            ]
        case .translate:
            return [
                v("translate_small", "Translate", .small, false, .actionsRow, [
                    a("open", "Translator", "character.bubble.fill", "/"),
                    a("history", "History", "clock", "/m/"),
                    a("saved", "Phrasebook", "bookmark", "/m/"),
                ]),
                v("translate_medium", "Translate Tools", .medium, true, .actionsGrid(columns: 2), [
                    a("open", "Web translator", "globe", "/"),
                    a("docs", "Documents", "doc.text", "https://translate.google.com/?tr=f&hl=en&tab=wT"),
                    a("conversation", "Conversation", "bubble.left.and.bubble.right", "/m/"),
                    a("history", "History", "clock", "/m/"),
                ]),
                v("translate_large", "Full Translate", .large, false, .translate(.interactive), []),
            ]
        case .search:
            return [
                v("search_medium", "Google Shortcuts", .medium, true, .actionsGrid(columns: 2), [
                    a("web", "Web", "globe", "/"),
                    a("images", "Images", "photo", "https://www.google.com/imghp"),
                    a("shopping", "Shopping", "bag", "https://www.google.com/search?tbm=shop"),
                    a("maps", "Maps", "map", "https://maps.google.com/maps"),
                ]),
                v("search_large", "Full Search", .large, false, .googleSearch(.interactive), []),
            ]
        case .earth:
            return [
                v("earth_medium", "Earth Explorer", .medium, true, .actionsGrid(columns: 2), [
                    a("open", "Open", "globe.americas.fill", "/web/"),
                    a("voyager", "Voyager", "sailboat", "/web/data/"),
                    a("projects", "Projects", "folder", "/web/projects/"),
                    a("search", "Search", "magnifyingglass", "/web/search"),
                ]),
                v("earth_large", "Full Earth", .large, false, .earth(.interactive), []),
            ]
        case .shopping:
            return [
                v("shopping_small", "Shopping", .small, false, .iconOnly(title: "Shopping"), [a("open", "Open", "bag.fill", "/")]),
                v("shopping_medium", "Shopping shortcuts", .medium, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/"),
                    a("deals", "Deals", "tag", "/deals"),
                    a("lists", "Lists", "list.bullet.rectangle", "/lists"),
                    a("browse", "Browse", "square.grid.2x2", "/browse"),
                ]),
                v("shopping_large", "Shop & deals", .large, false, .shopping(.interactive), []),
            ]
        case .keep:
            return [
                v("keep_small", "Recent notes", .small, false, .keep(.compact), []),
                v("keep_large", "Full Keep", .large, false, .keep(.interactive), []),
            ]
        case .travel:
            return [
                v("travel_small", "Travel", .small, false, .iconOnly(title: "Travel"), [a("trips", "Trips", "airplane", "/travel")]),
                v("travel_medium", "Trip Planner", .medium, true, .actionsGrid(columns: 2), [
                    a("trips", "Trips", "airplane", "/travel"),
                    a("explore", "Explore", "globe", "/travel/explore"),
                    a("flights", "Flights", "airplane.departure", "/flights"),
                    a("hotels", "Hotels", "bed.double", "/hotels"),
                ]),
                v("travel_large", "Trips & explore", .large, false, .travel(.interactive), []),
            ]
        case .meet:
            return [
                v("meet_small", "Quick join", .small, false, .meet(.compact), []),
                v("meet_medium", "Upcoming", .medium, false, .meet(.agenda), []),
                v("meet_large", "Meet hub", .large, false, .meet(.hub), []),
            ]
        case .contacts:
            return [
                v("contacts_medium", "Search", .medium, false, .contacts(.searchHub), []),
            ]
        case .blogger:
            return [
                v("blogger_small", "Blogger", .small, false, .iconOnly(title: "Blogger"), [a("home", "Open", "text.book.closed.fill", "/")]),
                v("blogger_medium", "Blogger Tools", .medium, true, .actionsGrid(columns: 2), [
                    a("home", "Reading list", "list.bullet", "/"),
                    a("create", "New post", "square.and.pencil", "/"),
                    a("reading", "Reading list", "book", "/reading-list"),
                    a("search", "Search", "magnifyingglass", "/"),
                ]),
            ]
        case .play:
            return [
                v("play_small", "Play", .small, false, .iconOnly(title: "Play"), [a("store", "Store", "play.circle.fill", "/store/apps")]),
                v("play_medium", "Play search", .medium, false, .googlePlay(.searchBar), []),
            ]
        case .news:
            return [
                v("news_small", "News", .small, false, .iconOnly(title: "News"), [a("home", "Headlines", "newspaper.fill", "/home")]),
                v("news_medium", "Headlines & search", .medium, false, .news(.headlines), []),
                v("news_large", "Picks & topics", .large, false, .news(.picksHub), []),
            ]
        case .chat:
            return [
                v("chat_small", "Chat", .small, false, .actionsRow, [
                    a("home", "Home", "bubble.left.and.bubble.right.fill", "/u/0/"),
                    a("spaces", "Spaces", "square.grid.3x3.fill", "/u/0/spaces"),
                    a("meet", "Meet", "video", "https://meet.google.com/"),
                ]),
                v("chat_large", "Chat Hub", .large, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/u/0/"),
                    a("spaces", "Spaces", "square.grid.3x3.fill", "/u/0/spaces"),
                    a("browse", "Browse", "square.grid.2x2", "/u/0/browse"),
                    a("meet", "Meet", "video", "https://meet.google.com/"),
                ]),
            ]
        case .finance:
            return [
                v("finance_small", "Finance", .small, false, .actionsRow, [
                    a("home", "Markets", "chart.line.uptrend.xyaxis", "/finance"),
                    a("markets", "Markets", "chart.bar", "/finance/markets"),
                    a("portfolio", "Portfolio", "briefcase", "/finance/portfolio"),
                ]),
                v("finance_medium", "Finance Hub", .medium, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/finance"),
                    a("markets", "Markets", "chart.bar", "/finance/markets"),
                    a("portfolio", "Portfolio", "briefcase", "/finance/portfolio"),
                    a("news", "News", "newspaper", "/finance/news"),
                ]),
            ]
        case .jamboard:
            return [
                v("jamboard_small", "Jamboard", .small, false, .actionsRow, [
                    a("open", "Boards", "paintbrush.fill", "/"),
                    a("create", "New jam", "plus", "/"),
                    a("shared", "Shared", "person.2", "/"),
                ]),
                v("jamboard_large", "Jamboard", .large, true, .actionsGrid(columns: 2), [
                    a("open", "Home", "house", "/"),
                    a("create", "New jam", "plus", "/"),
                    a("shared", "Shared with me", "person.2", "/"),
                    a("trash", "Trash", "trash", "/"),
                ]),
            ]
        case .classroom:
            return [
                v("classroom_small", "Classroom", .small, false, .actionsRow, [
                    a("home", "Classes", "graduationcap.fill", "/u/0/h/"),
                    a("calendar", "Calendar", "calendar", "/u/0/calendar"),
                    a("todo", "To-do", "checklist", "/u/0/notifications"),
                ]),
                v("classroom_large", "Classroom Hub", .large, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/u/0/h/"),
                    a("calendar", "Calendar", "calendar", "/u/0/calendar"),
                    a("archive", "Archive", "archivebox", "/u/0/archived"),
                    a("settings", "Settings", "gearshape", "/u/0/settings"),
                ]),
            ]
        case .artsCulture:
            return [
                v("arts_small", "Arts & Culture", .small, false, .iconOnly(title: "Arts"), [a("home", "Explore", "building.columns.fill", "/")]),
                v("arts_medium", "Arts Browse", .medium, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/"),
                    a("explore", "Explore", "globe", "/explore"),
                    a("nearby", "Near you", "mappin.and.ellipse", "/nearby"),
                    a("search", "Search", "magnifyingglass", "/search"),
                ]),
            ]
        case .voice:
            return [
                v("voice_small", "Voice", .small, false, .actionsRow, [
                    a("messages", "Messages", "bubble.left", "/messages"),
                    a("calls", "Calls", "phone", "/calls"),
                    a("voicemail", "Voicemail", "recordingtape", "/voicemail"),
                ]),
                v("voice_large", "Voice Hub", .large, true, .actionsGrid(columns: 2), [
                    a("messages", "Messages", "bubble.left", "/messages"),
                    a("calls", "Calls", "phone", "/calls"),
                    a("voicemail", "Voicemail", "recordingtape", "/voicemail"),
                    a("settings", "Settings", "gearshape", "/settings"),
                ]),
            ]
        case .chromeWebStore:
            return [
                v("webstore_small", "Web Store", .small, false, .iconOnly(title: "Web Store"), [a("home", "Store", "bag.fill", "/webstore")]),
                v("webstore_medium", "Chrome Web Store", .medium, true, .actionsGrid(columns: 2), [
                    a("extensions", "Extensions", "puzzlepiece.extension", "/webstore/category/extensions"),
                    a("themes", "Themes", "paintpalette", "/webstore/category/themes"),
                    a("home", "Home", "house", "/webstore"),
                    a("search", "Search", "magnifyingglass", "/webstore/search"),
                ]),
            ]
        case .fi:
            return [
                v("fi_small", "Fi", .small, false, .actionsRow, [
                    a("home", "Account", "antenna.radiowaves.left.and.right", "/"),
                    a("billing", "Billing", "creditcard", "/billing"),
                    a("coverage", "Coverage", "map", "/coverage"),
                ]),
                v("fi_medium", "Google Fi", .medium, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/"),
                    a("billing", "Billing", "creditcard", "/billing"),
                    a("coverage", "Coverage", "map", "/coverage"),
                    a("support", "Support", "questionmark.circle", "/support"),
                ]),
            ]
        case .ads:
            return [
                v("ads_small", "Ads", .small, false, .actionsRow, [
                    a("home", "Overview", "megaphone.fill", "/aw/"),
                    a("campaigns", "Campaigns", "chart.bar", "/aw/campaigns"),
                    a("reports", "Reports", "doc.text", "/aw/reports"),
                ]),
                v("ads_medium", "Google Ads", .medium, true, .actionsGrid(columns: 2), [
                    a("home", "Home", "house", "/aw/"),
                    a("campaigns", "Campaigns", "chart.bar", "/aw/campaigns"),
                    a("tools", "Tools", "wrench", "/aw/tools"),
                    a("billing", "Billing", "creditcard", "/aw/billing"),
                ]),
            ]
        case .remoteDesktop:
            return [
                v("remote_small", "Remote", .small, false, .actionsRow, [
                    a("access", "Remote access", "desktopcomputer", "/access"),
                    a("support", "Support", "person.crop.circle", "/support"),
                    a("downloads", "Downloads", "arrow.down.circle", "/"),
                ]),
                v("remote_large", "Remote Desktop", .large, true, .actionsGrid(columns: 2), [
                    a("access", "Remote access", "desktopcomputer", "/access"),
                    a("support", "Support", "person.crop.circle", "/support"),
                    a("downloads", "Downloads", "arrow.down.circle", "/"),
                    a("help", "Help", "questionmark.circle", "/support"),
                ]),
            ]
        case .gemini:
            return [
                v("gemini_large", "Gemini", .large, false, .gemini(.assistant), []),
            ]
        case .books:
            return [
                v("books_large", "Library & reading", .large, false, .books(.interactive), []),
            ]
        case .unknown:
            return genericUtilityVariants(prefix: "generic", appName: app.name)
        }
    }

    static func variant(for app: LauncherApp, variantID: String?) -> WidgetVariant {
        let variants = variants(for: app)
        let normalizedID: String? = {
            guard let id = variantID else { return nil }
            if WidgetAppProfile.from(app: app) == .contacts {
                if id == "contacts_small" || id == "contacts_large" { return "contacts_medium" }
            }
            if WidgetAppProfile.from(app: app) == .gmail, id == "gmail_medium" {
                return "gmail_large"
            }
            if WidgetAppProfile.from(app: app) == .photos {
                if id == "photos_small" || id == "photos_large" { return "photos_medium" }
            }
            return id
        }()
        if let normalizedID, let match = variants.first(where: { $0.id == normalizedID }) {
            return match
        }
        return variants.first ?? WidgetVariant(
            id: "fallback_small",
            title: "Quick Access",
            size: .small,
            showHeader: false,
            layoutMode: .actionsRow,
            actions: [a("open", "Open", "arrow.up.right", nil)]
        )
    }
}

private extension WidgetTemplates {
    static func genericUtilityVariants(prefix: String, appName: String) -> [WidgetVariant] {
        [
            WidgetVariant(
                id: "\(prefix)_small",
                title: "Quick Access",
                size: .small,
                showHeader: false,
                layoutMode: .actionsRow,
                actions: [
                    a("open", "Open", "arrow.up.right", "/"),
                    a("search", "Search", "magnifyingglass", "/"),
                    a("favorites", "Favorites", "star", "/"),
                ]
            ),
            WidgetVariant(
                id: "\(prefix)_medium",
                title: appName,
                size: .medium,
                showHeader: true,
                layoutMode: .actionsGrid(columns: 2),
                actions: [
                    a("open", "Open", "arrow.up.right", "/"),
                    a("search", "Search", "magnifyingglass", "/"),
                    a("favorites", "Favorites", "star", "/"),
                    a("recent", "Recent", "clock.arrow.circlepath", "/"),
                ]
            ),
        ]
    }

    static func a(_ id: String, _ title: String, _ image: String, _ path: String?) -> WidgetAction {
        WidgetAction(id: id, title: title, systemImage: image, urlPath: path)
    }
}

extension WidgetLayoutMode {
    /// Panels that embed interactive web content need to become key so text fields and the web view receive input.
    var needsKeyCapablePanel: Bool {
        switch self {
        case .interactiveMap: true
        case .gmail(.interactive): true
        case .drive(.interactive): true
        case .keep(.interactive): true
        case .translate(.interactive): true
        case .earth(.interactive): true
        case .googleSearch(.interactive): true
        case .books(.interactive): true
        case .travel(.interactive): true
        case .shopping(.interactive): true
        case .youtube(.searchBar): true
        case .youtube(.interactiveNav): true
        case .news(_): true
        case .contacts(.searchHub): true
        case .forms(_): true
        case .docs(_): true
        case .sheets(_): true
        case .slides(_): true
        case .googlePlay(_): true
        case .gemini(_): true
        case .photos: true
        case .meet: false
        case .calendar: false
        default: false
        }
    }

    /// Fixed sizes for tiered Gmail / Drive widgets (variant size already matches the tier).
    func desktopPanelSizeOverride() -> CGSize? {
        switch self {
        case .gmail(let mode):
            switch mode {
            case .compact: return CGSize(width: 220, height: 220)
            case .interactive: return CGSize(width: 540, height: 580)
            }
        case .drive(let mode):
            switch mode {
            case .recentQuick: return CGSize(width: 230, height: 260)
            case .filesPreview: return CGSize(width: 420, height: 480)
            case .interactive: return CGSize(width: 540, height: 580)
            }
        case .keep(let mode):
            switch mode {
            case .compact: return CGSize(width: 230, height: 260)
            case .interactive: return CGSize(width: 540, height: 580)
            }
        case .translate(.interactive):
            return CGSize(width: 560, height: 640)
        case .earth(.interactive):
            return CGSize(width: 580, height: 640)
        case .googleSearch(.interactive):
            return CGSize(width: 480, height: 220)
        case .calendar(let mode):
            switch mode {
            case .compact: return CGSize(width: 230, height: 260)
            case .agenda: return CGSize(width: 420, height: 480)
            }
        case .books(.interactive):
            return CGSize(width: 420, height: 260)
        case .travel(.interactive):
            return CGSize(width: 520, height: 196)
        case .shopping(.interactive):
            return CGSize(width: 520, height: 196)
        case .youtube(.searchBar):
            return CGSize(width: 400, height: 118)
        case .youtube(.interactiveNav):
            return CGSize(width: 420, height: 292)
        case .meet(let mode):
            switch mode {
            case .compact: return CGSize(width: 240, height: 220)
            case .agenda: return CGSize(width: 400, height: 280)
            case .hub: return CGSize(width: 440, height: 520)
            }
        case .contacts(.searchHub):
            return CGSize(width: 380, height: 178)
        case .news(let mode):
            switch mode {
            case .headlines: return CGSize(width: 420, height: 340)
            case .picksHub: return CGSize(width: 440, height: 520)
            }
        case .forms(.dashboard):
            return CGSize(width: 440, height: 300)
        case .docs(.dashboard):
            return CGSize(width: 440, height: 300)
        case .sheets(.dashboard):
            return CGSize(width: 440, height: 300)
        case .slides(.dashboard):
            return CGSize(width: 440, height: 300)
        case .googlePlay(.searchBar):
            return CGSize(width: 400, height: 168)
        case .gemini(.assistant):
            return CGSize(width: 400, height: 236)
        case .photos:
            return CGSize(width: 420, height: 460)
        default:
            return nil
        }
    }

    func widgetPreviewCardSize() -> CGSize? {
        switch self {
        case .gmail(let mode):
            switch mode {
            case .compact: return CGSize(width: 200, height: 200)
            case .interactive: return CGSize(width: 480, height: 440)
            }
        case .drive(let mode):
            switch mode {
            case .recentQuick: return CGSize(width: 210, height: 230)
            case .filesPreview: return CGSize(width: 380, height: 420)
            case .interactive: return CGSize(width: 480, height: 440)
            }
        case .keep(let mode):
            switch mode {
            case .compact: return CGSize(width: 210, height: 230)
            case .interactive: return CGSize(width: 480, height: 440)
            }
        case .translate(.interactive):
            return CGSize(width: 480, height: 460)
        case .earth(.interactive):
            return CGSize(width: 490, height: 470)
        case .googleSearch(.interactive):
            return CGSize(width: 440, height: 210)
        case .calendar(let mode):
            switch mode {
            case .compact: return CGSize(width: 210, height: 240)
            case .agenda: return CGSize(width: 380, height: 440)
            }
        case .books(.interactive):
            return CGSize(width: 380, height: 240)
        case .travel(.interactive):
            return CGSize(width: 460, height: 178)
        case .shopping(.interactive):
            return CGSize(width: 460, height: 178)
        case .youtube(.searchBar):
            return CGSize(width: 360, height: 108)
        case .youtube(.interactiveNav):
            return CGSize(width: 380, height: 268)
        case .meet(let mode):
            switch mode {
            case .compact: return CGSize(width: 210, height: 200)
            case .agenda: return CGSize(width: 360, height: 250)
            case .hub: return CGSize(width: 380, height: 480)
            }
        case .contacts(.searchHub):
            return CGSize(width: 340, height: 152)
        case .news(let mode):
            switch mode {
            case .headlines: return CGSize(width: 380, height: 300)
            case .picksHub: return CGSize(width: 380, height: 480)
            }
        case .forms(.dashboard):
            return CGSize(width: 380, height: 280)
        case .docs(.dashboard):
            return CGSize(width: 380, height: 280)
        case .sheets(.dashboard):
            return CGSize(width: 380, height: 280)
        case .slides(.dashboard):
            return CGSize(width: 380, height: 280)
        case .googlePlay(.searchBar):
            return CGSize(width: 360, height: 158)
        case .gemini(.assistant):
            return CGSize(width: 400, height: 236)
        case .photos:
            return CGSize(width: 380, height: 420)
        default:
            return nil
        }
    }
}
