//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//

import Compound
import SwiftUI

struct QRCodeLoginScreen: View {
    @ObservedObject var context: QRCodeLoginScreenViewModel.Context
    
    @State private var qrFrame = CGRect.zero
    @FocusState private var checkCodeInputFocus
    
    var backgroundStyle: Color {
        if case .error = context.viewState.state {
            .compound.bgCanvasDefault
        } else {
            .compound.bgSubtleSecondary
        }
    }
    
    var body: some View {
        mainContent
            .toolbar { toolbar }
            .toolbar(.visible, for: .navigationBar)
            .background()
            .backgroundStyle(backgroundStyle)
            .interactiveDismissDisabled()
            .navigationBarBackButtonHidden(!context.viewState.shouldDisplayBackButton)
    }
    
    @ViewBuilder
    var mainContent: some View {
        switch context.viewState.state {
        case .loginInstructions:
            loginInstructionsContent
        case .linkDesktopInstructions:
            linkDesktopInstructionsContent
        case .scan:
            qrScannerContent
        case .displayCode:
            displayCodeContent
        case .displayQR:
            displayQRContent
        case .confirmCode:
            confirmCodeContent
        case .error(let errorState):
            QRCodeErrorView(errorState: errorState, canSignInManually: context.viewState.canSignInManually) { action in
                context.send(viewAction: .errorAction(action))
            }
        }
    }
    
