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

import SafariServices
import SignalServiceKit
import SignalUI

// MARK: - RegistrationProfileState

public struct RegistrationProfileState: Equatable {
    let e164: E164
    let phoneNumberDiscoverability: PhoneNumberDiscoverability
}

// MARK: - RegistrationProfilePresenter

protocol RegistrationProfilePresenter: AnyObject {
    func goToNextStep(
        givenName: OWSUserProfile.NameComponent,
        familyName: OWSUserProfile.NameComponent?,
        avatarData: Data?,
        phoneNumberDiscoverability: PhoneNumberDiscoverability
    )
}

// MARK: - RegistrationProfileViewController

class RegistrationProfileViewController: OWSViewController {
    private var profilesFAQURL: URL { URL(string: "https://support.signal.org/hc/articles/360007459591")! }

    var state: RegistrationProfileState
    public init(
        state: RegistrationProfileState,
        presenter: RegistrationProfilePresenter
    ) {
        self.presenter = presenter
        self.state = state

        super.init()
    }

    @available(*, unavailable)
    public override init() {
        owsFail("This should not be called")
    }

    // MARK: Internal state

    private weak var presenter: RegistrationProfilePresenter?

    private var givenNameComponent: OWSUserProfile.NameComponent? {
        return OWSUserProfile.NameComponent(truncating: givenNameTextField.text ?? "")
    }

    private var familyNameComponent: OWSUserProfile.NameComponent? {
        return OWSUserProfile.NameComponent(truncating: familyNameTextField.text ?? "")
    }

    private var avatarData: Data? {
        didSet { render() }
    }

    // MARK: Rendering

    private lazy var nextBarButton = UIBarButtonItem(
        title: CommonStrings.nextButton,
        style: .done,
        target: self,
        action: #selector(didTapNext),
        accessibilityIdentifier: "registration.profile.nextButton"
    )

    private lazy var titleLabel: UILabel = {
        let result = UILabel.titleLabelForRegistration(text: OWSLocalizedString(
            "REGISTRATION_PROFILE_SETUP_TITLE",
            comment: "During registration, users set up their profile. This is the title on the screen where this is done."
        ))
        result.accessibilityIdentifier = "registration.profile.titleLabel"
        return result
    }()

    private lazy var explanationView: LinkingTextView = {
        let result = LinkingTextView()
        result.attributedText = .composed(of: [
            OWSLocalizedString(
                "REGISTRATION_PROFILE_SETUP_SUBTITLE",
                comment: "During registration, users set up their profile. This is the subtitle on the screen where this is done. It tells users about the privacy of their profile. A \"learn more\" link will be added to the end of this string."
            ),
            " ",
            CommonStrings.learnMore.styled(with: {
                // We'd like a link that doesn't go anywhere, because we'd like to handle the
                // tapping ourselves. We use a "fake" URL because BonMot needs one.
                return StringStyle.Part.link(profilesFAQURL)
            }())
        ])
        result.font = .fontForRegistrationExplanationLabel
        result.textAlignment = .center
        result.delegate = self
        return result
    }()

    private lazy var avatarTapGestureRecognizer = UITapGestureRecognizer(
        target: self,
        action: #selector(didTapAvatar)
    )

    private let avatarSize: CGFloat = 64
    private lazy var avatarView: AvatarImageView = {
        let result = AvatarImageView()
        result.autoSetDimensions(to: .square(avatarSize))
        result.accessibilityIdentifier = "registration.profile.avatarView"

        result.addGestureRecognizer(avatarTapGestureRecognizer)
        result.isUserInteractionEnabled = true
        return result
    }()

    private lazy var avatarContainerView: UIView = {
        let result = UIView()
        result.addSubview(avatarView)
        result.autoSetDimension(.height, toSize: avatarSize)
        avatarView.autoCenterInSuperview()
        return result
    }()

    private lazy var cameraImageView: UIImageView = {
        let result = UIImageView.withTemplateImageName(
            "camera-compact",
            // This color will be swiftly updated during renders.
            tintColor: Theme.secondaryTextAndIconColor
        )
        result.autoSetDimensions(to: CGSize(square: 16))
        return result
    }()

    private lazy var cameraImageWrapperView: UIView = {
        let result = UIView()
        let size = CGSize(square: 28)
        result.addSubview(cameraImageView)
        result.layer.cornerRadius = size.largerAxis / 2
        cameraImageView.autoCenterInSuperview()
        result.autoSetDimensions(to: size)
        result.addGestureRecognizer(avatarTapGestureRecognizer)
        result.isUserInteractionEnabled = true
        return result
    }()

