//
//  MeterView.swift
//  USB Meter
//
//  Created by Bogdan Timofte on 04/03/2020.
//  Copyright © 2020 Bogdan Timofte. All rights reserved.
//
// MARK: Parent frame https://stackoverflow.com/questions/56832865/how-to-access-parents-frame-in-swiftui

import SwiftUI
import CoreBluetooth

struct MeterView: View {
    private struct TabBarStyle {
        let horizontalPadding: CGFloat
        let topPadding: CGFloat
        let bottomPadding: CGFloat
        let chipHorizontalPadding: CGFloat
        let chipVerticalPadding: CGFloat
        let outerPadding: CGFloat
        let barBackgroundOpacity: CGFloat
        let materialOpacity: CGFloat
        let shadowOpacity: CGFloat
        let floatingInset: CGFloat

        static let portrait = TabBarStyle(
            horizontalPadding: 16,
            topPadding: 10,
            bottomPadding: 8,
            chipHorizontalPadding: 10,
            chipVerticalPadding: 7,
            outerPadding: 6,
            barBackgroundOpacity: 0.10,
            materialOpacity: 0.78,
            shadowOpacity: 0,
            floatingInset: 0
        )

        static let portraitCompact = TabBarStyle(
            horizontalPadding: 16,
            topPadding: 10,
            bottomPadding: 8,
            chipHorizontalPadding: 12,
            chipVerticalPadding: 10,
            outerPadding: 6,
            barBackgroundOpacity: 0.14,
            materialOpacity: 0.90,
            shadowOpacity: 0,
            floatingInset: 0
        )

        static let landscapeInline = TabBarStyle(
            horizontalPadding: 12,
            topPadding: 10,
            bottomPadding: 8,
            chipHorizontalPadding: 10,
            chipVerticalPadding: 7,
            outerPadding: 6,
            barBackgroundOpacity: 0.10,
            materialOpacity: 0.78,
            shadowOpacity: 0,
            floatingInset: 0
        )

        static let landscapeFloating = TabBarStyle(
            horizontalPadding: 16,
            topPadding: 10,
            bottomPadding: 0,
            chipHorizontalPadding: 11,
            chipVerticalPadding: 11,
            outerPadding: 7,
            barBackgroundOpacity: 0.16,
            materialOpacity: 0.88,
            shadowOpacity: 0.12,
            floatingInset: 12
        )
    }

    private enum MeterTab: String, Hashable {
        case home
        case live
        case chart
        case chargeRecord
        case dataGroups
        case settings

        var systemImage: String {
            switch self {
            case .home: return "house.fill"
            case .live: return "waveform.path.ecg"
            case .chart: return "chart.xyaxis.line"
            case .chargeRecord: return "gauge.with.dots.needle.50percent"
            case .dataGroups: return "square.grid.2x2.fill"
            case .settings: return "gearshape.fill"
            }
        }
    }
    
    @EnvironmentObject private var meter: Meter
    @Environment(\.dismiss) private var dismiss

    private static let isMacIPadApp: Bool = ProcessInfo.processInfo.isiOSAppOnMac
    #if os(iOS)
    private static let isPhone: Bool = UIDevice.current.userInterfaceIdiom == .phone
    #else
    private static let isPhone: Bool = false
    #endif
    
    @State private var selectedMeterTab: MeterTab = .home
    @State private var navBarTitle: String = "Meter"
    @State private var navBarShowRSSI: Bool = false
    @State private var navBarRSSI: Int = 0
    @State private var landscapeTabBarHeight: CGFloat = 0

