import SwiftUI
import CoreLocation
#if os(iOS)
import UIKit
#else
import AppKit
#endif
struct LocationChannelsSheet: View {
    @Binding var isPresented: Bool
    @EnvironmentObject private var locationChannelsModel: LocationChannelsModel
    @EnvironmentObject private var peerListModel: PeerListModel
    @ThemedPalette private var palette
    @State private var customGeohash: String = ""
    @State private var customError: String? = nil

    private enum Strings {
        static let title: LocalizedStringKey = "location_channels.title"
        static let description: LocalizedStringKey = "location_channels.description"
        static let requestPermissions: LocalizedStringKey = "location_channels.action.request_permissions"
        static let permissionDenied: LocalizedStringKey = "location_channels.permission_denied"
        static let openSettings: LocalizedStringKey = "location_channels.action.open_settings"
        static let loadingNearby: LocalizedStringKey = "location_channels.loading_nearby"
        static let grantToFind: LocalizedStringKey = "location_channels.grant_to_find"
        static let teleport: LocalizedStringKey = "location_channels.action.teleport"
        static let bookmarked: LocalizedStringKey = "location_channels.bookmarked_section_title"

        static let invalidGeohash = String(localized: "location_channels.error.invalid_geohash", comment: "Error shown when a custom geohash is invalid")
        static let switchChannelHint = String(localized: "location_channels.accessibility.switch_hint", comment: "Accessibility hint on a channel row explaining activation switches to it")
        static let addBookmark = String(localized: "location_channels.accessibility.add_bookmark", comment: "Accessibility action name for bookmarking a channel")
        static let removeBookmark = String(localized: "location_channels.accessibility.remove_bookmark", comment: "Accessibility action name for removing a channel bookmark")

        static func meshTitle(_ count: Int) -> String {
            let label = String(localized: "location_channels.mesh_label", comment: "Label for the mesh channel row")
            return rowTitle(label: label, count: count)
        }

        static func levelTitle(for level: GeohashChannelLevel, count: Int) -> String {
            // High-precision uncertainty: if count is 0 for high-precision levels,
            // show "?" because presence broadcasting is disabled for privacy.
            let isHighPrecision = (level == .neighborhood || level == .block || level == .building)
            if isHighPrecision && count == 0 {
                return String(
                    format: String(localized: "location_channels.row_title_unknown", defaultValue: "%@ [? people]"),
                    locale: .current,
                    level.displayName
                )
            }
            return rowTitle(label: level.displayName, count: count)
        }

        static func bookmarkTitle(geohash: String, count: Int) -> String {
            // Check precision for bookmarks too
            let len = geohash.count
            // Neighborhood=6, Block=7, Building=8+
            let isHighPrecision = (len >= 6)
            if isHighPrecision && count == 0 {
                return String(
                    format: String(localized: "location_channels.row_title_unknown", defaultValue: "%@ [? people]"),
                    locale: .current,
                    "#\(geohash)"
                )
            }
            return rowTitle(label: "#\(geohash)", count: count)
        }

        static func subtitlePrefix(geohash: String, coverage: String) -> String {
            String(
                format: String(localized: "location_channels.subtitle_prefix", comment: "Subtitle prefix showing geohash and coverage"),
                locale: .current,
                geohash, coverage
            )
        }

        static func subtitle(prefix: String, name: String?) -> String {
            guard let name, !name.isEmpty else { return prefix }
            return String(
                format: String(localized: "location_channels.subtitle_with_name", comment: "Subtitle combining prefix and resolved location name"),
                locale: .current,
                prefix, name
            )
        }

        private static func rowTitle(label: String, count: Int) -> String {
            String(
                format: String(localized: "location_channels.row_title", comment: "List row title with participant count"),
                locale: .current,
                label, count
            )
        }
    }

