//
// OpenRocky — Voice-first AI Agent
// https://github.com/openrocky
//
// Developed by everettjf with the assistance of Claude Code and Codex.
// Date: 2026-03-25
// Copyright (c) 2026 everettjf. All rights reserved.
//

import SwiftUI
import Combine

struct ContentView: View {
    @StateObject private var chatProviderStore = OpenRockyProviderStore()
    @StateObject private var voiceProviderStore = OpenRockyRealtimeProviderStore()
    @ObservedObject private var shellRuntime = OpenRockyShellRuntime.shared
    @StateObject private var sessionRuntime = OpenRockySessionRuntime()
    @StateObject private var skillStore = OpenRockyBuiltInToolStore.shared
    @ObservedObject private var characterStore = OpenRockyCharacterStore.shared
    @AppStorage("rocky.onboarding.completed") private var onboardingCompleted = false
    @State private var showsOnboarding = false
    @State private var showsProviderSettings = false
    @State private var showsVoiceNotConfiguredAlert = false
    @State private var showsConversationList = false
    @State private var conversationID: String = ""
    @State private var chatRefreshToken: UUID = UUID()
    @State private var sidebarVisibility: NavigationSplitViewVisibility = .automatic
    /// Drives programmatic navigation from voice home → chat.
    @State private var navPath = NavigationPath()

    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    @Environment(\.scenePhase) private var scenePhase

    private let storage = OpenRockyPersistentStorageProvider.shared

    // MARK: - Body

    var body: some View {
        Group {
            if horizontalSizeClass == .regular {
                iPadLayout
            } else {
                iPhoneLayout
            }
        }
        .task {
            if conversationID.isEmpty {
                startNewConversation()
            }
            shellRuntime.bootstrapIfNeeded()
            sessionRuntime.conversationID = conversationID
            sessionRuntime.syncProviders(
                chatConfiguration: chatProviderStore.configuration,
                voiceConfiguration: voiceProviderStore.configuration
            )
            if !onboardingCompleted || !chatProviderStore.configuration.isConfigured {
                showsOnboarding = true
            }
            await autoEnterVoiceIfNeeded()
        }
        .onReceive(NotificationCenter.default.publisher(for: OpenRockyAppLifecycleService.willExitNotification)) { _ in
            if sessionRuntime.isMicrophoneActive {
                sessionRuntime.stopVoiceSession()
            }
        }
        .onChange(of: scenePhase) { _, newPhase in
            guard newPhase == .active else { return }
            // Check if Siri set the voice-start flag while the app was in the background
            if UserDefaults.standard.bool(forKey: "rocky.launch.startVoice") {
                UserDefaults.standard.set(false, forKey: "rocky.launch.startVoice")
                Task {
                    try? await Task.sleep(for: .seconds(0.5))
                    if !sessionRuntime.isMicrophoneActive {
                        startVoiceFromShortcut()
                    }
                }
            }
        }
        .onChange(of: conversationID) { _, newID in
            sessionRuntime.conversationID = newID
        }
        .onChange(of: chatProviderStore.configuration) { _, _ in
            sessionRuntime.syncProviders(
                chatConfiguration: chatProviderStore.configuration,
                voiceConfiguration: voiceProviderStore.configuration
            )
            // Force chat controller/client recreation when provider config changes,
            // even if high-level identity fields stay the same.
            chatRefreshToken = UUID()
        }
        .onChange(of: voiceProviderStore.configuration) { _, _ in
            sessionRuntime.syncProviders(
                chatConfiguration: chatProviderStore.configuration,
                voiceConfiguration: voiceProviderStore.configuration
            )
        }
        .onChange(of: sessionRuntime.isMicrophoneActive) { _, active in
            // Voice ↔ Media coordination: pause music while voice session holds the audio
            // session, resume after. Keeps the two features from fighting over output.
            if active {
                OpenRockyMediaPlayerService.shared.pauseForVoice()
            } else {
                OpenRockyMediaPlayerService.shared.resumeAfterVoice()
            }
        }
        .overlay(alignment: .top) {
            OpenRockyMediaPlayerBar()
                .padding(.top, 4)
        }
        .sheet(isPresented: $showsProviderSettings) {
            OpenRockyProviderSettingsView(
                providerStore: chatProviderStore,
                realtimeProviderStore: voiceProviderStore,
                skillStore: skillStore,
                characterStore: characterStore
            )
            .presentationSizing(.form)
        }
        .fullScreenCover(isPresented: $showsOnboarding, onDismiss: {
            onboardingCompleted = true
        }) {
            OpenRockyOnboardingView(
                providerStore: chatProviderStore,
                realtimeProviderStore: voiceProviderStore
            )
        }
        .overlay { keyboardShortcutButtons }
        .alert("Voice Not Configured", isPresented: $showsVoiceNotConfiguredAlert) {
            Button("Open Settings") {
                showsProviderSettings = true
            }
            Button("Cancel", role: .cancel) {}
        } message: {
            Text("Please set up a voice provider in Settings before starting a voice session.")
        }
        .sheet(item: $sessionRuntime.pendingPermissionPrompt) { kind in
            OpenRockyPermissionPromptView(
                kind: kind,
                onPrimaryAction: {
                    switch kind {
                    case .micPriming:
                        sessionRuntime.continueAfterMicPriming(voiceConfiguration: voiceProviderStore.configuration)
                    case .micDenied:
                        sessionRuntime.pendingPermissionPrompt = nil
                        OpenRockyPermissionCenter.openSettings()
                    }
                },
                onDismiss: {
                    sessionRuntime.pendingPermissionPrompt = nil
                }
            )
        }
    }

    // MARK: - iPhone Layout

