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

import UIKit
import SessionUtilitiesKit

public extension UIContextualAction {
    private static var lookupMap: Atomic<[Int: [String: [Int: ThemeValue]]]> = Atomic([:])
    
    enum Side: Int {
        case leading
        case trailing
        
        func key(for indexPath: IndexPath) -> String {
            return "\(indexPath.section)-\(indexPath.row)-\(rawValue)"
        }
        
        init?(for view: UIView) {
            guard view.frame.minX == 0 else {
                self = .trailing
                return
            }
            
            self = .leading
        }
    }
    
    convenience init(
        title: String? = nil,
        icon: UIImage? = nil,
        iconHeight: CGFloat = Values.mediumFontSize,
        themeTintColor: ThemeValue = .white,
        themeBackgroundColor: ThemeValue,
        side: Side,
        actionIndex: Int,
        indexPath: IndexPath,
        tableView: UITableView,
        handler: @escaping UIContextualAction.Handler
    ) {
        self.init(style: .normal, title: title, handler: handler)
        self.image = UIContextualAction
            .imageWith(
                title: title,
                icon: icon,
                iconHeight: iconHeight,
                themeTintColor: themeTintColor
            )?
            .withRenderingMode(.alwaysTemplate)
        self.themeBackgroundColor = themeBackgroundColor
        
        UIContextualAction.lookupMap.mutate {
            $0[tableView.hashValue] = ($0[tableView.hashValue] ?? [:])
                .setting(
                    side.key(for: indexPath),
                    (($0[tableView.hashValue] ?? [:])[side.key(for: indexPath)] ?? [:])
                        .setting(actionIndex, themeTintColor)
                )
        }
    }
    
    private static func imageWith(
        title: String?,
        icon: UIImage?,
        iconHeight: CGFloat,
        themeTintColor: ThemeValue
    ) -> UIImage? {
        let stackView: UIStackView = UIStackView()
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.spacing = 4
        
        if let icon: UIImage = icon {
            let aspectRatio: CGFloat = (icon.size.width / icon.size.height)
            let imageView: UIImageView = UIImageView(image: icon)
            imageView.frame = CGRect(x: 0, y: 0, width: (iconHeight * aspectRatio), height: iconHeight)
            imageView.contentMode = .scaleAspectFit
            imageView.themeTintColor = themeTintColor
            stackView.addArrangedSubview(imageView)
        }
        
        if let title: String = title {
            let label: UILabel = UILabel()
            label.font = .systemFont(ofSize: Values.smallFontSize)
            label.text = title
            label.textAlignment = .center
            label.themeTextColor = themeTintColor
            label.minimumScaleFactor = 0.75
            label.numberOfLines = (title.components(separatedBy: " ").count > 1 ? 2 : 1)
            label.frame = CGRect(
                origin: .zero,
                // Note: It looks like there is a semi-max width of 68px for images in the swipe actions
                // if the image ends up larger then there an odd behaviour can occur where 8/10 times the
                // image is scaled down to fit, but ocassionally (primarily if you hide the action and
                // immediately swipe to show it again once the cell hits the edge of the screen) the image
                // won't be scaled down but will be full size - appearing as if two different images are used
                size: label.sizeThatFits(CGSize(width: 68, height: 999))
            )
            label.set(.width, to: label.frame.width)
            
            stackView.addArrangedSubview(label)
        }
        
        stackView.frame = CGRect(
            origin: .zero,
            size: stackView.systemLayoutSizeFitting(CGSize(width: 999, height: 999))
        )
        
        // Based on https://stackoverflow.com/a/41288197/1118398
        let renderFormat: UIGraphicsImageRendererFormat = UIGraphicsImageRendererFormat()
        renderFormat.scale = UIScreen.main.scale
        
        let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(
            size: stackView.bounds.size,
            format: renderFormat
        )
        return renderer.image { rendererContext in
            stackView.layer.render(in: rendererContext.cgContext)
        }
    }
    
    private static func firstSubviewOfType<T>(in superview: UIView) -> T? {
        guard !(superview is T) else { return superview as? T }
        guard !superview.subviews.isEmpty else { return nil }
        
        for subview in superview.subviews {
            if let result: T = firstSubviewOfType(in: subview) {
                return result
            }
        }
        
        return nil
    }
    
    static func willBeginEditing(indexPath: IndexPath, tableView: UITableView) {
        guard
            let targetCell: UITableViewCell = tableView.cellForRow(at: indexPath),
            targetCell.superview != tableView,
            let targetSuperview: UIView = targetCell.superview?
                .subviews
                .filter({ $0 != targetCell })
                .first,
            let side: Side = Side(for: targetSuperview),
            let themeMap: [Int: ThemeValue] = UIContextualAction.lookupMap.wrappedValue
                .getting(tableView.hashValue)?
                .getting(side.key(for: indexPath)),
            targetSuperview.subviews.count == themeMap.count
        else { return }
        
        let targetViews: [UIImageView] = targetSuperview.subviews
            .compactMap { subview in firstSubviewOfType(in: subview) }
        
        guard targetViews.count == themeMap.count else { return }
        
        // Set the imageView and background colours (so they change correctly when the theme changes)
        targetViews.enumerated().forEach { index, targetView in
            guard let themeTintColor: ThemeValue = themeMap[index] else { return }
            
            targetView.themeTintColor = themeTintColor
        }
    }
    
    static func didEndEditing(indexPath: IndexPath?, tableView: UITableView) {
        guard let indexPath: IndexPath = indexPath else { return }
        
        let leadingKey: String = Side.leading.key(for: indexPath)
        let trailingKey: String = Side.trailing.key(for: indexPath)
        
        guard
            UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[leadingKey] != nil ||
            UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[trailingKey] != nil
        else { return }
        
        UIContextualAction.lookupMap.mutate {
            $0[tableView.hashValue]?[leadingKey] = nil
            $0[tableView.hashValue]?[trailingKey] = nil
            
            if $0[tableView.hashValue]?.isEmpty == true {
                $0[tableView.hashValue] = nil
            }
        }
    }
}