    var body: some View {
        GeometryReader { proxy in
            let landscape = isLandscape(size: proxy.size)
            let usesOverlayTabBar = landscape && Self.isPhone
            let tabBarStyle = tabBarStyle(
                for: landscape,
                usesOverlayTabBar: usesOverlayTabBar,
                size: proxy.size
            )
            let tabBarPresentation = tabBarPresentation(
                for: proxy.size,
                usesOverlayTabBar: usesOverlayTabBar
            )

            VStack(spacing: 0) {
                if Self.isMacIPadApp {
                    macNavigationHeader
                }
                Group {
                    if landscape {
                        landscapeDeck(
                            size: proxy.size,
                            usesOverlayTabBar: usesOverlayTabBar,
                            tabBarStyle: tabBarStyle,
                            tabBarPresentation: tabBarPresentation
                        )
                    } else {
                        portraitContent(
                            size: proxy.size,
                            tabBarStyle: tabBarStyle,
                            tabBarPresentation: tabBarPresentation
                        )
                    }
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
            }
            #if !targetEnvironment(macCatalyst)
            .navigationBarHidden(Self.isMacIPadApp || landscape)
            #endif
        }
        .background(meterBackground)
        .modifier(IOSOnlyNavBar(
            apply: !Self.isMacIPadApp,
            title: navBarTitle,
            showRSSI: navBarShowRSSI,
            rssi: navBarRSSI,
            meter: meter
        ))
        .onAppear {
            navBarTitle = meter.name.isEmpty ? "Meter" : meter.name
            navBarShowRSSI = meter.operationalState > .notPresent
            navBarRSSI = meter.btSerial.averageRSSI
        }
        .onChange(of: meter.name) { name in
            navBarTitle = name.isEmpty ? "Meter" : name
        }
        .onChange(of: meter.operationalState) { state in
            navBarShowRSSI = state > .notPresent
        }
        .onChange(of: meter.btSerial.averageRSSI) { newRSSI in
            if abs(newRSSI - navBarRSSI) >= 5 {
                navBarRSSI = newRSSI
            }
        }
        .onChange(of: selectedMeterTab) { newTab in
            meter.preferredTabIdentifier = newTab.rawValue
        }
    }

    // MARK: - Custom navigation header for Designed-for-iPad on Mac

    private var macNavigationHeader: some View {
        HStack(spacing: 12) {
            Button {
                dismiss()
            } label: {
                HStack(spacing: 4) {
                    Image(systemName: "chevron.left")
                        .font(.body.weight(.semibold))
                    Text("USB Meters")
                }
                .foregroundColor(.accentColor)
            }
            .buttonStyle(.plain)

            Text(meter.name.isEmpty ? "Meter" : meter.name)
                .font(.headline)
                .lineLimit(1)

            Spacer()

            MeterConnectionToolbarButton(
                operationalState: meter.operationalState,
                showsTitle: true,
                connectAction: { meter.connect() },
                disconnectAction: { meter.disconnect() }
            )

            if meter.operationalState > .notPresent {
                RSSIView(RSSI: meter.btSerial.averageRSSI)
                    .frame(width: 18, height: 18)
            }

        }
        .padding(.horizontal, 16)
        .padding(.vertical, 10)
        .background(
            Rectangle()
                .fill(.ultraThinMaterial)
                .ignoresSafeArea(edges: .top)
        )
        .overlay(alignment: .bottom) {
            Rectangle()
                .fill(Color.secondary.opacity(0.12))
                .frame(height: 1)
        }
    }

    private func portraitContent(
        size: CGSize,
        tabBarStyle: TabBarStyle,
        tabBarPresentation: AdaptiveTabBarPresentation
    ) -> some View {
        portraitSegmentedDeck(
            size: size,
            tabBarStyle: tabBarStyle,
            tabBarPresentation: tabBarPresentation
        )
    }

    @ViewBuilder
    private func landscapeDeck(
        size: CGSize,
        usesOverlayTabBar: Bool,
        tabBarStyle: TabBarStyle,
        tabBarPresentation: AdaptiveTabBarPresentation
    ) -> some View {
        if usesOverlayTabBar {
            landscapeOverlaySegmentedDeck(
                size: size,
                tabBarStyle: tabBarStyle,
                tabBarPresentation: tabBarPresentation
            )
        } else {
            landscapeSegmentedDeck(
                size: size,
                tabBarStyle: tabBarStyle,
                tabBarPresentation: tabBarPresentation
            )
        }
    }

    private func landscapeOverlaySegmentedDeck(
        size: CGSize,
        tabBarStyle: TabBarStyle,
        tabBarPresentation: AdaptiveTabBarPresentation
    ) -> some View {
        ZStack(alignment: .top) {
            landscapeSegmentedContent(size: size)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                .padding(.top, landscapeContentTopPadding(for: tabBarStyle))
                .id(displayedMeterTab)
                .transition(.opacity.combined(with: .move(edge: .trailing)))

            segmentedTabBar(
                style: tabBarStyle,
                presentation: tabBarPresentation,
                showsConnectionAction: !Self.isMacIPadApp
            )
        }
        .animation(.easeInOut(duration: 0.22), value: displayedMeterTab)
        .animation(.easeInOut(duration: 0.22), value: availableMeterTabs)
        .onAppear {
            restoreSelectedTab()
        }
        .onPreferenceChange(MeterTabBarHeightPreferenceKey.self) { height in
            if height > 0 {
                landscapeTabBarHeight = height
            }
        }
    }

    private func landscapeSegmentedDeck(
        size: CGSize,
        tabBarStyle: TabBarStyle,
        tabBarPresentation: AdaptiveTabBarPresentation
    ) -> some View {
        VStack(spacing: 0) {
            segmentedTabBar(
                style: tabBarStyle,
                presentation: tabBarPresentation,
                showsConnectionAction: !Self.isMacIPadApp
            )

            landscapeSegmentedContent(size: size)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                .id(displayedMeterTab)
                .transition(.opacity.combined(with: .move(edge: .trailing)))
        }
        .animation(.easeInOut(duration: 0.22), value: displayedMeterTab)
        .animation(.easeInOut(duration: 0.22), value: availableMeterTabs)
        .onAppear {
            restoreSelectedTab()
        }
    }

    private func portraitSegmentedDeck(
        size: CGSize,
        tabBarStyle: TabBarStyle,
        tabBarPresentation: AdaptiveTabBarPresentation
    ) -> some View {
        VStack(spacing: 0) {
            segmentedTabBar(
                style: tabBarStyle,
                presentation: tabBarPresentation
            )

            portraitSegmentedContent(size: size)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                .id(displayedMeterTab)
                .transition(.opacity.combined(with: .move(edge: .trailing)))
        }
        .animation(.easeInOut(duration: 0.22), value: displayedMeterTab)
        .animation(.easeInOut(duration: 0.22), value: availableMeterTabs)
        .onAppear {
            restoreSelectedTab()
        }
    }

    private func segmentedTabBar(
        style: TabBarStyle,
        presentation: AdaptiveTabBarPresentation,
        showsConnectionAction: Bool = false
    ) -> some View {
        let isFloating = style.floatingInset > 0
        let cornerRadius = presentation.showsTitles ? 14.0 : 22.0
        let unselectedForegroundColor = isFloating ? Color.primary.opacity(0.86) : Color.primary
        let unselectedChipFill = isFloating ? Color.black.opacity(0.08) : Color.secondary.opacity(0.12)

        return HStack {
            Spacer(minLength: 0)

            HStack(spacing: 8) {
                ForEach(availableMeterTabs, id: \.self) { tab in
                    let isSelected = displayedMeterTab == tab

                    Button {
                        withAnimation(.easeInOut(duration: 0.2)) {
                            selectedMeterTab = tab
                        }
                    } label: {
                        HStack(spacing: 6) {
                            Image(systemName: tab.systemImage)
                                .font(.subheadline.weight(.semibold))
                            if presentation.showsTitles {
                                Text(title(for: tab))
                                    .font(.subheadline.weight(.semibold))
                                    .lineLimit(1)
                            }
                        }
                        .foregroundColor(
                            isSelected
                            ? .white
                            : unselectedForegroundColor
                        )
                        .padding(.horizontal, style.chipHorizontalPadding)
                        .padding(.vertical, style.chipVerticalPadding)
                        .frame(maxWidth: .infinity)
                        .background(
                            Capsule()
                                .fill(
                                    isSelected
                                    ? meter.color.opacity(isFloating ? 0.94 : 1)
                                    : unselectedChipFill
                                )
                        )
                    }
                    .buttonStyle(.plain)
                    .accessibilityLabel(title(for: tab))
                }
            }
            .frame(maxWidth: presentation.maxWidth)
            .padding(style.outerPadding)
            .background(
                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                    .fill(
                        isFloating
                        ? LinearGradient(
                            colors: [
                                Color.white.opacity(0.76),
                                Color.white.opacity(0.52)
                            ],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        )
                        : LinearGradient(
                            colors: [
                                Color.secondary.opacity(style.barBackgroundOpacity),
                                Color.secondary.opacity(style.barBackgroundOpacity)
                            ],
                            startPoint: .topLeading,
                            endPoint: .bottomTrailing
                        )
                    )
            )
            .overlay {
                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                    .stroke(
                        isFloating ? Color.black.opacity(0.08) : Color.clear,
                        lineWidth: 1
                    )
            }
            .background {
                RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
                    .fill(.ultraThinMaterial)
                    .opacity(style.materialOpacity)
            }
            .shadow(color: Color.black.opacity(style.shadowOpacity), radius: isFloating ? 28 : 24, x: 0, y: isFloating ? 16 : 12)

            Spacer(minLength: 0)
        }
        .padding(.horizontal, style.horizontalPadding)
        .padding(.top, style.topPadding)
        .padding(.bottom, style.bottomPadding)
        .background(
            GeometryReader { geometry in
                Color.clear
                    .preference(key: MeterTabBarHeightPreferenceKey.self, value: geometry.size.height)
            }
        )
        .padding(.horizontal, style.floatingInset)
        .background {
            if style.floatingInset == 0 {
                Rectangle()
                    .fill(.ultraThinMaterial)
                    .opacity(style.materialOpacity)
                    .ignoresSafeArea(edges: .top)
            }
        }
        .overlay(alignment: .bottom) {
            if style.floatingInset == 0 {
                Rectangle()
                    .fill(Color.secondary.opacity(0.12))
                    .frame(height: 1)
            }
        }
        .overlay(alignment: .trailing) {
            if showsConnectionAction {
                MeterConnectionToolbarButton(
                    operationalState: meter.operationalState,
                    showsTitle: false,
                    connectAction: { meter.connect() },
                    disconnectAction: { meter.disconnect() }
                )
                .font(.title3.weight(.semibold))
                .padding(.trailing, style.horizontalPadding + style.floatingInset + 4)
                .padding(.top, style.topPadding)
                .padding(.bottom, style.bottomPadding)
            }
        }
    }

    @ViewBuilder
    private func landscapeSegmentedContent(size: CGSize) -> some View {
        switch displayedMeterTab {
        case .home:
            MeterHomeTabView(
                size: size,
                isLandscape: true,
                showChargeRecordTab: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        selectedMeterTab = .chargeRecord
                    }
                },
                showDataGroupsTab: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        selectedMeterTab = .dataGroups
                    }
                }
            )
        case .live:
            MeterLiveTabView(size: size, isLandscape: true)
        case .chart:
            MeterChartTabView(size: size, isLandscape: true)
        case .chargeRecord:
            MeterChargeRecordTabView().equatable()
        case .dataGroups:
            MeterDataGroupsTabView()
        case .settings:
            MeterSettingsTabView(isMacIPadApp: Self.isMacIPadApp) {
                withAnimation(.easeInOut(duration: 0.22)) {
                    selectedMeterTab = .home
                }
            }
        }
    }

    @ViewBuilder
    private func portraitSegmentedContent(size: CGSize) -> some View {
        switch displayedMeterTab {
        case .home:
            MeterHomeTabView(
                size: size,
                isLandscape: false,
                showChargeRecordTab: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        selectedMeterTab = .chargeRecord
                    }
                },
                showDataGroupsTab: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        selectedMeterTab = .dataGroups
                    }
                }
            )
        case .live:
            MeterLiveTabView(size: size, isLandscape: false)
        case .chart:
            MeterChartTabView(size: size, isLandscape: false)
        case .chargeRecord:
            MeterChargeRecordTabView().equatable()
        case .dataGroups:
            MeterDataGroupsTabView()
        case .settings:
            MeterSettingsTabView(isMacIPadApp: Self.isMacIPadApp) {
                withAnimation(.easeInOut(duration: 0.22)) {
                    selectedMeterTab = .home
                }
            }
        }
    }

    private var availableMeterTabs: [MeterTab] {
        var tabs: [MeterTab] = [.home, .live, .chart]
        if meter.supportsRecordingView {
            tabs.append(.chargeRecord)
        }
        tabs.append(.dataGroups)
        tabs.append(.settings)
        return tabs
    }

    private var displayedMeterTab: MeterTab {
        if availableMeterTabs.contains(selectedMeterTab) {
            return selectedMeterTab
        }
        return .home
    }

    private func restoreSelectedTab() {
        guard let restoredTab = MeterTab(rawValue: meter.preferredTabIdentifier) else {
            meter.preferredTabIdentifier = MeterTab.home.rawValue
            selectedMeterTab = .home
            return
        }

        selectedMeterTab = availableMeterTabs.contains(restoredTab) ? restoredTab : .home
    }

    private var meterBackground: some View {
        LinearGradient(
            colors: [
                meter.color.opacity(0.22),
                Color.secondary.opacity(0.08),
                Color.clear
            ],
            startPoint: .topLeading,
            endPoint: .bottomTrailing
        )
        .ignoresSafeArea()
    }

    private func isLandscape(size: CGSize) -> Bool {
        size.width > size.height
    }

    private func tabBarStyle(for landscape: Bool, usesOverlayTabBar: Bool, size: CGSize) -> TabBarStyle {
        if usesOverlayTabBar {
            return .landscapeFloating
        }

        if landscape {
            return .landscapeInline
        }

        if Self.isPhone && size.width < 390 {
            return .portraitCompact
        }

        return .portrait
    }

    private func tabBarPresentation(for size: CGSize, usesOverlayTabBar: Bool) -> AdaptiveTabBarPresentation {
        if usesOverlayTabBar {
            return AdaptiveTabBarPresentation(
                showsTitles: false,
                maxWidth: 260
            )
        }

        return AdaptiveTabBarPresentation.standard(for: size)
    }

    private func landscapeContentTopPadding(for style: TabBarStyle) -> CGFloat {
        if style.floatingInset > 0 {
            return max(landscapeTabBarHeight * 0.44, 26)
        }

        return max(landscapeTabBarHeight - 6, 0)
    }

    private func title(for tab: MeterTab) -> String {
        switch tab {
        case .home:
            return "Home"
        case .live:
            return "Live"
        case .chart:
            return "Chart"
        case .chargeRecord:
            return "Charge Record"
        case .dataGroups:
            return meter.dataGroupsTitle
        case .settings:
            return "Settings"
        }
    }

}

private struct MeterTabBarHeightPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}

// MARK: - Conditional navigation bar modifier (skipped on Designed-for-iPad / Mac)

private struct IOSOnlyNavBar: ViewModifier {
    let apply: Bool
    let title: String
    let showRSSI: Bool
    let rssi: Int
    let meter: Meter

    @ViewBuilder
    func body(content: Content) -> some View {
        if apply {
            content
                .navigationBarTitle(title)
                .toolbar {
                    ToolbarItemGroup(placement: .navigationBarTrailing) {
                        MeterConnectionToolbarButton(
                            operationalState: meter.operationalState,
                            showsTitle: false,
                            connectAction: { meter.connect() },
                            disconnectAction: { meter.disconnect() }
                        )
                        .font(.body.weight(.semibold))
                        if showRSSI {
                            RSSIView(RSSI: rssi)
                                .frame(width: 18, height: 18)
                        }
                    }
                }
        } else {
            content
        }
    }
}