    private var iPhoneLayout: some View {
        NavigationStack(path: $navPath) {
            voiceHomeView
                .navigationDestination(for: ChatDestination.self) { _ in
                    chatDetailView
                }
                .toolbarBackground(OpenRockyPalette.background, for: .navigationBar)
        }
        .sheet(isPresented: $showsConversationList) {
            OpenRockyConversationListView(
                conversations: storage.conversations,
                currentID: conversationID,
                onSelect: { id in conversationID = id },
                onNew: { startNewConversation() },
                onDelete: { id in storage.deleteConversation(id) }
            )
            .presentationDetents([.fraction(0.67), .large])
            .presentationDragIndicator(.visible)
            .presentationBackground(OpenRockyPalette.background)
        }
    }

    // MARK: - iPad Layout (sidebar + detail)

    private var iPadLayout: some View {
        NavigationSplitView(columnVisibility: $sidebarVisibility) {
            // Sidebar: conversation list
            OpenRockyConversationListContent(
                conversations: storage.conversations,
                currentID: conversationID,
                onSelect: { id in conversationID = id },
                onNew: { startNewConversation() },
                onDelete: { id in storage.deleteConversation(id) }
            )
            .navigationTitle("Rocky")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button { startNewConversation() } label: {
                        Image(systemName: "square.and.pencil")
                            .foregroundStyle(OpenRockyPalette.accent)
                    }
                }
                ToolbarItem(placement: .topBarLeading) {
                    Button { showsProviderSettings = true } label: {
                        Image(systemName: "gearshape.fill")
                            .foregroundStyle(OpenRockyPalette.muted)
                    }
                }
            }
            .toolbarBackground(OpenRockyPalette.background, for: .navigationBar)
        } detail: {
            NavigationStack(path: $navPath) {
                voiceHomeView
                    .navigationDestination(for: ChatDestination.self) { _ in
                        chatDetailView
                    }
                    .toolbarBackground(OpenRockyPalette.background, for: .navigationBar)
            }
        }
        .navigationSplitViewStyle(.balanced)
    }

    // MARK: - Voice Home (new default landing)

    private var voiceHomeView: some View {
        OpenRockyVoiceHomeView(
            sessionRuntime: sessionRuntime,
            chatProviderStore: chatProviderStore,
            realtimeProviderStore: voiceProviderStore,
            onOpenSettings: { showsProviderSettings = true },
            onOpenChat: { navPath.append(ChatDestination()) }
        )
        .navigationBarHidden(true)
    }

    // MARK: - Chat Detail (pushed from voice home)

    private var chatDetailView: some View {
        Group {
            if !conversationID.isEmpty {
                chatExperienceView
            }
        }
        .navigationTitle("Chat History")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Button {
                    showsConversationList = true
                } label: {
                    Image(systemName: "list.bullet")
                        .foregroundStyle(OpenRockyPalette.accent)
                }
            }
        }
        .toolbarBackground(OpenRockyPalette.background, for: .navigationBar)
    }

    private var chatExperienceView: some View {
        OpenRockyChatExperienceScreen(
            bootstrap: shellRuntime.probe,
            transcript: sessionRuntime.session.liveTranscript,
            providerConfiguration: chatProviderStore.configuration,
            skillStore: skillStore,
            contentTopInset: 0,
            conversationID: conversationID,
            refreshToken: chatRefreshToken,
            openSettings: { showsProviderSettings = true },
            onConversationListTap: {
                if horizontalSizeClass == .regular {
                    sidebarVisibility = .all
                } else {
                    showsConversationList = true
                }
            }
        )
    }

    /// Hidden buttons that register keyboard shortcuts for iPad external keyboards.
    private var keyboardShortcutButtons: some View {
        Group {
            Button("") { startNewConversation() }
                .keyboardShortcut("n", modifiers: .command)

            Button("") { showsProviderSettings = true }
                .keyboardShortcut(",", modifiers: .command)
        }
        .frame(width: 0, height: 0)
        .opacity(0)
        .allowsHitTesting(false)
    }

    private func startNewConversation() {
        conversationID = storage.createConversation()
    }

    /// Called by the Siri-launch flag — route through the voice home's normal gate so
    /// permission priming, config check, and voice-mode routing all still apply.
    private func startVoiceFromShortcut() {
        if !navPath.isEmpty { navPath.removeLast(navPath.count) }
        sessionRuntime.toggleVoiceSession(voiceConfiguration: voiceProviderStore.configuration)
    }

    /// Decide whether to auto-enter voice mode at cold-launch time:
    /// - Siri-launch: always enter (gated only on API-key presence)
    /// - Normal launch: enter only when the user has opted in via Preferences
    /// Either way, no API key = no auto-entry (avoids hitting an error screen on a
    /// fresh install where the user hasn't configured a voice provider yet).
    private func autoEnterVoiceIfNeeded() async {
        let siriLaunched = UserDefaults.standard.bool(forKey: "rocky.launch.startVoice")
        if siriLaunched {
            UserDefaults.standard.set(false, forKey: "rocky.launch.startVoice")
        }

        guard voiceProviderStore.configuration.isConfigured else { return }
        let shouldEnter = siriLaunched || OpenRockyPreferences.shared.voiceAutoStartOnLaunch
        guard shouldEnter else { return }

        try? await Task.sleep(for: .seconds(0.8))
        if !sessionRuntime.isMicrophoneActive {
            startVoiceFromShortcut()
        }
    }
}

/// Opaque token that represents the chat destination in the NavigationStack path.
/// Using a value type keeps the navigation stable across provider / conversation changes.
private struct ChatDestination: Hashable {}

#Preview {
    ContentView()
}