    var body: some View {
        NavigationView {
            VStack(alignment: .leading, spacing: 12) {
                HStack(spacing: 12) {
                    Text(Strings.title)
                        .bitchatFont(size: 18)
                    Spacer()
                    closeButton
                }
                Text(Strings.description)
                    .bitchatFont(size: 12)
                    .foregroundColor(palette.secondary)

                Group {
                    switch locationChannelsModel.permissionState {
                    case .notDetermined:
                        Button(action: { locationChannelsModel.enableLocationChannels() }) {
                            Text(Strings.requestPermissions)
                                .bitchatFont(size: 12)
                                .foregroundColor(standardGreen)
                                .frame(maxWidth: .infinity)
                                .padding(.vertical, 6)
                                .background(standardGreen.opacity(0.12))
                                .cornerRadius(6)
                        }
                        .buttonStyle(.plain)
                    case .denied, .restricted:
                        VStack(alignment: .leading, spacing: 8) {
                            Text(Strings.permissionDenied)
                                .bitchatFont(size: 12)
                                .foregroundColor(palette.secondary)
                            Button(Strings.openSettings, action: SystemSettings.location.open)
                            .buttonStyle(.plain)
                        }
                    case .authorized:
                        EmptyView()
                    }
                }

                channelList
                Spacer()
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 12)
            .themedSurface()
            #if os(iOS)
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarHidden(true)
            #else
            .navigationTitle("")
            #endif
        }
        #if os(macOS)
        .frame(minWidth: 420, minHeight: 520)
        #endif
        .themedSheetBackground()
        .onAppear {
            // Refresh channels when opening
            if locationChannelsModel.permissionState == .authorized {
                locationChannelsModel.refreshChannels()
            }
            // Begin periodic refresh while sheet is open
            locationChannelsModel.beginLiveRefresh()
            // Geohash sampling is now managed by ChatViewModel globally
        }
        .onDisappear {
            locationChannelsModel.endLiveRefresh()
        }
        .onChange(of: locationChannelsModel.permissionState) { newValue in
            if newValue == .authorized {
                locationChannelsModel.refreshChannels()
            }
        }
        .onChange(of: locationChannelsModel.availableChannels) { _ in }
    }

    private var closeButton: some View {
        SheetCloseButton { isPresented = false }
    }

