//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalRingRTC
import SignalServiceKit
import SignalUI

@objc
protocol CallControlsDelegate: AnyObject {
    func didPressRing()
    func didPressJoin()
    func didPressHangup()
    func didPressMore()
}

class CallControls: UIView {
    private lazy var topStackView = createTopStackView()
    private lazy var hangUpButton: CallButton = {
        let button = createButton(
            iconName: "phone-down-fill-28",
            accessibilityLabel: viewModel.hangUpButtonAccessibilityLabel,
            action: #selector(CallControlsViewModel.didPressHangup)
        )
        button.unselectedBackgroundColor = .ows_accentRed
        return button
    }()
    private(set) lazy var audioSourceButton = createButton(
        iconName: "speaker-fill-28",
        accessibilityLabel: viewModel.audioSourceAccessibilityLabel,
        action: #selector(CallControlsViewModel.didPressAudioSource)
    )
    private lazy var muteButton = createButton(
        iconName: "mic-fill",
        selectedIconName: "mic-slash-fill-28",
        accessibilityLabel: viewModel.muteButtonAccessibilityLabel,
        action: #selector(CallControlsViewModel.didPressMute)
    )
    private lazy var videoButton = createButton(
        iconName: "video-fill-28",
        selectedIconName: "video-slash-fill-28",
        accessibilityLabel: viewModel.videoButtonAccessibilityLabel,
        action: #selector(CallControlsViewModel.didPressVideo)
    )
    private lazy var ringButton = createButton(
        iconName: "bell-ring-fill-28",
        selectedIconName: "bell-slash-fill",
        // TODO: Accessibility label
        action: #selector(CallControlsViewModel.didPressRing)
    )
    private lazy var flipCameraButton: CallButton = {
        let button = createButton(
            iconName: "switch-camera-28",
            accessibilityLabel: viewModel.flipCameraButtonAccessibilityLabel,
            action: #selector(CallControlsViewModel.didPressFlipCamera)
        )
        button.selectedIconColor = button.iconColor
        button.selectedBackgroundColor = button.unselectedBackgroundColor
        return button
    }()
    private lazy var moreButton = createButton(
        iconName: "more",
        accessibilityLabel: viewModel.moreButtonAccessibilityLabel,
        action: #selector(CallControlsViewModel.didPressMore)
    )

    private lazy var joinButtonActivityIndicator = UIActivityIndicatorView(style: .medium)

    private lazy var joinButton: UIButton = {
        let height: CGFloat = 56

        let button = OWSButton()
        button.setTitleColor(.ows_white, for: .normal)
        button.setBackgroundImage(UIImage.image(color: .ows_accentGreen), for: .normal)
        button.titleLabel?.font = UIFont.dynamicTypeBodyClamped.semibold()
        button.clipsToBounds = true
        button.layer.cornerRadius = height / 2
        button.block = { [weak self, unowned button] in
            self?.viewModel.didPressJoin()
        }
        button.contentEdgeInsets = UIEdgeInsets(top: 17, leading: 17, bottom: 17, trailing: 17)
        button.addSubview(joinButtonActivityIndicator)
        joinButtonActivityIndicator.autoCenterInSuperview()

        // Expand the button to fit text if necessary.
        button.autoSetDimension(.width, toSize: 168, relation: .greaterThanOrEqual)
        button.autoSetDimension(.height, toSize: height)
        return button
    }()

    static func joinButtonLabel(for call: SignalCall) -> String {
        return CallControlsViewModel.joinButtonLabel(for: call)
    }

    private lazy var gradientView: UIView = {
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [
            UIColor.black.withAlphaComponent(0).cgColor,
            UIColor.ows_blackAlpha60.cgColor
        ]
        let view = OWSLayerView(frame: .zero) { view in
            gradientLayer.frame = view.bounds
        }
        view.layer.addSublayer(gradientLayer)
        return view
    }()

    private weak var delegate: CallControlsDelegate!
    private let viewModel: CallControlsViewModel