    private func textField(
        placeholder: String,
        textContentType: UITextContentType,
        accessibilityIdentifierSuffix: String
    ) -> UITextField {
        let result = OWSTextField()
        result.font = .dynamicTypeSubheadlineClamped
        result.adjustsFontForContentSizeCategory = true
        result.textAlignment = .natural
        result.autocorrectionType = .no
        result.spellCheckingType = .no

        result.placeholder = placeholder
        result.textContentType = textContentType
        result.accessibilityIdentifier = "registration.profile.\(accessibilityIdentifierSuffix)"

        result.delegate = self
        result.addTarget(self, action: #selector(didTextFieldChange), for: .editingChanged)

        result.autoSetDimension(.height, toSize: 50, relation: .greaterThanOrEqual)

        return result
    }

    private lazy var givenNameTextField: UITextField = textField(
        placeholder: OWSLocalizedString(
            "REGISTRATION_PROFILE_SETUP_GIVEN_NAME_FIELD_PLACEHOLDER",
            comment: "During registration, users set up their profile. Users input a given name. This is the placeholder for that field."
        ),
        textContentType: .givenName,
        accessibilityIdentifierSuffix: "givenName"
    )

    private lazy var familyNameTextField: UITextField = textField(
        placeholder: OWSLocalizedString(
            "REGISTRATION_PROFILE_SETUP_FAMILY_NAME_FIELD_PLACEHOLDER",
            comment: "During registration, users set up their profile. Users input a family name. This is the placeholder for that field."
        ),
        textContentType: .familyName,
        accessibilityIdentifierSuffix: "familyName"
    )

    private lazy var textFieldStrokes: [UIView] = [givenNameTextField, familyNameTextField].map {
        // This color will be swiftly updated during renders.
        $0.addBottomStroke(color: Theme.cellSeparatorColor, strokeWidth: .hairlineWidth)
    }

    private enum NameOrder {
        case familyNameFirst
        case givenNameFirst
    }
    private var nameOrder: NameOrder { Locale.current.isCJKV ? .familyNameFirst : .givenNameFirst }

    private lazy var firstTextField: UITextField = {
        switch nameOrder {
        case .givenNameFirst: return givenNameTextField
        case .familyNameFirst: return familyNameTextField
        }
    }()

    private lazy var secondTextField: UITextField = {
        switch nameOrder {
        case .givenNameFirst: return familyNameTextField
        case .familyNameFirst: return givenNameTextField
        }
    }()

    private lazy var nameStackView: UIView = {
        let result = UIStackView(arrangedSubviews: [firstTextField, secondTextField])
        result.axis = .vertical
        result.distribution = .fillEqually
        return result
    }()

    private lazy var phoneNumberDisclosureView: PhoneNumberPrivacyLabel = {
        return PhoneNumberPrivacyLabel(phoneNumberDiscoverability: state.phoneNumberDiscoverability, onTap: { [weak self] in
            guard let self else { return }
            let vc = RegistrationPhoneNumberDiscoverabilityViewController(
                state: RegistrationPhoneNumberDiscoverabilityState(
                    e164: self.state.e164,
                    phoneNumberDiscoverability: self.state.phoneNumberDiscoverability
                ),
                presenter: self
            )
            self.presentFormSheet(OWSNavigationController(rootViewController: vc), animated: true)
        })
    }()

    public override func viewDidLoad() {
        super.viewDidLoad()
        initialRender()
    }

    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        if !UIDevice.current.isIPhone5OrShorter {
            // Small devices may obscure parts of the UI behind the keyboard, especially with larger
            // font sizes.
            firstTextField.becomeFirstResponder()
        }
    }

    public override func themeDidChange() {
        super.themeDidChange()
        render()
    }

    private func initialRender() {
        navigationItem.setHidesBackButton(true, animated: false)

        let scrollView = UIScrollView()
        view.addSubview(scrollView)
        scrollView.autoPinWidthToSuperviewMargins()
        scrollView.autoPinEdge(toSuperviewEdge: .top)
        scrollView.autoPinEdge(.bottom, to: .bottom, of: keyboardLayoutGuideViewSafeArea)

        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.distribution = .fill
        stackView.spacing = 24
        stackView.setCustomSpacing(12, after: titleLabel)
        scrollView.addSubview(stackView)
        stackView.autoPinWidth(toWidthOf: scrollView)
        stackView.autoPinHeightToSuperview()

        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(explanationView)
        stackView.addArrangedSubview(avatarContainerView)
        stackView.addArrangedSubview(nameStackView)
        stackView.addArrangedSubview(phoneNumberDisclosureView)
        stackView.addArrangedSubview(UIView.vStretchingSpacer())

        scrollView.addSubview(cameraImageWrapperView)
        cameraImageWrapperView.autoPinEdge(.bottom, to: .bottom, of: avatarView)
        cameraImageWrapperView.autoPinEdge(.trailing, to: .trailing, of: avatarView)

        firstTextField.returnKeyType = .next
        secondTextField.returnKeyType = .done

        render()
    }

