// The gamepad-driven "Add Host" screen (iOS/iPadOS/macOS/tvOS) — the controller counterpart of
// AddHostSheet, reached from the launcher's Add Host tile. Three field rows (name / address /
// port) plus the Add action, navigated with the same vertical focus list as the gamepad settings;
// A on a field opens GamepadKeyboard in a bottom tray, so a host can be registered end to end
// without touching the screen. Field edits are live (the row shows every keystroke); B closes the
// keyboard first, then cancels the screen — the same "back peels one layer" rule as a console UI.
// tvOS swaps the custom keyboard tray for the SYSTEM fullscreen keyboard (TVTextEntry): unlike
// iOS/macOS, tvOS HAS a first-class controller/remote-drivable text entry, so the native one wins.

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

struct GamepadAddHostView: View {
    @Environment(\.dismiss) private var dismiss
    let onAdd: (StoredHost) -> Void

    #if os(iOS)
    /// `.compact` in a landscape phone window — tighter chrome so the keyboard tray still fits.
    @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 to fit the tray
    #endif
    @State private var name = ""
    @State private var address = ""
    @State private var port = "9777"
    @State private var focusID: String?
    /// The field row the keyboard tray is editing; nil ⇒ the row list owns the controller.
    @State private var editing: String?

    var body: some View {
        GamepadMenuList(
            items: rows,
            focusID: $focusID,
            onActivate: { activate(id: $0.id) },
            onBack: { dismiss() },
            isActive: editing == nil
        ) { row, focused in
            rowView(row, focused: focused)
                .frame(maxWidth: GamepadFormMetrics.rowMaxWidth)
                .padding(.horizontal, 24)
        }
        .frame(maxWidth: .infinity)
        .safeAreaInset(edge: .top, spacing: 0) {
            VStack(spacing: 4) {
                Text("Add Host")
                    .font(.geist(gamepadTitleSize(compact: compact), .bold, relativeTo: .title))
                    .foregroundStyle(.white)
                if !compact {
                    Text("Hosts on this network appear automatically — add one by address "
                        + "for everything else.")
                        .font(.geist(GamepadFormMetrics.detailFont, relativeTo: .caption))
                        .foregroundStyle(.white.opacity(0.55))
                        .multilineTextAlignment(.center)
                        .frame(maxWidth: GamepadFormMetrics.rowMaxWidth * 0.72)
                }
            }
            .padding(.top, gamepadTitleTopPadding(compact: compact))
            .padding(.bottom, compact ? 4 : 8)
            .frame(maxWidth: .infinity)
            .overlay(alignment: .topTrailing) { closeButton.padding(.top, 20).padding(.trailing, 20) }
            .background { GamepadTrayScrim(edge: .top) }
        }
        .safeAreaInset(edge: .bottom, spacing: 0) {
            bottomTray
                // Equal distance from the left and bottom edges for the legend pill (see GamepadHomeView).
                .padding(.horizontal, compact ? 12 : 18)
                .padding(.bottom, compact ? 12 : 18)
                .padding(.top, compact ? 6 : 10)
                .background { GamepadTrayScrim(edge: .bottom) }
        }
        // No aurora — the same clean Liquid-Glass-over-dark base as the gamepad settings screen.
        .background { GamepadFormBackground() }
        // A port can't exceed 5 digits — cap while typing so the row can't grow absurd.
        .onChange(of: port) { _, value in
            if value.count > 5 { port = String(value.prefix(5)) }
        }
        #if os(tvOS)
        // tvOS types with the SYSTEM fullscreen keyboard (TVTextEntry) instead of the custom
        // tray — the remote and the pad both drive it natively. Same `editing` state as the
        // tray, just a different presentation; done (or Menu, edits-stick) commits and returns.
        .fullScreenCover(isPresented: Binding(
            get: { editing != nil },
            set: { if !$0 { editing = nil } })
        ) {
            if let field = editing {
                TVTextEntry(
                    title: fieldTitle(field),
                    text: editingBinding(field).wrappedValue,
                    keyboardType: keyboardType(field)
                ) { value in
                    commitEntry(field, value)
                    editing = nil
                }
            }
        }
        #endif
    }