    init(
        call: SignalCall,
        callService: CallService,
        confirmationToastManager: CallControlsConfirmationToastManager,
        delegate: CallControlsDelegate
    ) {
        let viewModel = CallControlsViewModel(
            call: call,
            callService: callService,
            confirmationToastManager: confirmationToastManager,
            delegate: delegate
        )
        self.viewModel = viewModel
        self.delegate = delegate
        super.init(frame: .zero)

        viewModel.refreshView = { [weak self] in
            self?.updateControls()
        }

        addSubview(gradientView)
        gradientView.autoPinEdgesToSuperviewEdges()

        let joinButtonContainer = UIView()
        joinButtonContainer.addSubview(joinButton)
        joinButtonContainer.layoutMargins = UIEdgeInsets(hMargin: 16, vMargin: 0)
        joinButton.autoPinWidthToSuperviewMargins(relation: .lessThanOrEqual)
        joinButton.autoPinHeightToSuperview()

        let controlsStack = UIStackView(arrangedSubviews: [
            topStackView,
            joinButtonContainer
        ])
        controlsStack.axis = .vertical
        controlsStack.spacing = 40
        controlsStack.alignment = .center

        addSubview(controlsStack)
        controlsStack.autoPinWidthToSuperview()
        controlsStack.autoPinEdge(toSuperviewSafeArea: .bottom, withInset: 40, relation: .lessThanOrEqual)
        NSLayoutConstraint.autoSetPriority(.defaultHigh - 1) {
            controlsStack.autoPinEdge(toSuperviewSafeArea: .bottom, withInset: 56)
        }
        controlsStack.autoPinEdge(toSuperviewEdge: .top)

        updateControls()
    }

    func createTopStackView() -> UIStackView {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 16

        stackView.addArrangedSubview(audioSourceButton)
        stackView.addArrangedSubview(flipCameraButton)
        stackView.addArrangedSubview(videoButton)
        stackView.addArrangedSubview(muteButton)
        stackView.addArrangedSubview(moreButton)
        stackView.addArrangedSubview(ringButton)
        stackView.addArrangedSubview(hangUpButton)

        return stackView
    }