    private func render() {
        navigationItem.rightBarButtonItem = givenNameComponent != nil ? nextBarButton : nil

        avatarView.image = avatarData?.asImage ?? Self.databaseStorage.read { transaction in
            Self.avatarBuilder.defaultAvatarImageForLocalUser(
                diameterPoints: UInt(avatarSize),
                transaction: transaction
            )
        }

        view.backgroundColor = Theme.backgroundColor
        nextBarButton.tintColor = Theme.accentBlueColor
        titleLabel.textColor = .colorForRegistrationTitleLabel
        explanationView.textColor = .colorForRegistrationExplanationLabel
        explanationView.linkTextAttributes = [
            .foregroundColor: Theme.accentBlueColor,
            .underlineColor: UIColor.clear
        ]
        cameraImageView.tintColor = Theme.secondaryTextAndIconColor
        cameraImageWrapperView.backgroundColor = Theme.backgroundColor
        [givenNameTextField, familyNameTextField].forEach { $0.textColor = Theme.primaryTextColor }
        textFieldStrokes.forEach { $0.backgroundColor = Theme.cellSeparatorColor }

        phoneNumberDisclosureView.render()
    }

    // MARK: Events

    @objc
    private func didTextFieldChange() {
        render()
    }

    @objc
    private func didTapAvatar() {
        Logger.info("")

        let vc = AvatarSettingsViewController(
            context: .profile,
            currentAvatarImage: avatarData?.asImage
        ) { [weak self] newAvatarImage in
            guard let self else { return }
            self.avatarData = newAvatarImage?.asData
        }
        presentFormSheet(OWSNavigationController(rootViewController: vc), animated: true)
    }

    @objc
    private func didTapNext() {
        Logger.info("")
        goToNextStepIfPossible()
    }

    private func goToNextStepIfPossible() {
        Logger.info("")

        guard let givenNameComponent else {
            // This can happen if you try to advance via the keyboard.
            return
        }

        presenter?.goToNextStep(
            givenName: givenNameComponent,
            familyName: familyNameComponent,
            avatarData: avatarData,
            phoneNumberDiscoverability: state.phoneNumberDiscoverability
        )
    }
}

// MARK: - UITextViewDelegate

extension RegistrationProfileViewController: UITextViewDelegate {
    func textView(
        _ textView: UITextView,
        shouldInteractWith URL: URL,
        in characterRange: NSRange,
        interaction: UITextItemInteraction
    ) -> Bool {
        if textView == explanationView {
            showLearnMoreUi()
        }
        return false
    }

    private func showLearnMoreUi() {
        let actionSheet = ActionSheetController(
            title: OWSLocalizedString(
                "REGISTRATION_PROFILE_SETUP_MORE_INFO_TITLE",
                comment: "During registration, users set up their profile. They can learn more about the privacy of their profile by clicking a \"learn more\" button. This is the title on a sheet that appears when they do that."
            ),
            message: OWSLocalizedString(
                "REGISTRATION_PROFILE_SETUP_MORE_INFO_DETAILS",
                comment: "During registration, users set up their profile. They can learn more about the privacy of their profile by clicking a \"learn more\" button. This is the message on a sheet that appears when they do that."
            )
        )

        actionSheet.addAction(.init(title: CommonStrings.learnMore) { [weak self] _ in
            guard let self else { return }
            self.present(SFSafariViewController(url: self.profilesFAQURL), animated: true)
        })

        actionSheet.addAction(.init(title: CommonStrings.okayButton, style: .cancel))

        presentActionSheet(actionSheet)
    }
}

// MARK: - UITextFieldDelegate

extension RegistrationProfileViewController: UITextFieldDelegate {
    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        switch textField {
        case firstTextField:
            secondTextField.becomeFirstResponder()
        case secondTextField:
            goToNextStepIfPossible()
        default:
            owsFailBeta("Got a \"return\" event for an unexpected text field")
        }
        return false
    }
}

// MARK: - RegistrationPhoneNumberDiscoverabilityPresenter

extension RegistrationProfileViewController: RegistrationPhoneNumberDiscoverabilityPresenter {

    var presentedAsModal: Bool { return true }

    func setPhoneNumberDiscoverability(_ phoneNumberDiscoverability: PhoneNumberDiscoverability) {
        phoneNumberDisclosureView.phoneNumberDiscoverability = phoneNumberDiscoverability
        self.state = RegistrationProfileState(
            e164: self.state.e164,
            phoneNumberDiscoverability: phoneNumberDiscoverability
        )
        self.presentedViewController?.dismiss(animated: true)
    }
}

// MARK: - PhoneNumberPrivacyLabel

extension RegistrationProfileViewController {
    private class PhoneNumberPrivacyLabel: UIView {

