import AppKit

final class PaddedToggleButton: NSButton {
    var contentPadding = NSEdgeInsets(top: 4, left: 7, bottom: 4, right: 7) {
        didSet {
            if oldValue.top != self.contentPadding.top ||
                oldValue.left != self.contentPadding.left ||
                oldValue.bottom != self.contentPadding.bottom ||
                oldValue.right != self.contentPadding.right
            {
                self.invalidateIntrinsicContentSize()
            }
        }
    }

    override var intrinsicContentSize: NSSize {
        let size = super.intrinsicContentSize
        return NSSize(
            width: size.width + self.contentPadding.left + self.contentPadding.right,
            height: size.height + self.contentPadding.top + self.contentPadding.bottom)
    }
}

final class InlineIconToggleButton: NSButton {
    private let iconView = NSImageView()
    private let titleField = NSTextField(labelWithString: "")
    private let stack = NSStackView()
    private var paddingConstraints: [NSLayoutConstraint] = []
    private var iconSizeConstraints: [NSLayoutConstraint] = []
    private var isConfiguring = false // Batch invalidation during setup

    var contentPadding = NSEdgeInsets(top: 4, left: 7, bottom: 4, right: 7) {
        didSet {
            self.paddingConstraints.first { $0.firstAttribute == .top }?.constant = self.contentPadding.top
            self.paddingConstraints.first { $0.firstAttribute == .leading }?.constant = self.contentPadding.left
            self.paddingConstraints.first { $0.firstAttribute == .trailing }?.constant = -self.contentPadding.right
            self.paddingConstraints.first { $0.firstAttribute == .bottom }?.constant = -self.contentPadding.bottom
            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }
        }
    }

    override var title: String {
        get { "" }
        set {
            super.title = ""
            super.alternateTitle = ""
            super.attributedTitle = NSAttributedString(string: "")
            super.attributedAlternateTitle = NSAttributedString(string: "")
            self.titleField.stringValue = newValue
            self.setAccessibilityLabel(newValue)
            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }
        }
    }

    override var image: NSImage? {
        get { nil }
        set {
            super.image = nil
            super.alternateImage = nil
            self.iconView.image = newValue
            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }
        }
    }

    func setContentTintColor(_ color: NSColor?) {
        self.iconView.contentTintColor = color
        self.titleField.textColor = color
    }

    func setTitleFontSize(_ size: CGFloat) {
        self.titleField.font = NSFont.systemFont(ofSize: size)
    }

    func setAllowsTwoLineTitle(_ allow: Bool) {
        let hasWhitespace = self.titleField.stringValue.rangeOfCharacter(from: .whitespacesAndNewlines) != nil
        let shouldWrap = allow && hasWhitespace
        self.titleField.maximumNumberOfLines = shouldWrap ? 2 : 1
        self.titleField.usesSingleLineMode = !shouldWrap
        self.titleField.lineBreakMode = shouldWrap ? .byWordWrapping : .byTruncatingTail
    }

    override var intrinsicContentSize: NSSize {
        let size = self.stack.fittingSize
        return NSSize(
            width: size.width + self.contentPadding.left + self.contentPadding.right,
            height: size.height + self.contentPadding.top + self.contentPadding.bottom)
    }

    init(title: String, image: NSImage, target: AnyObject?, action: Selector?) {
        super.init(frame: .zero)
        self.target = target
        self.action = action
        self.isConfiguring = true // Batch invalidations during setup
        self.configure()
        self.title = title
        self.image = image
        self.isConfiguring = false
        self.invalidateIntrinsicContentSize() // Single invalidation after setup
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        nil
    }

    private func configure() {
        self.bezelStyle = .regularSquare
        self.isBordered = false
        self.setButtonType(.toggle)
        self.controlSize = .small
        self.wantsLayer = true
        self.setAccessibilityRole(.button)

        self.iconView.imageScaling = .scaleNone
        self.iconView.translatesAutoresizingMaskIntoConstraints = false
        self.titleField.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
        self.titleField.alignment = .left
        self.titleField.lineBreakMode = .byTruncatingTail
        self.titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        self.titleField.setContentHuggingPriority(.defaultLow, for: .horizontal)
        self.setContentTintColor(NSColor.secondaryLabelColor)

        self.stack.orientation = .horizontal
        self.stack.alignment = .centerY
        self.stack.spacing = 1
        self.stack.translatesAutoresizingMaskIntoConstraints = false
        self.stack.addArrangedSubview(self.iconView)
        self.stack.addArrangedSubview(self.titleField)
        self.addSubview(self.stack)

        let iconWidth = self.iconView.widthAnchor.constraint(equalToConstant: 16)
        let iconHeight = self.iconView.heightAnchor.constraint(equalToConstant: 16)
        self.iconSizeConstraints = [iconWidth, iconHeight]

        let top = self.stack.topAnchor.constraint(
            greaterThanOrEqualTo: self.topAnchor,
            constant: self.contentPadding.top)
        let leading = self.stack.leadingAnchor.constraint(
            greaterThanOrEqualTo: self.leadingAnchor,
            constant: self.contentPadding.left)
        let trailing = self.stack.trailingAnchor.constraint(
            lessThanOrEqualTo: self.trailingAnchor,
            constant: -self.contentPadding.right)
        let centerX = self.stack.centerXAnchor.constraint(equalTo: self.centerXAnchor)
        centerX.priority = .defaultHigh
        let centerY = self.stack.centerYAnchor.constraint(equalTo: self.centerYAnchor)
        let bottom = self.stack.bottomAnchor.constraint(
            lessThanOrEqualTo: self.bottomAnchor,
            constant: -self.contentPadding.bottom)
        self.paddingConstraints = [top, leading, trailing, bottom, centerX, centerY]

        NSLayoutConstraint.activate(self.paddingConstraints + self.iconSizeConstraints)
    }
}