    private var channelList: some View {
        ScrollView {
            LazyVStack(spacing: 0) {
                channelRow(title: Strings.meshTitle(peerListModel.reachableMeshPeerCount), subtitlePrefix: Strings.subtitlePrefix(geohash: "bluetooth", coverage: bluetoothRangeString()), isSelected: isMeshSelected, titleColor: standardBlue, titleBold: peerListModel.reachableMeshPeerCount > 0) {
                    locationChannelsModel.select(ChannelID.mesh)
                    isPresented = false
                }
                .padding(.vertical, 6)

                let nearby = locationChannelsModel.availableChannels.filter { $0.level != .building }
                if !nearby.isEmpty {
                    ForEach(nearby) { channel in
                        sectionDivider
                        let coverage = coverageString(forPrecision: channel.geohash.count)
                        let nameBase = locationName(for: channel.level)
                        let namePart = nameBase.map { formattedNamePrefix(for: channel.level) + $0 }
                        let participantCount = peerListModel.participantCount(for: channel.geohash)
                        let subtitlePrefix = Strings.subtitlePrefix(geohash: channel.geohash, coverage: coverage)
                        let highlight = participantCount > 0
                        channelRow(
                            title: Strings.levelTitle(for: channel.level, count: participantCount),
                            subtitlePrefix: subtitlePrefix,
                            subtitleName: namePart,
                            isSelected: isSelected(channel),
                            titleBold: highlight,
                            trailingAccessory: {
                                Button(action: { locationChannelsModel.toggleBookmark(channel.geohash) }) {
                                    Image(systemName: locationChannelsModel.isBookmarked(channel.geohash) ? "bookmark.fill" : "bookmark")
                                        .font(.bitchatSystem(size: 14))
                                }
                                .buttonStyle(.plain)
                                .padding(.leading, 8)
                                .accessibilityLabel(locationChannelsModel.isBookmarked(channel.geohash) ? Strings.removeBookmark : Strings.addBookmark)
                            },
                            accessoryActionTitle: locationChannelsModel.isBookmarked(channel.geohash) ? Strings.removeBookmark : Strings.addBookmark,
                            accessoryAction: { locationChannelsModel.toggleBookmark(channel.geohash) }
                        ) {
                            locationChannelsModel.markTeleported(for: channel.geohash, false)
                            locationChannelsModel.select(ChannelID.location(channel))
                            isPresented = false
                        }
                        .padding(.vertical, 6)
                    }
                } else if locationChannelsModel.permissionState == .authorized {
                    sectionDivider
                    HStack(spacing: 8) {
                        ProgressView()
                        Text(Strings.loadingNearby)
                            .bitchatFont(size: 12)
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding(.vertical, 10)
                } else {
                    // No permission means no fix is coming: an honest hint
                    // beats a spinner that would never finish.
                    sectionDivider
                    Text(Strings.grantToFind)
                        .bitchatFont(size: 12)
                        .foregroundColor(palette.secondary)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.vertical, 10)
                }

                sectionDivider
                customTeleportSection
                    .padding(.vertical, 8)

                let bookmarkedList = locationChannelsModel.bookmarks
                if !bookmarkedList.isEmpty {
                    sectionDivider
                    bookmarkedSection(bookmarkedList)
                        .padding(.vertical, 8)
                }

            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding(.vertical, 6)
            .themedSurface()
        }
        .themedSurface()
    }

    private var sectionDivider: some View {
        Rectangle()
            .fill(dividerColor)
            .frame(height: 1)
    }

    private var dividerColor: Color { palette.divider }

    private var customTeleportSection: some View {
        VStack(alignment: .leading, spacing: 6) {
            HStack(spacing: 2) {
                Text(verbatim: "#")
                    .bitchatFont(size: 14)
                    .foregroundColor(palette.secondary)
                TextField("geohash", text: $customGeohash)
                    #if os(iOS)
                    .textInputAutocapitalization(.never)
                    .autocorrectionDisabled(true)
                    .keyboardType(.asciiCapable)
                    #endif
                    .bitchatFont(size: 14)
                    .onChange(of: customGeohash) { newValue in
                        let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
                        let filtered = newValue
                            .lowercased()
                            .replacingOccurrences(of: "#", with: "")
                            .filter { allowed.contains($0) }
                        if filtered.count > 12 {
                            customGeohash = String(filtered.prefix(12))
                        } else if filtered != newValue {
                            customGeohash = filtered
                        }
                    }
                let normalized = customGeohash
                    .trimmed
                    .lowercased()
                    .replacingOccurrences(of: "#", with: "")
                let isValid = validateGeohash(normalized)
                Button(action: {
                    let gh = normalized
                    guard isValid else { customError = Strings.invalidGeohash; return }
                    locationChannelsModel.teleport(to: gh)
                    isPresented = false
                }) {
                    HStack(spacing: 6) {
                        Text(Strings.teleport)
                            .bitchatFont(size: 14)
                        Image(systemName: "face.dashed")
                            .font(.bitchatSystem(size: 14))
                    }
                }
                .buttonStyle(.plain)
                .bitchatFont(size: 14)
                .padding(.vertical, 6)
                .padding(.horizontal, 10)
                .background(palette.secondary.opacity(0.12))
                .cornerRadius(6)
                .opacity(isValid ? 1.0 : 0.4)
                .disabled(!isValid)
            }
            if let err = customError {
                Text(err)
                    .bitchatFont(size: 12)
                    .foregroundColor(.red)
            }
        }
    }

    private func bookmarkedSection(_ entries: [String]) -> some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(Strings.bookmarked)
                .bitchatFont(size: 12)
                .foregroundColor(palette.secondary)
            LazyVStack(spacing: 0) {
                ForEach(Array(entries.enumerated()), id: \.offset) { index, gh in
                    let level = levelForLength(gh.count)
                    let channel = GeohashChannel(level: level, geohash: gh)
                    let coverage = coverageString(forPrecision: gh.count)
                    let subtitle = Strings.subtitlePrefix(geohash: gh, coverage: coverage)
                    let name = locationChannelsModel.bookmarkNames[gh]
                    let participantCount = peerListModel.participantCount(for: gh)
                    channelRow(
                        title: Strings.bookmarkTitle(geohash: gh, count: participantCount),
                        subtitlePrefix: subtitle,
                        subtitleName: name.map { formattedNamePrefix(for: level) + $0 },
                        isSelected: locationChannelsModel.isSelected(channel),
                        trailingAccessory: {
                            Button(action: { locationChannelsModel.toggleBookmark(gh) }) {
                                Image(systemName: locationChannelsModel.isBookmarked(gh) ? "bookmark.fill" : "bookmark")
                                    .font(.bitchatSystem(size: 14))
                            }
                            .buttonStyle(.plain)
                            .padding(.leading, 8)
                            .accessibilityLabel(locationChannelsModel.isBookmarked(gh) ? Strings.removeBookmark : Strings.addBookmark)
                        },
                        accessoryActionTitle: locationChannelsModel.isBookmarked(gh) ? Strings.removeBookmark : Strings.addBookmark,
                        accessoryAction: { locationChannelsModel.toggleBookmark(gh) }
                    ) {
                        let inRegional = locationChannelsModel.availableChannels.contains { $0.geohash == gh }
                        if !inRegional && !locationChannelsModel.availableChannels.isEmpty {
                            locationChannelsModel.markTeleported(for: gh, true)
                        } else {
                            locationChannelsModel.markTeleported(for: gh, false)
                        }
                        locationChannelsModel.select(ChannelID.location(channel))
                        isPresented = false
                    }
                    .padding(.vertical, 6)
                    .onAppear { locationChannelsModel.resolveBookmarkNameIfNeeded(for: gh) }

                    if index < entries.count - 1 {
                        sectionDivider
                    }
                }
            }
        }
    }


    private func isSelected(_ channel: GeohashChannel) -> Bool {
        locationChannelsModel.isSelected(channel)
    }

    private var isMeshSelected: Bool {
        if case .mesh = locationChannelsModel.selectedChannel { return true }
        return false
    }

    @ViewBuilder
    private func channelRow(
        title: String,
        subtitlePrefix: String,
        subtitleName: String? = nil,
        subtitleNameBold _: Bool = false,
        isSelected: Bool,
        titleColor: Color? = nil,
        titleBold: Bool = false,
        @ViewBuilder trailingAccessory: () -> some View = { EmptyView() },
        accessoryActionTitle: String? = nil,
        accessoryAction: (() -> Void)? = nil,
        action: @escaping () -> Void
    ) -> some View {
        HStack(alignment: .center, spacing: 8) {
            VStack(alignment: .leading) {
                // Render title with smaller font for trailing count in parentheses
                let parts = splitTitleAndCount(title)
                HStack(spacing: 4) {
                    Text(parts.base)
                            .bitchatFont(size: 14)
                            .fontWeight(titleBold ? .bold : .regular)
                            .foregroundColor(titleColor ?? palette.primary)
                        if let count = parts.countSuffix, !count.isEmpty {
                            Text(count)
                                .bitchatFont(size: 11)
                                .foregroundColor(palette.secondary)
                        }
                    }
                let subtitleFull = Strings.subtitle(prefix: subtitlePrefix, name: subtitleName)
                Text(subtitleFull)
                    .bitchatFont(size: 12)
                    .foregroundColor(palette.secondary)
                    .lineLimit(1)
                    .truncationMode(.tail)
                }
                Spacer()
                if isSelected {
                    Text(verbatim: "✔︎")
                        .bitchatFont(size: 16)
                        .foregroundColor(standardGreen)
                }
                trailingAccessory()
            }
            .frame(maxWidth: .infinity, alignment: .leading)
        .contentShape(Rectangle())
        .onTapGesture(perform: action)
        // The row is a plain HStack with a tap gesture, which VoiceOver reads
        // as disconnected static text. Expose it as one activatable button;
        // the visible bookmark accessory is mirrored as a named action.
        .accessibilityElement(children: .ignore)
        .accessibilityLabel(Text(verbatim: "\(title), \(Strings.subtitle(prefix: subtitlePrefix, name: subtitleName))"))
        .accessibilityAddTraits(isSelected ? [.isButton, .isSelected] : [.isButton])
        .accessibilityHint(Strings.switchChannelHint)
        .accessibilityAction(.default, action)
        .accessibilityActions {
            if let accessoryActionTitle, let accessoryAction {
                Button(accessoryActionTitle, action: accessoryAction)
            }
        }
    }

    // Split a title like "#mesh [3 people]" into base and suffix "[3 people]"
    private func splitTitleAndCount(_ s: String) -> (base: String, countSuffix: String?) {
        guard let idx = s.lastIndex(of: "[") else { return (s, nil) }
        let prefix = String(s[..<idx]).trimmed
        let suffix = String(s[idx...])
        return (prefix, suffix)
    }
    private func validateGeohash(_ s: String) -> Bool {
        let allowed = Set("0123456789bcdefghjkmnpqrstuvwxyz")
        guard !s.isEmpty, s.count <= 12 else { return false }
        return s.allSatisfy { allowed.contains($0) }
    }

    private func levelForLength(_ len: Int) -> GeohashChannelLevel {
        switch len {
        case 0...2: return .region
        case 3...4: return .province
        case 5: return .city
        case 6: return .neighborhood
        case 7: return .block
        case 8: return .building
        default: return .block
        }
    }
}

