// App settings. The host creates a native virtual output at exactly the chosen size/refresh —
// there is no scaling anywhere in the pipeline.
//
// Navigation differs per platform, but all three group the same categories (General, Display,
// Audio, Controllers, Advanced, About): macOS uses a tabbed preferences window; iOS/iPadOS uses
// an adaptive NavigationSplitView — a category sidebar + detail pane on iPad, auto-collapsing to
// a hierarchical push list on iPhone (the system Settings idiom on each); tvOS uses a
// focus-native pushed-picker layout. The individual sections (`streamModeSection`,
// `audioSection`, …) are shared across all three so a setting is defined exactly once — they
// live in SettingsView+Sections.swift, with their helpers in SettingsView+Support.swift.

#if os(macOS)
import AppKit
#endif
import PunktfunkKit
import SwiftUI

@MainActor
struct SettingsView: View {
    @Environment(\.dismiss) private var dismiss
    @AppStorage(DefaultsKey.streamWidth) var width = 1920
    @AppStorage(DefaultsKey.streamHeight) var height = 1080
    @AppStorage(DefaultsKey.streamHz) var hz = 60
    @AppStorage(DefaultsKey.compositor) var compositor = 0
    @AppStorage(DefaultsKey.gamepadType) var gamepadType = 0
    @AppStorage(DefaultsKey.bitrateKbps) var bitrateKbps = 0
    @AppStorage(DefaultsKey.presenter) var presenter = "stage2"
    @AppStorage(DefaultsKey.hdrEnabled) var hdrEnabled = true
    @AppStorage(DefaultsKey.enable444) var enable444 = true
    @AppStorage(DefaultsKey.libraryEnabled) var libraryEnabled = false
    @AppStorage(DefaultsKey.fullscreenWhileStreaming) var fullscreenWhileStreaming = true
    @AppStorage(DefaultsKey.micEnabled) var micEnabled = true
    @AppStorage(DefaultsKey.audioChannels) var audioChannels = 2
    @AppStorage(DefaultsKey.codec) var codec = "auto"
    @AppStorage(DefaultsKey.hudEnabled) var hudEnabled = true
    @AppStorage(DefaultsKey.hudPlacement) var hudPlacement = HUDPlacement.topTrailing.rawValue
    @ObservedObject var gamepads = GamepadManager.shared
    #if !os(tvOS)
    @AppStorage(DefaultsKey.gamepadUIEnabled) var gamepadUIEnabled = true
    #endif
    #if DEBUG && !os(tvOS)
    @State var showControllerTest = false
    #endif
    #if os(iOS)
    @AppStorage(DefaultsKey.pointerCapture) var pointerCapture = true
    @AppStorage(DefaultsKey.touchMode) var touchMode = TouchInputMode.trackpad.rawValue
    // The sidebar selection drives the detail pane on iPad and the pushed sub-page on iPhone.
    // Width class decides the initial value: nil on iPhone (show the category list first),
    // General on iPad (a two-column layout should never open with an empty detail).
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    @State private var settingsSelection: SettingsCategory?
    // Tracked so the detail can show its own Done whenever the sidebar (and its Done) is off screen
    // — not just on iPhone, but on any iPad layout that collapses the sidebar to an overlay. Starts
    // .doubleColumn so iPad reliably opens with the sidebar (and its Done) visible.
    @State private var columnVisibility: NavigationSplitViewVisibility = .doubleColumn
    // Sticky once the wheel lands on "Custom…", so editing a width/height that briefly equals a
    // preset doesn't snap the wheel back off Custom. A stored non-preset value reads as custom even
    // when this is false (see `isCustomResolution`), so it survives relaunches without persisting.
    @State var customMode = false
    #endif
    #if os(macOS)
    @AppStorage(DefaultsKey.speakerUID) var speakerUID = ""
    @AppStorage(DefaultsKey.micUID) var micUID = ""
    @State var outputDevices: [AudioDevice] = []
    @State var inputDevices: [AudioDevice] = []
    #endif

    #if os(iOS)
    /// `initialCategory` is nil in the app (the list opens un-selected on iPhone; iPad lands on
    /// General via `onAppear`). The screenshot harness passes an explicit category so the captured
    /// shot opens on a real settings page (a populated detail) rather than the bare category list.
    init(initialCategory: SettingsCategory? = nil) {
        _settingsSelection = State(initialValue: initialCategory)
    }
    #endif

    var body: some View {
        #if os(tvOS)
        // Native tv pattern: no inline text entry (typing numbers with a remote is
        // miserable and the inline field chrome fights the focus system). Modes are
        // preset pickers that push selection lists like the system Settings app.
        tvBody
        #elseif os(macOS)
        macBody
        #else
        iosBody
        #endif
    }