    private func updateControls() {
        // Top row
        audioSourceButton.isHidden = viewModel.audioSourceButtonIsHidden
        hangUpButton.isHidden = viewModel.hangUpButtonIsHidden
        muteButton.isHidden = viewModel.muteButtonIsHidden
        moreButton.isHidden = viewModel.moreButtonIsHidden
        videoButton.isHidden = viewModel.videoButtonIsHidden
        flipCameraButton.isHidden = viewModel.flipCameraButtonIsHidden
        ringButton.isHidden = viewModel.ringButtonIsHidden

        // Bottom row
        joinButton.superview?.isHidden = viewModel.joinButtonIsHidden

        // Sizing and spacing
        let controlCount = topStackView.arrangedSubviews.filter({!$0.isHidden}).count
        topStackView.spacing = viewModel.controlSpacing(controlCount: controlCount)
        let shouldControlButtonsBeSmall = viewModel.shouldControlButtonsBeSmall(controlCount: controlCount)
        for view in topStackView.arrangedSubviews {
            if let button = view as? CallButton {
                button.isSmall = shouldControlButtonsBeSmall
            }
        }

        // Show/hide the superview to adjust the containing stack.
        gradientView.isHidden = viewModel.gradientViewIsHidden

        videoButton.isSelected = viewModel.videoButtonIsSelected
        muteButton.isSelected = viewModel.muteButtonIsSelected
        audioSourceButton.isSelected = viewModel.audioSourceButtonIsSelected
        ringButton.isSelected = viewModel.ringButtonIsSelected
        flipCameraButton.isSelected = viewModel.flipCameraButtonIsSelected
        moreButton.isSelected = viewModel.moreButtonIsSelected

        if !viewModel.audioSourceButtonIsHidden {
            let config = viewModel.audioSourceButtonConfiguration
            audioSourceButton.showDropdownArrow = config.showDropdownArrow
            audioSourceButton.iconName = config.iconName
        }

        if
            !viewModel.ringButtonIsHidden,
            let ringButtonConfig = viewModel.ringButtonConfiguration
        {
            ringButton.isUserInteractionEnabled = ringButtonConfig.isUserInteractionEnabled
            ringButton.isSelected = ringButtonConfig.isSelected
            ringButton.shouldDrawAsDisabled = ringButtonConfig.shouldDrawAsDisabled
        }

        if !viewModel.joinButtonIsHidden {
            let joinButtonConfig = viewModel.joinButtonConfig
            joinButton.setTitle(joinButtonConfig.label, for: .normal)
            joinButton.setTitleColor(joinButtonConfig.color, for: .normal)
            joinButton.adjustsImageWhenHighlighted = joinButtonConfig.adjustsImageWhenHighlighted
            joinButton.isUserInteractionEnabled = joinButtonConfig.isUserInteractionEnabled
            if viewModel.shouldJoinButtonActivityIndicatorBeAnimating {
                joinButtonActivityIndicator.startAnimating()
            } else {
                joinButtonActivityIndicator.stopAnimating()
            }
        }

        hangUpButton.accessibilityLabel = viewModel.hangUpButtonAccessibilityLabel
        audioSourceButton.accessibilityLabel = viewModel.audioSourceAccessibilityLabel
        muteButton.accessibilityLabel = viewModel.muteButtonAccessibilityLabel
        videoButton.accessibilityLabel = viewModel.videoButtonAccessibilityLabel
        flipCameraButton.accessibilityLabel = viewModel.flipCameraButtonAccessibilityLabel
        moreButton.accessibilityLabel = viewModel.moreButtonAccessibilityLabel
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func createButton(
        iconName: String,
        selectedIconName: String? = nil,
        accessibilityLabel: String? = nil,
        action: Selector
    ) -> CallButton {
        let button = CallButton(iconName: iconName)
        button.selectedIconName = selectedIconName
        button.accessibilityLabel = accessibilityLabel
        button.addTarget(viewModel, action: action, for: .touchUpInside)
        button.setContentHuggingHorizontalHigh()
        button.setCompressionResistanceHorizontalLow()
        button.alpha = 0.9
        return button
    }
}

private class CallControlsViewModel {
    private let call: SignalCall
    private let callService: CallService
    private weak var delegate: CallControlsDelegate?
    private let confirmationToastManager: CallControlsConfirmationToastManager
    fileprivate var refreshView: (() -> Void)?
    init(
        call: SignalCall,
        callService: CallService,
        confirmationToastManager: CallControlsConfirmationToastManager,
        delegate: CallControlsDelegate
    ) {
        self.call = call
        self.callService = callService
        self.confirmationToastManager = confirmationToastManager
        self.delegate = delegate
        switch call.mode {
        case .individual(let call):
            call.addObserverAndSyncState(self)
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            call.addObserverAndSyncState(self)
        }
        callService.audioService.delegate = self
    }

    deinit {
        callService.audioService.delegate = nil
    }

    private var hasExternalAudioInputsAndAudioSource: Bool {
        let audioService = callService.audioService
        return audioService.hasExternalInputs && audioService.currentAudioSource != nil
    }

    var audioSourceButtonIsHidden: Bool {
        if hasExternalAudioInputsAndAudioSource {
            return false
        } else if UIDevice.current.isIPad {
            // iPad *only* supports speaker mode, if there are no external
            // devices connected, so we don't need to show the button unless
            // we have alternate audio sources.
            return true
        } else {
            return !call.isOutgoingVideoMuted
        }
    }

    struct AudioSourceButtonConfiguration {
        let showDropdownArrow: Bool
        let iconName: String
    }

    var audioSourceButtonConfiguration: AudioSourceButtonConfiguration {
        let showDropdownArrow: Bool
        let iconName: String
        if
            callService.audioService.hasExternalInputs,
            let audioSource = callService.audioService.currentAudioSource
        {
            showDropdownArrow = true
            if audioSource.isBuiltInEarPiece {
                iconName = "phone-fill-28"
            } else if audioSource.isBuiltInSpeaker {
                iconName = "speaker-fill-28"
            } else {
                iconName = "speaker-bt-fill-28"
            }
        } else {
            // No bluetooth audio detected
            showDropdownArrow = false
            iconName = "speaker-fill-28"
        }
        return AudioSourceButtonConfiguration(showDropdownArrow: showDropdownArrow, iconName: iconName)
    }

    var hangUpButtonIsHidden: Bool {
        switch call.mode {
        case .individual(_):
            return false
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            return !call.hasJoinedOrIsWaitingForAdminApproval
        }
    }

    var muteButtonIsHidden: Bool {
        return false
    }