// MARK: - Standardized Colors
// (The tor and internet-gateway toggles moved to AppInfoView's Settings pane;
// IRCToggleStyle now lives in Views/Components.)
extension LocationChannelsSheet {
    private var standardGreen: Color { palette.primary }
    private var standardBlue: Color { palette.accentBlue }
}

// MARK: - Coverage helpers
extension LocationChannelsSheet {
    private func coverageString(forPrecision len: Int) -> String {
        // Approximate max cell dimension at equator for a given geohash length.
        // Values sourced from common geohash dimension tables.
        let maxMeters: Double = {
            switch len {
            case 2: return 1_250_000
            case 3: return 156_000
            case 4: return 39_100
            case 5: return 4_890
            case 6: return 1_220
            case 7: return 153
            case 8: return 38.2
            case 9: return 4.77
            case 10: return 1.19
            default:
                if len <= 1 { return 5_000_000 }
                // For >10, scale down conservatively by ~1/4 each char
                let over = len - 10
                return 1.19 * pow(0.25, Double(over))
            }
        }()

        let usesMetric: Bool = {
            if #available(iOS 16.0, macOS 13.0, *) {
                return Locale.current.measurementSystem == .metric
            } else {
                return Locale.current.usesMetricSystem
            }
        }()
        if usesMetric {
            let km = maxMeters / 1000.0
            return "~\(formatDistance(km)) km"
        } else {
            let miles = maxMeters / 1609.344
            return "~\(formatDistance(miles)) mi"
        }
    }

    private func formatDistance(_ value: Double) -> String {
        if value >= 100 { return String(format: "%.0f", value.rounded()) }
        if value >= 10 { return String(format: "%.1f", value) }
        return String(format: "%.1f", value)
    }

    private func bluetoothRangeString() -> String {
        let usesMetric: Bool = {
            if #available(iOS 16.0, macOS 13.0, *) {
                return Locale.current.measurementSystem == .metric
            } else {
                return Locale.current.usesMetricSystem
            }
        }()
        // Approximate Bluetooth LE range for typical mobile devices; environment dependent
        return usesMetric ? "~10–50 m" : "~30–160 ft"
    }

    private func locationName(for level: GeohashChannelLevel) -> String? {
        locationChannelsModel.locationName(for: level)
    }

    private func formattedNamePrefix(for level: GeohashChannelLevel) -> String {
        switch level {
        case .region:
            return ""
        case .building, .block, .neighborhood, .city, .province:
            return "~"
        }
    }
}