    // MARK: - macOS: tabbed preferences

    #if os(macOS)
    private var macBody: some View {
        TabView {
            Form {
                streamModeSection
                compositorSection
            }
            .formStyle(.grouped)
            .tabItem { Label("General", systemImage: "gearshape") }

            Form {
                presenterSection
                hdrSection
                windowSection
                statisticsSection
            }
            .formStyle(.grouped)
            .tabItem { Label("Display", systemImage: "display") }

            Form {
                audioSection
            }
            .formStyle(.grouped)
            .onAppear {
                outputDevices = AudioDevices.outputs()
                inputDevices = AudioDevices.inputs()
            }
            .tabItem { Label("Audio", systemImage: "speaker.wave.2") }

            Form {
                controllersSection
            }
            .formStyle(.grouped)
            .onAppear {
                gamepads.refresh()
                gamepads.startDiscovery()
            }
            .onDisappear { gamepads.stopDiscovery() }
            .tabItem { Label("Controllers", systemImage: "gamecontroller") }

            Form {
                experimentalSection
            }
            .formStyle(.grouped)
            .tabItem { Label("Advanced", systemImage: "slider.horizontal.3") }

            AcknowledgementsView()
                .tabItem { Label("About", systemImage: "info.circle") }
        }
        .frame(width: 480, height: 460)
    }
    #endif

    // MARK: - iOS / iPadOS: adaptive split view