    var videoButtonIsHidden: Bool {
        return false
    }

    var flipCameraButtonIsHidden: Bool {
        if call.isOutgoingVideoMuted {
            return true
        }

        switch call.mode {
        case .individual(let call):
            return ![.idle, .dialing, .remoteRinging, .localRinging_Anticipatory, .localRinging_ReadyToAnswer].contains(call.state)
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            // Because joined group calls include the `moreButton`, we're out of
            // space for the `flipCameraButton`.
            //
            // Recall that the flip camera button is in the local pip once someone
            // else has joined the call. That leaves the issue of where to locate
            // the flip camera button when the user is fullscreen because they have
            // joined and are the sole member in the call.
            //
            // TODO: Implement the future designs for this. In the meantime, Design
            // wants us to omit the flip camera button from Call Controls when in
            // joined fullscreen.
            return call.hasJoinedOrIsWaitingForAdminApproval
        }
    }

    var joinButtonIsHidden: Bool {
        switch call.mode {
        case .individual(_):
            // TODO: Introduce lobby for starting 1:1 video calls.
            return true
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            return call.hasJoinedOrIsWaitingForAdminApproval
        }
    }

    struct JoinButtonConfiguration {
        let label: String
        let color: UIColor
        let adjustsImageWhenHighlighted: Bool
        let isUserInteractionEnabled: Bool
    }

    var joinButtonConfig: JoinButtonConfiguration {
        if call.isFull {
            // Make the button look disabled, but don't actually disable it.
            // We want to show a toast if the user taps anyway.
            return JoinButtonConfiguration(
                label: OWSLocalizedString(
                    "GROUP_CALL_IS_FULL",
                    comment: "Text explaining the group call is full"
                ),
                color: .ows_whiteAlpha40,
                adjustsImageWhenHighlighted: false,
                isUserInteractionEnabled: true
            )
        }
        if call.joinState == .joining {
            return JoinButtonConfiguration(
                label: "",
                color: .ows_whiteAlpha40,
                adjustsImageWhenHighlighted: false,
                isUserInteractionEnabled: false
            )
        }
        return JoinButtonConfiguration(
            label: Self.joinButtonLabel(for: call),
            color: .white,
            adjustsImageWhenHighlighted: true,
            isUserInteractionEnabled: true
        )
    }

    static func joinButtonLabel(for call: SignalCall) -> String {
        switch call.mode {
        case .individual:
            // We only show a lobby for 1:1 calls when the call is being initiated.
            // TODO: The work of adding the lobby for 1:1 calls in the unified call view
            // controller (currently GroupCallViewController) is not yet complete.
            return startCallText()
        case .groupThread(let call):
            return call.ringRestrictions.contains(.callInProgress) ? joinCallText() : startCallText()
        case .callLink(let call):
            return call.mayNeedToAskToJoin ? askToJoinText() : joinCallText()
        }
    }

    private static func startCallText() -> String {
        return OWSLocalizedString(
            "CALL_START_BUTTON",
            comment: "Button to start a call"
        )
    }

    private static func joinCallText() -> String {
        return OWSLocalizedString(
            "GROUP_CALL_JOIN_BUTTON",
            comment: "Button to join an ongoing group call"
        )
    }

    private static func askToJoinText() -> String {
        return OWSLocalizedString(
            "ASK_TO_JOIN_CALL",
            comment: "Button to try to join a call. The admin may need to approve the request before the user can join."
        )
    }

    var shouldJoinButtonActivityIndicatorBeAnimating: Bool {
        return (call.joinState == .joining || call.joinState == .pending) && !joinButtonIsHidden
    }

    var ringButtonIsHidden: Bool {
        switch call.mode {
        case .individual(_):
            return true
        case .groupThread(let call):
            return call.joinState == .joined || call.ringRestrictions.contains(.callInProgress)
        case .callLink:
            return true
        }
    }

    struct RingButtonConfiguration {
        let isUserInteractionEnabled: Bool
        let isSelected: Bool
        let shouldDrawAsDisabled: Bool
    }

