// The gamepad-driven settings screen (iOS/iPadOS/macOS): the couch-relevant subset of SettingsView,
// restyled as a console settings page and fully navigable with a controller — up/down moves the
// focus bar, left/right steps the focused value, A cycles/toggles it, B closes. Shown from the
// gamepad home launcher (X); the touch SettingsView remains the full-fidelity editor (custom
// resolutions, the log bitrate slider, debug tools), and both write the same DefaultsKey storage,
// so values round-trip freely between the two.
//
// Rows are rebuilt from live @AppStorage on every render; the focus list dispatches adjust/
// activate back here BY ROW ID (see `adjust`/`activate`), so a stored input callback can never act
// on stale captured state. Left/right CLAMPS at a choice list's ends (the dull boundary thud tells
// the thumb it's the last option); A always cycles forward, wrapping, so every option is reachable
// with one button. Toggles read left = off, right = on — refusing a no-op with the same thud.

import PunktfunkKit
import SwiftUI
#if os(iOS) || os(macOS)
import GameController

struct GamepadSettingsView: View {
    @Environment(\.dismiss) private var dismiss
    @AppStorage(DefaultsKey.streamWidth) private var width = 1920
    @AppStorage(DefaultsKey.streamHeight) private var height = 1080
    @AppStorage(DefaultsKey.streamHz) private var hz = 60
    @AppStorage(DefaultsKey.compositor) private var compositor = 0
    @AppStorage(DefaultsKey.gamepadType) private var gamepadType = 0
    @AppStorage(DefaultsKey.bitrateKbps) private var bitrateKbps = 0
    @AppStorage(DefaultsKey.audioChannels) private var audioChannels = 2
    @AppStorage(DefaultsKey.hdrEnabled) private var hdrEnabled = true
    @AppStorage(DefaultsKey.enable444) private var enable444 = true
    @AppStorage(DefaultsKey.codec) private var codec = "auto"
    @AppStorage(DefaultsKey.micEnabled) private var micEnabled = true
    @AppStorage(DefaultsKey.hudEnabled) private var hudEnabled = true
    @AppStorage(DefaultsKey.hudPlacement) private var hudPlacement = HUDPlacement.topTrailing.rawValue
    @AppStorage(DefaultsKey.libraryEnabled) private var libraryEnabled = false
    @AppStorage(DefaultsKey.gamepadUIEnabled) private var gamepadUIEnabled = true
    @ObservedObject private var gamepads = GamepadManager.shared

    #if os(iOS)
    /// `.compact` in a landscape phone window — tighter chrome so more rows fit.
    @Environment(\.verticalSizeClass) private var vSizeClass

    private var compact: Bool { vSizeClass == .compact }
    #else
    private let compact = false // no size classes on macOS; the sheet is sized generously
    #endif
    @State private var focusID: String?

    var body: some View {
        GamepadMenuList(
            items: rows,
            focusID: $focusID,
            onAdjust: { row, delta in adjust(id: row.id, by: delta) },
            onActivate: { activate(id: $0.id) },
            onBack: { dismiss() }
        ) { row, focused in
            rowView(row, focused: focused)
                .frame(maxWidth: 620)
                .padding(.horizontal, 24)
        }
        .frame(maxWidth: .infinity)
        .safeAreaInset(edge: .top, spacing: 0) {
            Text("Settings")
                .font(.geist(compact ? 20 : 30, .bold, relativeTo: .title))
                .foregroundStyle(.white)
                .padding(.top, gamepadTitleTopPadding(compact: compact))
                .padding(.bottom, compact ? 4 : 8)
                .frame(maxWidth: .infinity)
                .overlay(alignment: .trailing) { closeButton.padding(.trailing, 20) }
                .background { GamepadTrayScrim(edge: .top) }
        }
        .safeAreaInset(edge: .bottom, alignment: .leading, spacing: 0) {
            VStack(alignment: .leading, spacing: 8) {
                Text(focusedDetail)
                    .font(.geist(13, relativeTo: .caption))
                    .foregroundStyle(.white.opacity(0.55))
                    .lineLimit(2, reservesSpace: true)
                    .animation(.smooth(duration: 0.2), value: focusID)
                GamepadHintBar(hints: [
                    .init(glyph: "arrow.left.and.right", text: "Adjust"),
                    .init(glyph: buttonGlyph(\.buttonA, fallback: "a.circle"), text: "Change"),
                    .init(glyph: buttonGlyph(\.buttonB, fallback: "b.circle"), text: "Done"),
                ])
            }
            .padding(.leading, 22)
            .padding(.trailing, 22)
            .padding(.vertical, compact ? 6 : 10)
            .frame(maxWidth: .infinity, alignment: .leading)
            .background { GamepadTrayScrim(edge: .bottom) }
        }
        .background { GamepadScreenBackground() }
        .onAppear {
            gamepads.refresh()
            gamepads.startDiscovery()
        }
        .onDisappear { gamepads.stopDiscovery() }
    }

