// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.

import UIKit
import GRDB
import DifferenceKit
import SessionUIKit
import SessionUtilitiesKit

public class SessionCell: UITableViewCell {
    public static let cornerRadius: CGFloat = 17
    
    public enum Style {
        case rounded
        case roundedEdgeToEdge
        case edgeToEdge
    }
    
    /// This value is here to allow the theming update callback to be released when preparing for reuse
    private var instanceView: UIView = UIView()
    private var position: Position?
    private var subtitleExtraView: UIView?
    private var onExtraActionTap: (() -> Void)?
    
    // MARK: - UI
    
    private var backgroundLeftConstraint: NSLayoutConstraint = NSLayoutConstraint()
    private var backgroundRightConstraint: NSLayoutConstraint = NSLayoutConstraint()
    private var topSeparatorLeftConstraint: NSLayoutConstraint = NSLayoutConstraint()
    private var topSeparatorRightConstraint: NSLayoutConstraint = NSLayoutConstraint()
    private var botSeparatorLeftConstraint: NSLayoutConstraint = NSLayoutConstraint()
    private var botSeparatorRightConstraint: NSLayoutConstraint = NSLayoutConstraint()
    private lazy var leftAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: leftAccessoryView)
    private lazy var rightAccessoryFillConstraint: NSLayoutConstraint = contentStackView.set(.height, to: .height, of: rightAccessoryView)
    
    private let cellBackgroundView: UIView = {
        let result: UIView = UIView()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.clipsToBounds = true
        result.themeBackgroundColor = .settings_tabBackground
        
        return result
    }()
    
    private let cellSelectedBackgroundView: UIView = {
        let result: UIView = UIView()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.themeBackgroundColor = .highlighted(.settings_tabBackground)
        result.alpha = 0
        
        return result
    }()
    
    private let topSeparator: UIView = {
        let result: UIView = UIView.separator()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.isHidden = true
        
        return result
    }()
    
    private let contentStackView: UIStackView = {
        let result: UIStackView = UIStackView()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.axis = .horizontal
        result.distribution = .fill
        result.alignment = .center
        result.spacing = Values.mediumSpacing
        result.isLayoutMarginsRelativeArrangement = true
        
        return result
    }()
    
    public let leftAccessoryView: AccessoryView = {
        let result: AccessoryView = AccessoryView()
        result.isHidden = true
        
        return result
    }()
    
    private let titleStackView: UIStackView = {
        let result: UIStackView = UIStackView()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.axis = .vertical
        result.distribution = .equalSpacing
        result.alignment = .fill
        result.setCompressionResistanceHorizontalLow()
        result.setContentHuggingLow()
        
        return result
    }()
    
    private let titleLabel: UILabel = {
        let result: UILabel = UILabel()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.font = .boldSystemFont(ofSize: 15)
        result.themeTextColor = .textPrimary
        result.numberOfLines = 0
        result.setCompressionResistanceHorizontalLow()
        result.setContentHuggingLow()
        
        return result
    }()
    
    private let subtitleLabel: UILabel = {
        let result: UILabel = UILabel()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.font = .systemFont(ofSize: 13)
        result.themeTextColor = .textPrimary
        result.numberOfLines = 0
        result.isHidden = true
        result.setCompressionResistanceHorizontalLow()
        result.setContentHuggingLow()
        
        return result
    }()
    
    private lazy var extraActionTopSpacingView: UIView = UIView.spacer(withHeight: Values.smallSpacing)
    
    private lazy var extraActionButton: UIButton = {
        let result: UIButton = UIButton()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.titleLabel?.font = .boldSystemFont(ofSize: Values.smallFontSize)
        result.titleLabel?.numberOfLines = 0
        result.contentHorizontalAlignment = .left
        result.contentEdgeInsets = UIEdgeInsets(
            top: 8,
            left: 0,
            bottom: 0,
            right: 0
        )
        result.addTarget(self, action: #selector(extraActionTapped), for: .touchUpInside)
        result.isHidden = true
        
        ThemeManager.onThemeChange(observer: result) { [weak result] theme, _ in
            switch theme.interfaceStyle {
                case .light: result?.setThemeTitleColor(.textPrimary, for: .normal)
                default: result?.setThemeTitleColor(.primary, for: .normal)
            }
        }
        
        return result
    }()
    
    public let rightAccessoryView: AccessoryView = {
        let result: AccessoryView = AccessoryView()
        result.isHidden = true
        
        return result
    }()
    
    private let botSeparator: UIView = {
        let result: UIView = UIView.separator()
        result.translatesAutoresizingMaskIntoConstraints = false
        result.isHidden = true
        
        return result
    }()
    
    // MARK: - Initialization
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        setupViewHierarchy()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        setupViewHierarchy()
    }

    private func setupViewHierarchy() {
        self.themeBackgroundColor = .clear
        self.selectedBackgroundView = UIView()
        
        contentView.addSubview(cellBackgroundView)
        cellBackgroundView.addSubview(cellSelectedBackgroundView)
        cellBackgroundView.addSubview(topSeparator)
        cellBackgroundView.addSubview(contentStackView)
        cellBackgroundView.addSubview(botSeparator)
        
        contentStackView.addArrangedSubview(leftAccessoryView)
        contentStackView.addArrangedSubview(titleStackView)
        contentStackView.addArrangedSubview(rightAccessoryView)
        
        titleStackView.addArrangedSubview(titleLabel)
        titleStackView.addArrangedSubview(subtitleLabel)
        titleStackView.addArrangedSubview(extraActionTopSpacingView)
        titleStackView.addArrangedSubview(extraActionButton)
        
        setupLayout()
    }
    
    private func setupLayout() {
        cellBackgroundView.pin(.top, to: .top, of: contentView)
        backgroundLeftConstraint = cellBackgroundView.pin(.leading, to: .leading, of: contentView)
        backgroundRightConstraint = cellBackgroundView.pin(.trailing, to: .trailing, of: contentView)
        cellBackgroundView.pin(.bottom, to: .bottom, of: contentView)
        
        cellSelectedBackgroundView.pin(to: cellBackgroundView)
        
        topSeparator.pin(.top, to: .top, of: cellBackgroundView)
        topSeparatorLeftConstraint = topSeparator.pin(.left, to: .left, of: cellBackgroundView)
        topSeparatorRightConstraint = topSeparator.pin(.right, to: .right, of: cellBackgroundView)
        
        contentStackView.pin(to: cellBackgroundView)
        
        botSeparatorLeftConstraint = botSeparator.pin(.left, to: .left, of: cellBackgroundView)
        botSeparatorRightConstraint = botSeparator.pin(.right, to: .right, of: cellBackgroundView)
        botSeparator.pin(.bottom, to: .bottom, of: cellBackgroundView)
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        
        // Need to force the contentStackView to layout if needed as it might not have updated it's
        // sizing yet
        self.contentStackView.layoutIfNeeded()
        
        // Position the 'subtitleExtraView' at the end of the last line of text
        if
            let subtitleExtraView: UIView = self.subtitleExtraView,
            let subtitle: String = subtitleLabel.text,
            let font: UIFont = subtitleLabel.font
        {
            let layoutManager: NSLayoutManager = NSLayoutManager()
            let textStorage = NSTextStorage(
                attributedString: NSAttributedString(
                    string: subtitle,
                    attributes: [ .font: font ]
                )
            )
            textStorage.addLayoutManager(layoutManager)
            
            let textContainer: NSTextContainer = NSTextContainer(
                size: CGSize(
                    width: subtitleLabel.bounds.size.width,
                    height: 999
                )
            )
            textContainer.lineFragmentPadding = 0
            layoutManager.addTextContainer(textContainer)
            
            var glyphRange: NSRange = NSRange()
            layoutManager.characterRange(
                forGlyphRange: NSRange(location: subtitle.glyphCount - 1, length: 1),
                actualGlyphRange: &glyphRange
            )
            let lastGlyphRect: CGRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
            
            // Remove and re-add the 'subtitleExtraView' to clear any old constraints
            subtitleExtraView.removeFromSuperview()
            contentView.addSubview(subtitleExtraView)
            
            subtitleExtraView.pin(
                .top,
                to: .top,
                of: subtitleLabel,
                withInset: (lastGlyphRect.minY + ((lastGlyphRect.height / 2) - (subtitleExtraView.bounds.height / 2)))
            )
            subtitleExtraView.pin(
                .leading,
                to: .leading,
                of: subtitleLabel,
                withInset: lastGlyphRect.maxX
            )
        }
    }
    
    // MARK: - Content
    
    public override func prepareForReuse() {
        super.prepareForReuse()
        
        self.instanceView = UIView()
        self.position = nil
        self.onExtraActionTap = nil
        self.accessibilityIdentifier = nil
        
        leftAccessoryView.prepareForReuse()
        leftAccessoryFillConstraint.isActive = false
        titleLabel.text = ""
        titleLabel.themeTextColor = .textPrimary
        subtitleLabel.text = ""
        subtitleLabel.themeTextColor = .textPrimary
        rightAccessoryView.prepareForReuse()
        rightAccessoryFillConstraint.isActive = false
        
        topSeparator.isHidden = true
        subtitleLabel.isHidden = true
        extraActionTopSpacingView.isHidden = true
        extraActionButton.setTitle("", for: .normal)
        extraActionButton.isHidden = true
        botSeparator.isHidden = true
        
        subtitleExtraView?.removeFromSuperview()
        subtitleExtraView = nil
    }
    
    public func update<ID: Hashable & Differentiable>(
        with info: Info<ID>,
        style: Style,
        position: Position
    ) {
        self.instanceView = UIView()
        self.position = position
        self.subtitleExtraView = info.subtitleExtraViewGenerator?()
        self.onExtraActionTap = info.extraAction?.onTap
        self.accessibilityIdentifier = info.accessibilityIdentifier
        self.accessibilityLabel = info.accessibilityLabel
        self.isAccessibilityElement = true
        
        let leftFitToEdge: Bool = (info.leftAccessory?.shouldFitToEdge == true)
        let rightFitToEdge: Bool = (!leftFitToEdge && info.rightAccessory?.shouldFitToEdge == true)
        leftAccessoryFillConstraint.isActive = leftFitToEdge
        leftAccessoryView.update(
            with: info.leftAccessory,
            tintColor: info.tintColor,
            isEnabled: info.isEnabled,
            accessibilityLabel: info.leftAccessoryAccessibilityLabel
        )
        rightAccessoryView.update(
            with: info.rightAccessory,
            tintColor: info.tintColor,
            isEnabled: info.isEnabled,
            accessibilityLabel: info.rightAccessoryAccessibilityLabel
        )
        rightAccessoryFillConstraint.isActive = rightFitToEdge
        contentStackView.layoutMargins = UIEdgeInsets(
            top: (leftFitToEdge || rightFitToEdge ? 0 : Values.mediumSpacing),
            left: (leftFitToEdge ? 0 : Values.largeSpacing),
            bottom: (leftFitToEdge || rightFitToEdge ? 0 : Values.mediumSpacing),
            right: (rightFitToEdge ? 0 : Values.largeSpacing)
        )
        
        titleLabel.text = info.title
        titleLabel.themeTextColor = info.tintColor
        subtitleLabel.text = info.subtitle
        subtitleLabel.themeTextColor = info.tintColor
        subtitleLabel.isHidden = (info.subtitle == nil)
        extraActionTopSpacingView.isHidden = (info.extraAction == nil)
        extraActionButton.setTitle(info.extraAction?.title, for: .normal)
        extraActionButton.isHidden = (info.extraAction == nil)
        
        // Styling and positioning
        let defaultEdgePadding: CGFloat
        cellBackgroundView.themeBackgroundColor = (info.shouldHaveBackground ?
            .settings_tabBackground :
            nil
        )
        cellSelectedBackgroundView.isHidden = (!info.isEnabled || !info.shouldHaveBackground)
        
        switch style {
            case .rounded:
                defaultEdgePadding = Values.mediumSpacing
                backgroundLeftConstraint.constant = Values.largeSpacing
                backgroundRightConstraint.constant = -Values.largeSpacing
                cellBackgroundView.layer.cornerRadius = SessionCell.cornerRadius
                
            case .edgeToEdge:
                defaultEdgePadding = 0
                backgroundLeftConstraint.constant = 0
                backgroundRightConstraint.constant = 0
                cellBackgroundView.layer.cornerRadius = 0
                
            case .roundedEdgeToEdge:
                defaultEdgePadding = Values.mediumSpacing
                backgroundLeftConstraint.constant = 0
                backgroundRightConstraint.constant = 0
                cellBackgroundView.layer.cornerRadius = SessionCell.cornerRadius
        }
        
        let fittedEdgePadding: CGFloat = {
            func targetSize(accessory: Accessory?) -> CGFloat {
                switch accessory {
                    case .icon(_, let iconSize, _, _), .iconAsync(let iconSize, _, _, _):
                        return iconSize.size
                        
                    default: return defaultEdgePadding
                }
            }
            
            guard leftFitToEdge else {
                guard rightFitToEdge else { return defaultEdgePadding }
                
                return targetSize(accessory: info.rightAccessory)
            }
            
            return targetSize(accessory: info.leftAccessory)
        }()
        topSeparatorLeftConstraint.constant = (leftFitToEdge ? fittedEdgePadding : defaultEdgePadding)
        topSeparatorRightConstraint.constant = (rightFitToEdge ? -fittedEdgePadding : -defaultEdgePadding)
        botSeparatorLeftConstraint.constant = (leftFitToEdge ? fittedEdgePadding : defaultEdgePadding)
        botSeparatorRightConstraint.constant = (rightFitToEdge ? -fittedEdgePadding : -defaultEdgePadding)
        
        switch position {
            case .top:
                cellBackgroundView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
                topSeparator.isHidden = (style != .edgeToEdge)
                botSeparator.isHidden = false
                
            case .middle:
                cellBackgroundView.layer.maskedCorners = []
                topSeparator.isHidden = true
                botSeparator.isHidden = false
                
            case .bottom:
                cellBackgroundView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
                topSeparator.isHidden = false
                botSeparator.isHidden = (style != .edgeToEdge)
                
            case .individual:
                cellBackgroundView.layer.maskedCorners = [
                    .layerMinXMinYCorner, .layerMaxXMinYCorner,
                    .layerMinXMaxYCorner, .layerMaxXMaxYCorner
                ]
                topSeparator.isHidden = (style != .edgeToEdge)
                botSeparator.isHidden = (style != .edgeToEdge)
        }
    }
    
    public func update(isEditing: Bool, animated: Bool) {}
    
    // MARK: - Interaction
    
    public override func setHighlighted(_ highlighted: Bool, animated: Bool) {
        super.setHighlighted(highlighted, animated: animated)
        
        // If the 'cellSelectedBackgroundView' is hidden then there is no background so we
        // should update the titleLabel to indicate the highlighted state
        if cellSelectedBackgroundView.isHidden {
            titleLabel.alpha = (highlighted ? 0.8 : 1)
        }

        cellSelectedBackgroundView.alpha = (highlighted ? 1 : 0)
        leftAccessoryView.setHighlighted(highlighted, animated: animated)
        rightAccessoryView.setHighlighted(highlighted, animated: animated)
    }
    
    public override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        leftAccessoryView.setSelected(selected, animated: animated)
        rightAccessoryView.setSelected(selected, animated: animated)
    }
    
    @objc private func extraActionTapped() {
        onExtraActionTap?()
    }
}