    private var loginInstructionsContent: some View {
        FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
            VStack(alignment: .leading, spacing: 40) {
                TitleAndIcon(title: L10n.screenQrCodeLoginInitialStateTitle(InfoPlistReader.main.productionAppName),
                             subtitle: L10n.screenQrCodeLoginInitialStateSubtitle,
                             icon: \.computer,
                             iconStyle: .default)
                
                SFNumberedListView(items: context.viewState.instructions.loginItems)
            }
        } bottomContent: {
            Button(L10n.screenQrCodeLoginInitialStateButtonTitle) {
                context.send(viewAction: .startScan)
            }
            .buttonStyle(.compound(.primary))
        }
    }
    
    private var linkDesktopInstructionsContent: some View {
        FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
            VStack(alignment: .leading, spacing: 40) {
                TitleAndIcon(title: L10n.screenLinkNewDeviceDesktopTitle(InfoPlistReader.main.productionAppName),
                             icon: \.computer,
                             iconStyle: .default)
                
                SFNumberedListView(items: context.viewState.instructions.linkDesktopItems)
            }
        } bottomContent: {
            Button(L10n.screenLinkNewDeviceDesktopSubmit) {
                context.send(viewAction: .startScan)
            }
            .buttonStyle(.compound(.primary))
        }
    }
    
    @ViewBuilder
    private var displayCodeContent: some View {
        if case let .displayCode(displayCodeState) = context.viewState.state {
            FullscreenDialog(topPadding: 24) {
                VStack(spacing: 32) {
                    VStack(spacing: 24) {
                        displayCodeHeader(state: displayCodeState)
                            .padding(.horizontal, 8)
                        
                        PINTextField(pinCode: .constant(displayCodeState.code),
                                     maxLength: displayCodeState.code.count,
                                     size: .small)
                            .disabled(true)
                    }
                    
                    VStack(spacing: 4) {
                        ProgressView()
                        
                        Text(L10n.screenQrCodeLoginVerifyCodeLoading)
                            .foregroundColor(.compound.textSecondary)
                            .font(.compound.bodySM)
                            .multilineTextAlignment(.center)
                    }
                }
            } bottomContent: {
                Button(L10n.actionCancel) {
                    context.send(viewAction: .cancel)
                }
                .buttonStyle(.compound(.secondary))
            }
        }
    }
    
    private func displayCodeHeader(state: QRCodeLoginState.DisplayCodeState) -> some View {
        switch state {
        case .deviceCode:
            TitleAndIcon(title: L10n.screenQrCodeLoginDeviceCodeTitle,
                         subtitle: L10n.screenQrCodeLoginDeviceCodeSubtitle,
                         icon: \.computer,
                         iconStyle: .default)
        case .verificationCode:
            TitleAndIcon(title: L10n.screenQrCodeLoginVerifyCodeTitle,
                         subtitle: L10n.screenQrCodeLoginVerifyCodeSubtitle,
                         icon: \.lock,
                         iconStyle: .default)
        }
    }
    
    private var qrScannerContent: some View {
        FullscreenDialog(topPadding: 24) {
            VStack(spacing: 40) {
                TitleAndIcon(title: L10n.screenQrCodeLoginScanningStateTitle,
                             icon: \.takePhotoSolid,
                             iconStyle: .default)
                
                qrScanner
            }
        } bottomContent: {
            qrScannerFooter
        }
    }
    
    @ViewBuilder
    private var qrScannerFooter: some View {
        if case let .scan(scanState) = context.viewState.state {
            switch scanState {
            case .connecting:
                VStack(spacing: 4) {
                    ProgressView()
                    Text(L10n.screenQrCodeLoginConnectingSubtitle)
                        .foregroundColor(.compound.textSecondary)
                        .font(.compound.bodySM)
                        .multilineTextAlignment(.center)
                }
            case .scanning:
                // To keep the spacing consistent between states
                Button("") { }
                    .buttonStyle(.compound(.primary))
                    .hidden()
            case .scanFailed(let error):
                VStack(spacing: 16) {
                    Button(L10n.screenQrCodeLoginInvalidScanStateRetryButton) {
                        context.send(viewAction: .startScan)
                    }
                    .buttonStyle(.compound(.primary))
                    
                    VStack(spacing: 4) {
                        Label(error.title,
                              icon: \.errorSolid,
                              iconSize: .medium,
                              relativeTo: .compound.bodyMDSemibold)
                            .labelStyle(.custom(spacing: 10))
                            .font(.compound.bodyMDSemibold)
                            .foregroundColor(.compound.textCriticalPrimary)
                        
                        Text(error.description)
                            .foregroundColor(.compound.textSecondary)
                            .font(.compound.bodySM)
                            .multilineTextAlignment(.center)
                    }
                    .fixedSize(horizontal: false, vertical: true)
                }
            }
        }
    }
    
    private var qrScanner: some View {
        QRCodeScannerView(result: $context.qrResult, isScanning: context.viewState.state.isScanning)
            .aspectRatio(1.0, contentMode: .fill)
            .frame(maxWidth: 312)
            .readFrame($qrFrame)
            .background(.compound.bgCanvasDefault)
            .overlay(QRScannerViewOverlay(length: qrFrame.height))
    }
    
    @ViewBuilder
    private var displayQRContent: some View {
        if case let .displayQR(image) = context.viewState.state {
            FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
                VStack(spacing: 32) {
                    TitleAndIcon(title: L10n.screenLinkNewDeviceMobileTitle(InfoPlistReader.main.productionAppName),
                                 icon: \.takePhotoSolid,
                                 iconStyle: .default)
                    
                    Image(uiImage: image)
                        .interpolation(.none) // to stop it getting blurred
                        .resizable()
                        .scaledToFit()
                        .frame(width: 200, height: 200)
                        .accessibilityLabel(L10n.a11yQrCode)
                    
                    SFNumberedListView(items: context.viewState.instructions.linkMobileItems)
                }
            } bottomContent: { }
        }
    }
    
    @ViewBuilder
    private var confirmCodeContent: some View {
        if case let .confirmCode(confirmCode) = context.viewState.state {
            FullscreenDialog(topPadding: 24, horizontalPadding: 24) {
                VStack(spacing: 24) {
                    TitleAndIcon(title: L10n.screenLinkNewDeviceEnterNumberTitle,
                                 subtitle: L10n.screenLinkNewDeviceEnterNumberSubtitle,
                                 icon: \.computer,
                                 iconStyle: .default)
                    
                    VStack(spacing: 10) {
                        Text(L10n.screenLinkNewDeviceEnterNumberNotice)
                            .font(.compound.bodyMDSemibold)
                            .multilineTextAlignment(.center)
                            .foregroundStyle(.compound.textSecondary)
                        
                        PINTextField(pinCode: $context.checkCodeInput, maxLength: 2, size: .medium)
                            .focused($checkCodeInputFocus)
                            .disabled(confirmCode.isSending)
                        
                        if case .confirmCode(.invalidCode) = context.viewState.state {
                            Label(L10n.screenLinkNewDeviceEnterNumberErrorNumbersDoNotMatch,
                                  icon: \.errorSolid,
                                  iconSize: .medium,
                                  relativeTo: .compound.bodyMDSemibold)
                                .labelStyle(.custom(spacing: 10))
                                .font(.compound.bodyMDSemibold)
                                .foregroundColor(.compound.textCriticalPrimary)
                        }
                    }
                }
            } bottomContent: {
                switch confirmCode {
                case .inputCode, .sendingCode:
                    Button(L10n.actionContinue) {
                        context.send(viewAction: .sendCheckCode)
                    }
                    .buttonStyle(.compound(.primary))
                    .disabled(context.checkCodeInput.count < 2 || confirmCode.isSending)
                case .invalidCode:
                    Button(L10n.actionStartOver) {
                        context.send(viewAction: .errorAction(.startOver))
                    }
                    .buttonStyle(.compound(.primary))
                }
            }
            .onAppear { checkCodeInputFocus = true }
        }
    }
    
    @ToolbarContentBuilder
    private var toolbar: some ToolbarContent {
        ToolbarItem(placement: .cancellationAction) {
            if context.viewState.shouldDisplayCancelButton {
                Button(L10n.actionCancel) {
                    context.send(viewAction: .cancel)
                }
            }
        }
    }
}

private struct QRScannerViewOverlay: View {
    let length: CGFloat
    