    /// Touch/click fallback for closing — the controller path is B, a hardware keyboard's Esc
    /// rides the cancel action.
    private var closeButton: some View {
        Button { dismiss() } label: {
            Image(systemName: "xmark")
                .font(.system(size: 14, weight: .semibold))
                .foregroundStyle(.white)
                .frame(width: 34, height: 34)
                .glassBackground(Circle(), interactive: true)
                .contentShape(Circle())
        }
        .buttonStyle(.plain)
        .keyboardShortcut(.cancelAction)
        .accessibilityLabel("Close settings")
    }

    // MARK: - Row rendering

    private func rowView(_ row: Row, focused: Bool) -> some View {
        VStack(alignment: .leading, spacing: 6) {
            if let header = row.header {
                Text(header)
                    .font(.geist(12, .semibold, relativeTo: .caption))
                    .tracking(1.4)
                    .foregroundStyle(.white.opacity(0.45))
                    .padding(.leading, 16)
                    .padding(.top, 14)
            }
            HStack(spacing: 14) {
                Image(systemName: row.icon)
                    .font(.system(size: 17))
                    .foregroundStyle(focused ? Color.brand : .white.opacity(0.55))
                    .frame(width: 28)
                Text(row.label)
                    .font(.geist(16, .semibold, relativeTo: .body))
                    .foregroundStyle(.white)
                    .lineLimit(1)
                Spacer(minLength: 12)
                HStack(spacing: 9) {
                    Image(systemName: "chevron.left")
                        .font(.system(size: 12, weight: .semibold))
                        .foregroundStyle(.white.opacity(focused ? 0.6 : 0))
                    Text(row.value)
                        .font(.geist(15, .medium, relativeTo: .callout))
                        .foregroundStyle(focused ? .white : .white.opacity(0.6))
                        .lineLimit(1)
                    Image(systemName: "chevron.right")
                        .font(.system(size: 12, weight: .semibold))
                        .foregroundStyle(.white.opacity(focused ? 0.6 : 0))
                }
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 13)
            .background {
                RoundedRectangle(cornerRadius: 14, style: .continuous)
                    .fill(.white.opacity(focused ? 0.1 : 0))
            }
            .overlay {
                RoundedRectangle(cornerRadius: 14, style: .continuous)
                    .strokeBorder(.white.opacity(focused ? 0.22 : 0), lineWidth: 1)
            }
            .scaleEffect(focused ? 1.0 : 0.98)
            .animation(.smooth(duration: 0.18), value: focused)
        }
    }

    private var focusedDetail: String {
        rows.first { $0.id == focusID }?.detail ?? " "
    }

    // MARK: - Row model

    private struct Row: Identifiable {
        let id: String
        /// Section header drawn above this row (the first row of each group carries it).
        var header: String?
        let icon: String
        let label: String
        let value: String
        /// One-line explanation shown near the hint bar while this row is focused.
        let detail: String
        /// Left/right step; returns whether the value actually changed (false ⇒ boundary thud).
        let adjust: (Int) -> Bool
        /// A — cycle forward (wrapping) / flip.
        let activate: () -> Void
    }

    /// Dispatch by id so the focus list's stored input callbacks always act on freshly built rows
    /// (never on state captured at wire time).
    private func adjust(id: String, by delta: Int) -> Bool {
        rows.first { $0.id == id }?.adjust(delta) ?? false
    }

    private func activate(id: String) {
        rows.first { $0.id == id }?.activate()
    }