    var ringButtonConfiguration: RingButtonConfiguration? {
        switch call.mode {
        case .individual(_):
            // We never show the ring button for 1:1 calls.
            return nil
        case .groupThread(let call):
            // Leave the button visible but locked if joining, like the "join call" button.
            let isUserInteractionEnabled = call.joinState == .notJoined
            let isSelected: Bool
            if
                call.ringRestrictions.isEmpty,
                case .shouldRing = call.groupCallRingState
            {
                isSelected = false
            } else {
                isSelected = true
            }
            // Leave the button enabled so we can present an explanatory toast, but show it disabled.
            let shouldDrawAsDisabled = !call.ringRestrictions.isEmpty
            return RingButtonConfiguration(
                isUserInteractionEnabled: isUserInteractionEnabled,
                isSelected: isSelected,
                shouldDrawAsDisabled: shouldDrawAsDisabled
            )
        case .callLink:
            return nil
        }
    }

    var moreButtonIsHidden: Bool {
        switch call.mode {
        case .individual(_):
            return true
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            // [Call Link] TODO: Figure out if this should be shown while pending.
            return call.ringRtcCall.localDeviceState.joinState != .joined
        }
    }

    var gradientViewIsHidden: Bool {
        switch call.mode {
        case .individual:
            return call.joinState != .joined
        case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
            return !call.hasJoinedOrIsWaitingForAdminApproval
        }
    }

    var videoButtonIsSelected: Bool {
        return call.isOutgoingVideoMuted
    }

    var muteButtonIsSelected: Bool {
        return call.isOutgoingAudioMuted
    }

    var ringButtonIsSelected: Bool {
        if let config = ringButtonConfiguration {
            return config.isSelected
        }
        // Ring button shouldn't be shown in this case anyway.
        return false
    }

    var audioSourceButtonIsSelected: Bool {
        return callService.audioService.isSpeakerEnabled
    }

    var flipCameraButtonIsSelected: Bool {
        return false
    }

    var moreButtonIsSelected: Bool {
        return false
    }

    func controlSpacing(controlCount: Int) -> CGFloat {
        return (UIDevice.current.isNarrowerThanIPhone6 && controlCount > 4) ? 12 : 16
    }

    func shouldControlButtonsBeSmall(controlCount: Int) -> Bool {
        return UIDevice.current.isIPad ? false : controlCount > 4
    }
}

extension CallControlsViewModel: GroupCallObserver {
    func groupCallLocalDeviceStateChanged(_ call: GroupCall) {
        refreshView?()
    }

    func groupCallPeekChanged(_ call: GroupCall) {
        refreshView?()
    }

    func groupCallRemoteDeviceStatesChanged(_ call: GroupCall) {
        refreshView?()
    }

    func groupCallEnded(_ call: GroupCall, reason: GroupCallEndReason) {
        refreshView?()
    }
}

extension CallControlsViewModel: IndividualCallObserver {
    func individualCallStateDidChange(_ call: IndividualCall, state: CallState) {
        refreshView?()
    }

    func individualCallLocalVideoMuteDidChange(_ call: IndividualCall, isVideoMuted: Bool) {
        refreshView?()
    }

    func individualCallLocalAudioMuteDidChange(_ call: IndividualCall, isAudioMuted: Bool) {
        refreshView?()
    }

    func individualCallHoldDidChange(_ call: IndividualCall, isOnHold: Bool) {
        refreshView?()
    }

    func individualCallRemoteVideoMuteDidChange(_ call: IndividualCall, isVideoMuted: Bool) {
        refreshView?()
    }

    func individualCallRemoteSharingScreenDidChange(_ call: IndividualCall, isRemoteSharingScreen: Bool) {
        refreshView?()
    }
}

extension CallControlsViewModel: CallAudioServiceDelegate {
    func callAudioServiceDidChangeAudioSession(_ callAudioService: CallAudioService) {
        refreshView?()
    }

    func callAudioServiceDidChangeAudioSource(_ callAudioService: CallAudioService, audioSource: AudioSource?) {
        refreshView?()
    }
}

extension CallControlsViewModel {
    @objc
    func didPressHangup() {
        callService.callUIAdapter.localHangupCall(call)
        delegate?.didPressHangup()
    }