    #if os(iOS)
    private var iosBody: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            List(selection: $settingsSelection) {
                ForEach(SettingsCategory.allCases) { category in
                    // On iPhone the split view collapses to a push list, but a selection List
                    // draws no disclosure indicator of its own — add one in compact width for the
                    // expected drill-in affordance. On iPad the selected row highlights instead, so
                    // the chevron is omitted there.
                    HStack {
                        Label(category.title, systemImage: category.symbol)
                        if horizontalSizeClass == .compact {
                            Spacer()
                            Image(systemName: "chevron.forward")
                                .font(.footnote.weight(.semibold))
                                .foregroundStyle(.tertiary)
                                // Purely a drill-in affordance — the row's button trait already
                                // conveys "opens"; keep it out of the VoiceOver announcement.
                                .accessibilityHidden(true)
                        }
                    }
                    .tag(category)
                }
            }
            .navigationTitle("Settings")
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done") { dismiss() }
                }
            }
        } detail: {
            // NavigationSplitView hosts the detail in its own navigation context (its title bar),
            // so no inner NavigationStack — that would double the bar on iPad. On iPhone the split
            // view collapses to one stack and pushes this when a row is tapped. `?? .general` only
            // backs the brief pre-selection window; the list never auto-pushes on a nil selection.
            settingsDetail(settingsSelection ?? .general)
                // Keep a Done on the detail whenever the sidebar (and its Done) isn't on screen: the
                // iPhone push, or any iPad layout that collapsed the sidebar to an overlay. When the
                // sidebar is showing, its Done is the only one — so this stays hidden to avoid two.
                .toolbar {
                    if horizontalSizeClass == .compact || columnVisibility == .detailOnly {
                        ToolbarItem(placement: .confirmationAction) {
                            Button("Done") { dismiss() }
                        }
                    }
                }
        }
        .onAppear {
            if horizontalSizeClass == .regular, settingsSelection == nil {
                settingsSelection = .general
            }
            gamepads.refresh()
            gamepads.startDiscovery()
        }
        // A regular→regular launch sets the default above; this catches a compact→regular change
        // (e.g. an iPad leaving narrow split-screen multitasking) so the detail pane fills in.
        .onChange(of: horizontalSizeClass) { _, newValue in
            if newValue == .regular, settingsSelection == nil {
                settingsSelection = .general
            }
        }
        .onDisappear { gamepads.stopDiscovery() }
    }

    @ViewBuilder
    private func settingsDetail(_ category: SettingsCategory) -> some View {
        switch category {
        case .general:
            Form {
                streamModeSection
                pointerSection
                compositorSection
            }
            .formStyle(.grouped)
            .navigationTitle("General")
            .navigationBarTitleDisplayMode(.inline)
        case .display:
            Form {
                presenterSection
                hdrSection
                statisticsSection
            }
            .formStyle(.grouped)
            .navigationTitle("Display")
            .navigationBarTitleDisplayMode(.inline)
        case .audio:
            Form { audioSection }
                .formStyle(.grouped)
                .navigationTitle("Audio")
                .navigationBarTitleDisplayMode(.inline)
        case .controllers:
            Form { controllersSection }
                .formStyle(.grouped)
                .navigationTitle("Controllers")
                .navigationBarTitleDisplayMode(.inline)
        case .advanced:
            Form { experimentalSection }
                .formStyle(.grouped)
                .navigationTitle("Advanced")
                .navigationBarTitleDisplayMode(.inline)
        case .about:
            // Already a full scrollable view that sets its own "Acknowledgements" title; pin the
            // display mode inline to match the five sibling detail pages (it would otherwise inherit
            // the large title from the "Settings" sidebar root).
            AcknowledgementsView()
                .navigationBarTitleDisplayMode(.inline)
        }
    }
    #endif

    // MARK: - tvOS

    #if os(tvOS)
    private static let presets: [(label: String, tag: String)] = [
        ("720p @ 60", "1280x720x60"),
        ("1080p @ 60", "1920x1080x60"),
        ("4K @ 60", "3840x2160x60"),
    ]

    private var modeTag: Binding<String> {
        Binding(
            get: { "\(width)x\(height)x\(hz)" },
            set: { tag in
                let parts = tag.split(separator: "x").compactMap { Int($0) }
                guard parts.count == 3 else { return }
                width = parts[0]
                height = parts[1]
                hz = parts[2]
            })
    }

    private var hudEnabledTag: Binding<String> {
        Binding(get: { hudEnabled ? "on" : "off" }, set: { hudEnabled = $0 == "on" })
    }

    private var hdrEnabledTag: Binding<String> {
        Binding(get: { hdrEnabled ? "on" : "off" }, set: { hdrEnabled = $0 == "on" })
    }

    private var tvBody: some View {
        let currentTag = "\(width)x\(height)x\(hz)"
        let bounds = UIScreen.main.nativeBounds
        let nativeTag = "\(Int(max(bounds.width, bounds.height)))x"
            + "\(Int(min(bounds.width, bounds.height)))x\(UIScreen.main.maximumFramesPerSecond)"
        var options = Self.presets
        if !options.contains(where: { $0.tag == nativeTag }) {
            options.insert(("This TV (native)", nativeTag), at: 0)
        }
        if !options.contains(where: { $0.tag == currentTag }) {
            options.insert(("Custom (\(width)×\(height) @ \(hz))", currentTag), at: 0)
        }
        return ScrollView {
            VStack(spacing: 16) {
                TVSelectionRow(title: "Stream mode", options: options, selection: modeTag)
                TVSelectionRow(
                    title: "Bitrate",
                    options: SettingsOptions.bitrateOptions(current: bitrateKbps),
                    selection: $bitrateKbps)
                TVSelectionRow(
                    title: "Audio channels",
                    options: SettingsOptions.audioChannels,
                    selection: $audioChannels)
                if bitrateKbps > 1_000_000 {
                    Label(Self.gigabitWarning, systemImage: "exclamationmark.triangle.fill")
                        .font(.geist(12, relativeTo: .caption))
                        .foregroundStyle(.orange)
                        .multilineTextAlignment(.center)
                }
                TVSelectionRow(
                    title: "Compositor", options: SettingsOptions.compositors,
                    selection: $compositor)
                #if DEBUG
                TVSelectionRow(
                    title: "Presenter (debug)",
                    options: [("Stage 2 (default)", "stage2"), ("Stage 1 (debug)", "stage1")],
                    selection: $presenter)
                #endif
                TVSelectionRow(
                    title: "10-bit HDR",
                    options: [("On", "on"), ("Off", "off")], selection: hdrEnabledTag)
                Text("The host creates a virtual output at exactly this mode — native "
                    + "resolution, no scaling. \(Self.bitrateFooter) A specific compositor "
                    + "is honored only if available on the host.")
                    .font(.geist(12, relativeTo: .caption))
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.top, 8)
                TVSelectionRow(
                    title: "Statistics overlay",
                    options: [("On", "on"), ("Off", "off")], selection: hudEnabledTag)
                TVSelectionRow(
                    title: "Statistics position", options: SettingsOptions.hudPlacements,
                    selection: $hudPlacement)
                ForEach(gamepads.controllers) { controller in
                    controllerRow(controller)
                        .padding(.horizontal, 24)
                }
                TVSelectionRow(
                    title: "Use controller", options: controllerOptions,
                    selection: $gamepads.preferredID)
                TVSelectionRow(
                    title: "Controller type", options: SettingsOptions.padTypes,
                    selection: $gamepadType)
                Text(Self.controllersFooter)
                    .font(.geist(12, relativeTo: .caption))
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.top, 8)
                NavigationLink("Acknowledgements") { AcknowledgementsView() }
                    .padding(.top, 8)
            }
            .frame(maxWidth: 1000)
            .frame(maxWidth: .infinity)
            .padding(60)
        }
        .navigationTitle("Settings")
        .onAppear {
            gamepads.refresh()
            gamepads.startDiscovery()
        }
        .onDisappear { gamepads.stopDiscovery() }
    }
    #endif
}