    private var rows: [Row] {
        let resolution = resolutionOptions
        let refresh = SettingsOptions.refreshRates(including: hz)
            .map { (label: "\($0) Hz", tag: $0) }
        let bitrate = SettingsOptions.bitrateOptions(current: bitrateKbps)
        let controllers = SettingsOptions.controllerOptions(gamepads)
        return [
            choiceRow(
                id: "resolution", header: "Stream", icon: "aspectratio",
                label: "Resolution",
                detail: "The host creates a virtual display at exactly this size — no scaling.",
                options: resolution, current: "\(width)x\(height)"
            ) { tag in
                let parts = tag.split(separator: "x").compactMap { Int($0) }
                guard parts.count == 2 else { return }
                width = parts[0]
                height = parts[1]
            },
            choiceRow(
                id: "refresh", icon: "gauge.with.needle", label: "Refresh rate",
                detail: "Rates this display can actually show.",
                options: refresh, current: hz
            ) { hz = $0 },
            choiceRow(
                id: "bitrate", icon: "speedometer", label: "Bitrate",
                detail: "Automatic uses the host's default (20 Mbps). "
                    + "Run a speed test from the touch UI for an informed value.",
                options: bitrate, current: bitrateKbps
            ) { bitrateKbps = $0 },
            choiceRow(
                id: "compositor", icon: "macwindow", label: "Compositor",
                detail: "Which compositor drives the virtual output — honored only if "
                    + "available on the host.",
                options: SettingsOptions.compositors, current: compositor
            ) { compositor = $0 },

            choiceRow(
                id: "codec", header: "Video", icon: "film", label: "Video codec",
                detail: "A preference — the host falls back if it can't encode this one "
                    + "(10-bit and 4:4:4 are HEVC-only).",
                options: SettingsOptions.codecs, current: codec
            ) { codec = $0 },
            toggleRow(
                id: "hdr", icon: "sun.max", label: "10-bit HDR",
                detail: "HDR10 — engages when the host sends HDR content and this display "
                    + "supports it.",
                value: $hdrEnabled),
            toggleRow(
                id: "chroma", icon: "textformat", label: "Full chroma (4:4:4)",
                detail: "Sharper text and UI at more bandwidth — needs host opt-in and "
                    + "hardware decode.",
                value: $enable444),

            choiceRow(
                id: "audio", header: "Audio", icon: "speaker.wave.2", label: "Audio channels",
                detail: "The speaker layout requested from the host.",
                options: SettingsOptions.audioChannels, current: audioChannels
            ) { audioChannels = $0 },
            toggleRow(
                id: "mic", icon: "mic", label: "Microphone",
                detail: "Send this device's microphone to the host's virtual mic.",
                value: $micEnabled),

            choiceRow(
                id: "pad", header: "Controller", icon: "gamecontroller", label: "Use controller",
                detail: "Which pad is forwarded to the host, as player 1.",
                options: controllers, current: gamepads.preferredID
            ) { gamepads.preferredID = $0 },
            choiceRow(
                id: "padType", icon: "dpad", label: "Controller type",
                detail: "The virtual pad the host creates — Automatic matches this controller.",
                options: SettingsOptions.padTypes, current: gamepadType
            ) { gamepadType = $0 },

            toggleRow(
                id: "hud", header: "Interface", icon: "chart.bar", label: "Statistics overlay",
                detail: "Resolution, frame rate, throughput and latency while streaming.",
                value: $hudEnabled),
            choiceRow(
                id: "hudPlacement", icon: "rectangle.inset.topright.filled", label: "Overlay position",
                detail: "Which corner the statistics overlay sits in.",
                options: SettingsOptions.hudPlacements, current: hudPlacement
            ) { hudPlacement = $0 },
            toggleRow(
                id: "library", icon: "square.grid.2x2", label: "Game library",
                detail: "Browse and launch the host's games with \(buttonName(\.buttonY, "Y")) "
                    + "(experimental).",
                value: $libraryEnabled),
            toggleRow(
                id: "gamepadUI", icon: "hand.tap", label: "Controller-optimized UI",
                detail: "Turn off to use the touch interface even with a controller connected.",
                value: $gamepadUIEnabled),
        ]
    }

    /// Resolution choices as "WxH" tags — the current size is inserted when it's a custom mode
    /// (set via the touch settings), so cycling starts from it instead of jumping.
    private var resolutionOptions: [(label: String, tag: String)] {
        var options = SettingsOptions.resolutionModes()
            .map { (label: "\($0.name) · \($0.w) × \($0.h)", tag: "\($0.w)x\($0.h)") }
        let current = "\(width)x\(height)"
        if !options.contains(where: { $0.tag == current }) {
            options.insert((label: "Custom · \(width) × \(height)", tag: current), at: 0)
        }
        return options
    }

    /// The active controller's user-facing name for a button (for detail strings).
    private func buttonName(
        _ button: KeyPath<GCExtendedGamepad, GCControllerButtonInput>, _ fallback: String
    ) -> String {
        gamepads.active?.controller.extendedGamepad?[keyPath: button].localizedName ?? fallback
    }

    // MARK: - Row builders

    private func choiceRow<T: Equatable>(
        id: String, header: String? = nil, icon: String, label: String, detail: String,
        options: [(label: String, tag: T)], current: T, write: @escaping (T) -> Void
    ) -> Row {
        let index = options.firstIndex { $0.tag == current }
        return Row(
            id: id, header: header, icon: icon, label: label,
            value: index.map { options[$0].label } ?? "—",
            detail: detail,
            adjust: { delta in
                // Unknown current value: snap to the first option on any step.
                guard let index else {
                    guard let first = options.first else { return false }
                    write(first.tag)
                    return true
                }
                let target = index + delta
                guard target >= 0, target < options.count else { return false }
                write(options[target].tag)
                return true
            },
            activate: {
                guard let index else { return write(options.first?.tag ?? current) }
                write(options[(index + 1) % options.count].tag)
            })
    }

    private func toggleRow(
        id: String, header: String? = nil, icon: String, label: String, detail: String,
        value: Binding<Bool>
    ) -> Row {
        Row(
            id: id, header: header, icon: icon, label: label,
            value: value.wrappedValue ? "On" : "Off",
            detail: detail,
            adjust: { delta in
                // Directional semantics: left = off, right = on; a no-op reads as a boundary.
                let target = delta > 0
                guard value.wrappedValue != target else { return false }
                value.wrappedValue = target
                return true
            },
            activate: { value.wrappedValue.toggle() })
    }
}

#endif