    @objc
    func didPressAudioSource() {
        if callService.audioService.hasExternalInputs {
            callService.audioService.presentRoutePicker()
        } else {
            let shouldEnableSpeakerphone = !audioSourceButtonIsSelected
            callService.audioService.requestSpeakerphone(call: self.call, isEnabled: shouldEnableSpeakerphone)
            confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .speakerphone(isOn: shouldEnableSpeakerphone))
        }
        refreshView?()
    }

    @objc
    func didPressMute() {
        let shouldMute = !muteButtonIsSelected
        callService.updateIsLocalAudioMuted(isLocalAudioMuted: shouldMute)
        confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .mute(isOn: shouldMute))
        refreshView?()
    }

    @objc
    func didPressVideo() {
        callService.updateIsLocalVideoMuted(isLocalVideoMuted: !call.isOutgoingVideoMuted)

        // When turning off video, default speakerphone to on.
        if call.isOutgoingVideoMuted && !callService.audioService.hasExternalInputs {
            callService.audioService.requestSpeakerphone(call: self.call, isEnabled: true)
        }
        refreshView?()
    }

    @objc
    func didPressRing() {
        switch call.mode {
        case .individual:
            owsFailDebug("Can't control ringing for an individual call.")
        case .groupThread(let call):
            if call.ringRestrictions.isEmpty {
                switch call.groupCallRingState {
                case .shouldRing:
                    call.groupCallRingState = .doNotRing
                    confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .ring(isOn: false))
                case .doNotRing:
                    call.groupCallRingState = .shouldRing
                    confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .ring(isOn: true))
                default:
                    owsFailBeta("Ring button should not have been available to press!")
                }
                refreshView?()
            }
            delegate?.didPressRing()
        case .callLink:
            owsFailDebug("Can't ring Call Link call.")
        }
    }

    @objc
    func didPressFlipCamera() {
        if let isUsingFrontCamera = call.videoCaptureController.isUsingFrontCamera {
            callService.updateCameraSource(call: call, isUsingFrontCamera: !isUsingFrontCamera)
            refreshView?()
        }
    }

    @objc
    func didPressJoin() {
        delegate?.didPressJoin()
    }

    @objc
    func didPressMore() {
        delegate?.didPressMore()
    }
}

// MARK: - Accessibility

extension CallControlsViewModel {
    public var hangUpButtonAccessibilityLabel: String {
        switch call.mode {
        case .individual(_):
            return OWSLocalizedString(
                "CALL_VIEW_HANGUP_LABEL",
                comment: "Accessibility label for hang up call"
            )
        case .groupThread, .callLink:
            return OWSLocalizedString(
                "CALL_VIEW_LEAVE_CALL_LABEL",
                comment: "Accessibility label for leaving a call"
            )
        }
    }

    public var audioSourceAccessibilityLabel: String {
        // TODO: This is not the most helpful descriptor.
        return OWSLocalizedString(
            "CALL_VIEW_AUDIO_SOURCE_LABEL",
            comment: "Accessibility label for selection the audio source"
        )
    }

    public var muteButtonAccessibilityLabel: String {
        if call.isOutgoingAudioMuted {
            return OWSLocalizedString(
                "CALL_VIEW_UNMUTE_LABEL",
                comment: "Accessibility label for unmuting the microphone"
            )
        } else {
            return OWSLocalizedString(
                "CALL_VIEW_MUTE_LABEL",
                comment: "Accessibility label for muting the microphone"
            )
        }
    }

    public var videoButtonAccessibilityLabel: String {
        if call.isOutgoingVideoMuted {
            return OWSLocalizedString(
                "CALL_VIEW_TURN_VIDEO_ON_LABEL",
                comment: "Accessibility label for turning on the camera"
            )
        } else {
            return OWSLocalizedString(
                "CALL_VIEW_TURN_VIDEO_OFF_LABEL",
                comment: "Accessibility label for turning off the camera"
            )
        }
    }

    public var flipCameraButtonAccessibilityLabel: String {
        return OWSLocalizedString(
            "CALL_VIEW_SWITCH_CAMERA_DIRECTION",
            comment: "Accessibility label to toggle front- vs. rear-facing camera"
        )
    }

    public var moreButtonAccessibilityLabel: String {
        return OWSLocalizedString(
            "CALL_VIEW_MORE_LABEL",
            comment: "Accessibility label for the More button in the Call Controls row."
        )
    }
}