        private enum Constants {
            static let iconSize: CGFloat = 24.0
            static let verticalSpacing: CGFloat = 0.0
            static let horizontalSpacing: CGFloat = 12.0
            static let layoutInsets: UIEdgeInsets = UIEdgeInsets(
                top: 8,
                leading: 0,
                bottom: 8,
                trailing: 0
            )
        }

        var phoneNumberDiscoverability: PhoneNumberDiscoverability {
            didSet { render() }
        }
        private var onTap: (() -> Void)?

        // MARK: Init

        init(phoneNumberDiscoverability: PhoneNumberDiscoverability, onTap: (() -> Void)?) {
            self.phoneNumberDiscoverability = phoneNumberDiscoverability
            self.onTap = onTap
            super.init(frame: .zero)
            initialRender()
        }

        @available(*, unavailable, message: "Use other constructor")
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        // MARK: Views

        private lazy var button: OWSFlatButton = {
            return OWSFlatButton()
        }()

        private lazy var iconView: UIImageView = {
            let iconView = UIImageView()
            iconView.contentMode = .scaleAspectFit
            return iconView
        }()

        private lazy var titleLabel: UILabel = {
            let titleLabel = UILabel()
            titleLabel.numberOfLines = 0
            titleLabel.lineBreakMode = .byWordWrapping
            return titleLabel
        }()

        private lazy var subTitleLabel: UILabel = {
            let subTitleLabel = UILabel()
            subTitleLabel.numberOfLines = 0
            subTitleLabel.lineBreakMode = .byWordWrapping
            return subTitleLabel
        }()

        private lazy var disclosureView: UIImageView = {
            let disclosureView = UIImageView()
            disclosureView.contentMode = .scaleAspectFit
            return disclosureView
        }()

        // MARK: Layout

        private func initialRender() {

            addSubview(button)
            button.autoPinEdgesToSuperviewEdges()

            let iconContainer = UIView()
            iconContainer.addSubview(iconView)

            iconView.autoPinWidthToSuperview()
            iconView.autoSetDimensions(to: CGSize(square: Constants.iconSize))
            iconView.autoVCenterInSuperview()
            iconView.autoMatch(
                .height,
                to: .height,
                of: iconContainer,
                withOffset: 0,
                relation: .lessThanOrEqual)

            let topSpacer = UIView.vStretchingSpacer()
            let bottomSpacer = UIView.vStretchingSpacer()

            let vStack = UIStackView(arrangedSubviews: [
                topSpacer,
                titleLabel,
                subTitleLabel,
                bottomSpacer
            ])
            vStack.axis = .vertical
            vStack.spacing = Constants.verticalSpacing
            topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)

            let disclosureContainer = UIView()
            disclosureContainer.addSubview(disclosureView)

            disclosureView.autoPinEdgesToSuperviewEdges()
            disclosureView.autoSetDimension(.width, toSize: Constants.iconSize)

            let hStack = UIStackView(arrangedSubviews: [
                iconContainer,
                vStack,
                disclosureContainer
            ])

            hStack.axis = .horizontal
            hStack.spacing = Constants.horizontalSpacing
            hStack.isLayoutMarginsRelativeArrangement = true
            hStack.layoutMargins = Constants.layoutInsets
            hStack.isUserInteractionEnabled = false

            button.addSubview(hStack)
            hStack.autoPinEdgesToSuperviewEdges()
            button.addTarget(target: self, selector: #selector(disclosureButtonTapped))

            render()
        }

        public func render() {
            button.setBackgroundColors(upColor: Theme.backgroundColor)

            let labelIconName: String = {
                switch phoneNumberDiscoverability {
                case .everybody:
                    return "group"
                case .nobody:
                    return "lock"
                }
            }()

            titleLabel.text = OWSLocalizedString(
                "REGISTRATION_PROFILE_SETUP_FIND_MY_NUMBER_TITLE",
                comment: "During registration, users can choose who can see their phone number.")

            subTitleLabel.text = phoneNumberDiscoverability.nameForDiscoverability

            iconView.setTemplateImageName(labelIconName, tintColor: Theme.primaryIconColor)

            titleLabel.font = UIFont.dynamicTypeBodyClamped
            titleLabel.textColor = Theme.primaryTextColor

            subTitleLabel.font = UIFont.dynamicTypeCaption1Clamped
            subTitleLabel.textColor = Theme.secondaryTextAndIconColor

            disclosureView.setTemplateImage(
                UIImage(imageLiteralResourceName: "chevron-right-20"),
                tintColor: Theme.secondaryTextAndIconColor
            )
        }

        // MARK: Actions

        @objc
        func disclosureButtonTapped() {
            onTap?()
            render()
        }
    }
}

// MARK: - Data <-> UIImage conversions

private extension Data {
    var asImage: UIImage? { .init(data: self) }
}

private extension UIImage {
    var asData: Data { OWSProfileManager.avatarData(forAvatarImage: self) }
}