    private let dashRatio: CGFloat = 80.0 / 312.0
    private let emptyRatio: CGFloat = 232.0 / 312.0
    private let dashPhaseRatio: CGFloat = 40.0 / 312.0
    
    private var dashLength: CGFloat {
        length * dashRatio
    }
    
    private var emptyLength: CGFloat {
        length * emptyRatio
    }
    
    private var dashPhase: CGFloat {
        length * dashPhaseRatio
    }
    
    var body: some View {
        Rectangle()
            .stroke(.compound.textPrimary, style: StrokeStyle(lineWidth: 6.0, lineCap: .square, dash: [dashLength, emptyLength], dashPhase: dashPhase))
    }
}

// MARK: - Previews

import MatrixRustSDKMocks

struct QRCodeLoginScreen_Previews: PreviewProvider, TestablePreview {
    // Instructions
    static let loginInstructionsStateViewModel = QRCodeLoginScreenViewModel.mock(state: .loginInstructions)
    static let linkInstructionsStateViewModel = QRCodeLoginScreenViewModel.mock(state: .linkDesktopInstructions)
    
    /// Scanning
    static let scanningStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.scanning))
    
    static let connectingStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.connecting))
    
    static let invalidStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.scanFailed(.invalid)))
    
    static let notAllowedStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.scanFailed(.notAllowed(scannedProvider: "evil.com",
                                                                                                               allowedProviders: ["example.com",
                                                                                                                                  "server.net"]))))
    
    static let deviceNotSignedInStateViewModel = QRCodeLoginScreenViewModel.mock(state: .scan(.scanFailed(.deviceNotSignedIn)))
    
    /// Showing
    static let showingStateViewModel = QRCodeLoginScreenViewModel.mock(state: .displayQR(LinkNewDeviceServiceMock.mockQRCodeImage))
    
    // Displaying codes
    static let deviceCodeStateViewModel = QRCodeLoginScreenViewModel.mock(state: .displayCode(.deviceCode("12")))
    static let verificationCodeStateViewModel = QRCodeLoginScreenViewModel.mock(state: .displayCode(.verificationCode("123456")))
    
    static let confirmCodeStateViewModel = QRCodeLoginScreenViewModel.mock(state: .confirmCode(.inputCode(CheckCodeSenderProxy(underlyingSender: CheckCodeSenderSDKMock()))))
    static let confirmCodeEnteredStateViewModel = QRCodeLoginScreenViewModel.mock(state: .confirmCode(.inputCode(CheckCodeSenderProxy(underlyingSender: CheckCodeSenderSDKMock()))), checkCodeInput: "12")
    static let confirmCodeInvalidStateViewModel = QRCodeLoginScreenViewModel.mock(state: .confirmCode(.invalidCode))
    
    /// Errors (no need to test them all QRCodeErrorView covers that).
    static let errorStateViewModel = QRCodeLoginScreenViewModel.mock(state: .error(.declined))
    
    static var previews: some View {
        ElementNavigationStack { QRCodeLoginScreen(context: loginInstructionsStateViewModel.context) }
            .previewDisplayName("Login instructions")
        ElementNavigationStack { QRCodeLoginScreen(context: linkInstructionsStateViewModel.context) }
            .previewDisplayName("Link instructions")
        
        ElementNavigationStack { QRCodeLoginScreen(context: scanningStateViewModel.context) }
            .previewDisplayName("Scanning")
        ElementNavigationStack { QRCodeLoginScreen(context: connectingStateViewModel.context) }
            .previewDisplayName("Connecting")
        ElementNavigationStack { QRCodeLoginScreen(context: invalidStateViewModel.context) }
            .previewDisplayName("Invalid")
        ElementNavigationStack { QRCodeLoginScreen(context: notAllowedStateViewModel.context) }
            .previewDisplayName("Not allowed")
        ElementNavigationStack { QRCodeLoginScreen(context: deviceNotSignedInStateViewModel.context) }
            .previewDisplayName("Device not signed in")
        
        ElementNavigationStack { QRCodeLoginScreen(context: showingStateViewModel.context) }
            .previewDisplayName("Showing")
        
        ElementNavigationStack { QRCodeLoginScreen(context: deviceCodeStateViewModel.context) }
            .previewDisplayName("Device code")
        ElementNavigationStack { QRCodeLoginScreen(context: verificationCodeStateViewModel.context) }
            .previewDisplayName("Verification code")
        
        ElementNavigationStack { QRCodeLoginScreen(context: confirmCodeStateViewModel.context) }
            .previewDisplayName("Confirm code")
        ElementNavigationStack { QRCodeLoginScreen(context: confirmCodeEnteredStateViewModel.context) }
            .previewDisplayName("Confirm code entered")
        ElementNavigationStack { QRCodeLoginScreen(context: confirmCodeInvalidStateViewModel.context) }
            .previewDisplayName("Confirm code invalid")
        
        ElementNavigationStack { QRCodeLoginScreen(context: errorStateViewModel.context) }
            .previewDisplayName("Error")
    }
}