    /// The keyboard tray while editing, the controls legend otherwise. (tvOS never shows the
    /// tray — `editing` presents the system keyboard cover instead — so it's legend-only there.)
    @ViewBuilder private var bottomTray: some View {
        #if os(tvOS)
        GamepadHintBar(hints: [
            .init(glyph: buttonGlyph(\.buttonA, fallback: "a.circle"), text: "Select"),
            .init(glyph: buttonGlyph(\.buttonB, fallback: "b.circle"), text: "Cancel"),
        ])
        .frame(maxWidth: .infinity, alignment: .leading)
        #else
        if let editing {
            VStack(spacing: 10) {
                GamepadKeyboard(
                    text: editingBinding(editing),
                    allowed: allowedCharacters(editing),
                    onDone: { closeKeyboard() })
                    // Fresh keyboard per field: a touch user can retarget the tray by tapping
                    // another field row, and the keyboard's input wiring captured the previous
                    // binding on appear — new identity forces a rewire to the new field.
                    .id(editing)
                GamepadHintBar(hints: [
                    .init(glyph: buttonGlyph(\.buttonA, fallback: "a.circle"), text: "Type"),
                    .init(glyph: buttonGlyph(\.buttonX, fallback: "x.circle"), text: "Delete"),
                    .init(glyph: buttonGlyph(\.buttonB, fallback: "b.circle"), text: "Done"),
                ])
                .frame(maxWidth: .infinity, alignment: .leading)
            }
            .transition(.move(edge: .bottom).combined(with: .opacity))
        } else {
            GamepadHintBar(hints: [
                .init(glyph: buttonGlyph(\.buttonA, fallback: "a.circle"), text: "Select"),
                .init(glyph: buttonGlyph(\.buttonB, fallback: "b.circle"), text: "Cancel"),
            ])
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        #endif
    }

    /// 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: GamepadFormMetrics.closeFont, weight: .semibold))
                .foregroundStyle(.white)
                .frame(width: GamepadFormMetrics.closeSide, height: GamepadFormMetrics.closeSide)
                .glassBackground(Circle(), interactive: true)
                .contentShape(Circle())
        }
        .buttonStyle(.plain)
        #if !os(tvOS)
        .keyboardShortcut(.cancelAction) // unavailable on tvOS (Menu is the cancel there)
        #endif
        .accessibilityLabel("Cancel")
    }

    // MARK: - Rows

    private struct Row: Identifiable {
        let id: String
        let label: String
        var value = ""
        var placeholder = ""
        var isAction = false
    }

    private var rows: [Row] {
        [
            Row(id: "name", label: "Name", value: name, placeholder: "Optional — e.g. Living Room"),
            Row(id: "address", label: "Address", value: address, placeholder: "IP or hostname"),
            Row(id: "port", label: "Port", value: port, placeholder: "9777"),
            Row(id: "add", label: "Add Host", isAction: true),
        ]
    }

    private func rowView(_ row: Row, focused: Bool) -> some View {
        let m = GamepadFormMetrics.self
        return HStack(spacing: 14) {
            if row.isAction {
                Label("Add Host", systemImage: "plus.circle.fill")
                    .font(.geist(m.labelFont, .semibold, relativeTo: .body))
                    .foregroundStyle(canAdd ? Color.brand : .white.opacity(0.35))
                    .frame(maxWidth: .infinity)
            } else {
                Text(row.label)
                    .font(.geist(m.labelFont, .semibold, relativeTo: .body))
                    .foregroundStyle(.white)
                Spacer(minLength: 12)
                Text(row.value.isEmpty ? row.placeholder : row.value)
                    .font(.geistFixed(m.valueFont, .medium))
                    .foregroundStyle(row.value.isEmpty ? .white.opacity(0.35) : .white)
                    .lineLimit(1)
                    .truncationMode(.head) // keep the end of a long address visible while typing
                if editing == row.id {
                    // The live-edit caret: this row is what the keyboard tray is typing into.
                    Rectangle()
                        .fill(Color.brand)
                        .frame(width: 2, height: m.labelFont + 2)
                }
            }
        }
        .padding(.horizontal, m.rowHPad)
        .padding(.vertical, m.rowVPad)
        // Liquid Glass rows, matching the settings screen; the focused (or actively edited) row
        // takes the brand wash, and the edited row keeps its brand caret border.
        .consoleGlass(
            RoundedRectangle(cornerRadius: m.rowCorner, style: .continuous),
            tint: (focused || editing == row.id) ? Color.brand.opacity(0.30) : nil,
            interactive: focused)
        .overlay {
            RoundedRectangle(cornerRadius: m.rowCorner, style: .continuous)
                .strokeBorder(
                    editing == row.id ? Color.brand.opacity(0.7) : .white.opacity(focused ? 0.28 : 0.06),
                    lineWidth: 1)
        }
        .scaleEffect(focused ? 1.0 : 0.98)
        .animation(.smooth(duration: 0.18), value: focused)
    }

    // MARK: - Actions

    private func activate(id: String) {
        switch id {
        case "add":
            guard canAdd else {
                // Not addable yet — jump straight to what's missing instead of a dead press.
                focusID = "address"
                openKeyboard("address")
                return
            }
            onAdd(StoredHost(
                name: name.trimmingCharacters(in: .whitespaces),
                address: address.trimmingCharacters(in: .whitespaces),
                port: UInt16(port) ?? 9777))
            dismiss()
        default:
            openKeyboard(id)
        }
    }

    private var canAdd: Bool {
        !address.trimmingCharacters(in: .whitespaces).isEmpty
            && UInt16(port).map { $0 > 0 } == true
    }

    private func openKeyboard(_ id: String) {
        withAnimation(.spring(response: 0.32, dampingFraction: 0.86)) { editing = id }
    }

    private func closeKeyboard() {
        withAnimation(.spring(response: 0.32, dampingFraction: 0.86)) { editing = nil }
    }

    private func editingBinding(_ id: String) -> Binding<String> {
        switch id {
        case "name": return $name
        case "port": return $port
        default: return $address
        }
    }

    /// What the keyboard may type per field: a port is digits, an address never contains spaces;
    /// a name is free-form.
    private func allowedCharacters(_ id: String) -> CharacterSet? {
        switch id {
        case "port": return CharacterSet(charactersIn: "0123456789")
        case "address": return CharacterSet(charactersIn: " ").inverted
        default: return nil
        }
    }

    #if os(tvOS)
    // MARK: - System keyboard plumbing (see the fullScreenCover on `body`)

    private func fieldTitle(_ id: String) -> String {
        switch id {
        case "name": return "Name (optional)"
        case "port": return "Port"
        default: return "Address (IP or hostname)"
        }
    }

    /// .URL for the address (dots on the primary layer, no autocapitalize) — the closest tvOS
    /// keyboard to "hostname or IP".
    private func keyboardType(_ id: String) -> UIKeyboardType {
        switch id {
        case "port": return .numberPad
        case "address": return .URL
        default: return .default
        }
    }

    /// Apply a system-keyboard result, enforcing what `allowedCharacters` enforces per keystroke
    /// on the other platforms (the system keyboard will type anything).
    private func commitEntry(_ id: String, _ value: String) {
        switch id {
        case "port":
            editingBinding(id).wrappedValue = String(value.filter(\.isNumber).prefix(5))
        case "address":
            editingBinding(id).wrappedValue = value
                .replacingOccurrences(of: " ", with: "")
        default:
            editingBinding(id).wrappedValue = value
        }
    }
    #endif
}
#endif