final class StackedToggleButton: NSButton {
    private let iconView = NSImageView()
    private let titleField = NSTextField(labelWithString: "")
    private let stack = NSStackView()
    private var paddingConstraints: [NSLayoutConstraint] = []
    private var iconSizeConstraints: [NSLayoutConstraint] = []
    private var isConfiguring = false // Batch invalidation during setup

    var contentPadding = NSEdgeInsets(top: 2, left: 4, bottom: 2, right: 4) {
        didSet {
            self.paddingConstraints.first { $0.firstAttribute == .top }?.constant = self.contentPadding.top
            self.paddingConstraints.first { $0.firstAttribute == .leading }?.constant = self.contentPadding.left
            self.paddingConstraints.first { $0.firstAttribute == .trailing }?.constant = -self.contentPadding.right
            self.paddingConstraints.first { $0.firstAttribute == .bottom }?.constant = -self.contentPadding.bottom
            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }
        }
    }

    override var title: String {
        get { "" }
        set {
            super.title = ""
            super.alternateTitle = ""
            super.attributedTitle = NSAttributedString(string: "")
            super.attributedAlternateTitle = NSAttributedString(string: "")
            self.titleField.stringValue = newValue
            self.setAccessibilityLabel(newValue)
            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }
        }
    }

    override var image: NSImage? {
        get { nil }
        set {
            super.image = nil
            super.alternateImage = nil
            self.iconView.image = newValue
            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }
        }
    }

    func setContentTintColor(_ color: NSColor?) {
        self.iconView.contentTintColor = color
        self.titleField.textColor = color
    }

    func setTitleFontSize(_ size: CGFloat) {
        self.titleField.font = NSFont.systemFont(ofSize: size)
    }

    func setAllowsTwoLineTitle(_ allow: Bool) {
        let hasWhitespace = self.titleField.stringValue.rangeOfCharacter(from: .whitespacesAndNewlines) != nil
        let shouldWrap = allow && hasWhitespace
        self.titleField.maximumNumberOfLines = shouldWrap ? 2 : 1
        self.titleField.usesSingleLineMode = !shouldWrap
        self.titleField.lineBreakMode = shouldWrap ? .byWordWrapping : .byTruncatingTail
    }

    override var intrinsicContentSize: NSSize {
        let size = self.stack.fittingSize
        return NSSize(
            width: size.width + self.contentPadding.left + self.contentPadding.right,
            height: size.height + self.contentPadding.top + self.contentPadding.bottom)
    }

    init(title: String, image: NSImage, target: AnyObject?, action: Selector?) {
        super.init(frame: .zero)
        self.target = target
        self.action = action
        self.isConfiguring = true // Batch invalidations during setup
        self.configure()
        self.title = title
        self.image = image
        self.isConfiguring = false
        self.invalidateIntrinsicContentSize() // Single invalidation after setup
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        nil
    }

    private func configure() {
        self.bezelStyle = .regularSquare
        self.isBordered = false
        self.setButtonType(.toggle)
        self.controlSize = .small
        self.wantsLayer = true
        self.setAccessibilityRole(.button)

        self.iconView.imageScaling = .scaleNone
        self.iconView.translatesAutoresizingMaskIntoConstraints = false
        self.titleField.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize - 2)
        self.titleField.alignment = .center
        self.titleField.lineBreakMode = .byTruncatingTail
        self.titleField.maximumNumberOfLines = 1
        self.titleField.usesSingleLineMode = true
        self.setContentTintColor(NSColor.secondaryLabelColor)

        self.stack.orientation = .vertical
        self.stack.alignment = .centerX
        self.stack.spacing = 0
        self.stack.translatesAutoresizingMaskIntoConstraints = false
        self.stack.addArrangedSubview(self.iconView)
        self.stack.addArrangedSubview(self.titleField)
        self.addSubview(self.stack)

        let iconWidth = self.iconView.widthAnchor.constraint(equalToConstant: 16)
        let iconHeight = self.iconView.heightAnchor.constraint(equalToConstant: 16)
        self.iconSizeConstraints = [iconWidth, iconHeight]

        // Force an even layout width (button width minus padding) so the icon doesn't land on 0.5pt centers.
        let top = self.stack.topAnchor.constraint(
            greaterThanOrEqualTo: self.topAnchor,
            constant: self.contentPadding.top)
        let leading = self.stack.leadingAnchor.constraint(
            equalTo: self.leadingAnchor,
            constant: self.contentPadding.left)
        let trailing = self.stack.trailingAnchor.constraint(
            equalTo: self.trailingAnchor,
            constant: -self.contentPadding.right)
        let bottom = self.stack.bottomAnchor.constraint(
            lessThanOrEqualTo: self.bottomAnchor,
            constant: -self.contentPadding.bottom)
        let centerY = self.stack.centerYAnchor.constraint(equalTo: self.centerYAnchor)
        self.paddingConstraints = [top, leading, trailing, bottom, centerY]

        NSLayoutConstraint.activate(self.paddingConstraints + self.iconSizeConstraints)
    }
}
