import SwiftUI

@main
struct NucleicRemoteApp: App {
    // Bind to the shared instance so the background push handler (which runs with no mounted scene)
    // and the UI drive the same store.
    @StateObject private var store = RemoteStore.shared
    @Environment(\.scenePhase) private var scenePhase
    @UIApplicationDelegateAdaptor(PushAppDelegate.self) private var pushDelegate

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(store)
                // A Live Activity tap arrives as a `nucleic://` deep link: open the waiting
                // session, or surface the sessions list when work is only running.
                .onOpenURL { store.handleDeepLink($0) }
                .onAppear {
                    store.onAppear()
                    // Notification categories + delegate must be in place before any
                    // notification can arrive, so actions/taps always route.
                    NotificationRouter.shared.install(store: store)
                    // Surface the notifications prompt + register for APNS. The token rides along
                    // in the sync Hello; the relay uses it to wake the phone for approvals (§3).
                    // Skipped in demo mode so offline UI previews aren't blocked by the system dialog.
                    if ProcessInfo.processInfo.environment["NUCLEIC_DEMO"] != "1" {
                        PushRegistrar.shared.requestAuthorizationAndRegister()
                    }
                    // Anonymous, opt-out, once-a-day DAI heartbeat (docs/CLOUD_INFRA.md §4).
                    Task { await HeartbeatReporter.reportIfDue() }
                }
                // Re-check on every foreground so a new active day is counted; keep the store's
                // activity flag current so it only notifies for background transitions.
                .onChange(of: scenePhase) { _, phase in
                    store.setScenePhaseActive(phase == .active)
                    switch phase {
                    case .active:
                        // Re-plan transports immediately on foreground — the network may have
                        // changed while suspended, so don't wait for a dead socket to notice.
                        store.onForeground()
                        Task { await HeartbeatReporter.reportIfDue() }
                    case .background:
                        // Hold the process (and its live sockets) briefly so a quick app-switch
                        // doesn't drop the connection and force a cold reconnect on return.
                        store.onBackground()
                    default:
                        break
                    }
                }
        }
    }
}

struct RootView: View {
    @EnvironmentObject var store: RemoteStore
    // Appearance preferences — same storage keys and semantics as the Mac's Settings →
    // Appearance, so both devices honor the same choices.
    @AppStorage(AppAppearance.storageKey) private var appearanceRaw = AppAppearance.system.rawValue
    @AppStorage(AppTextSize.storageKey) private var textSizeRaw = AppTextSize.medium.rawValue
    @AppStorage(ColorVisionMode.storageKey) private var colorVisionRaw = ColorVisionMode.standard.rawValue

    var body: some View {
        VStack(spacing: 0) {
            // The same non-release warning strip the Mac window carries (dev/canary/beta/rc).
            BuildBanner()
            // A demo-mode strip so sample data is never mistaken for a live Mac connection.
            DemoBanner()
            Group {
                if store.isPaired {
                    // Width-adaptive shell: the iPhone's TabView on compact width, the Mac's
                    // sidebar+detail split on a regular-width iPad (see AdaptiveRootView).
                    AdaptiveRootView()
                } else {
                    PairingIntroView()
                }
            }
        }
        .overlay(alignment: .bottom) { ErrorBubble() }
        .tint(Palette.accent)
        .preferredColorScheme((AppAppearance(rawValue: appearanceRaw) ?? .system).colorScheme)
        .dynamicTypeSize((AppTextSize(rawValue: textSizeRaw) ?? .medium).dynamicTypeSize)
        // The palette resolves against the stored color-vision mode at render time; rebuild
        // the hierarchy when it changes so every cached view picks up the remapped colors.
        .id(colorVisionRaw)
    }
}

/// A transient red bubble for host-reported errors — the mobile echo of the Mac's last-error
/// overlay at the detail pane's bottom. Auto-dismissed by the store after a few seconds; a tap
/// dismisses it immediately.
private struct ErrorBubble: View {
    @EnvironmentObject var store: RemoteStore

    var body: some View {
        if let error = store.lastError {
            HStack(spacing: 8) {
                Image(systemName: "exclamationmark.triangle.fill")
                Text(error.message).lineLimit(3)
                Spacer(minLength: 0)
                Image(systemName: "xmark").opacity(0.7)
            }
            .font(.footnote.weight(.medium))
            .foregroundStyle(.white)
            .padding(.horizontal, 12)
            .padding(.vertical, 10)
            .background(Palette.danger, in: RoundedRectangle(cornerRadius: 12))
            .padding(.horizontal, 16)
            .padding(.bottom, 58)  // clear the tab bar
            .onTapGesture { store.dismissError() }
            .transition(.move(edge: .bottom).combined(with: .opacity))
            .animation(.easeInOut(duration: 0.2), value: store.lastError)
        }
    }
}
