<commit_before>import Cocoa
import Hangouts
import ParrotServiceExtension

/* TODO: Re-enable link previews later when they're not terrible... */
/* TODO: Use the PlaceholderMessage for sending messages. */
/* TODO: When selecting text and typing a completion character, wrap the text. */

public class MessageListViewController: NSWindowController, NSTextViewExtendedDelegate, ConversationDelegate {
	
	// This is instantly shown to the user when they send a message. It will
	// be updated automatically when the status of the message is known.
	public struct PlaceholderMessage: Message {
		public let sender: Person?
		public let timestamp: Date
		public let text: String
		public var failed: Bool = false
	}
	
	@IBOutlet var listView: ListView!
	@IBOutlet var indicator: NSProgressIndicator!
    @IBOutlet var entryView: NSTextView!
	@IBOutlet var statusView: NSTextField!
	@IBOutlet var imageView: NSButton!
	@IBOutlet var drawer: NSDrawer!
	@IBOutlet var drawerButton: NSButton!
	@IBOutlet var drawerView: NSView!
	
	// TODO: BEGONE!
	public var sendMessageHandler: (String, ParrotServiceExtension.Conversation) -> Void = {_ in}
	
	var _previews = [String: [LinkPreviewType]]()
	var _focusRow = -1
	var _note: NSObjectProtocol!
	lazy var popover: NSPopover = {
		let p = NSPopover()
		let v = NSViewController()
		v.view = self.statusView!
		p.contentViewController = v
		p.behavior = .applicationDefined
		return p
	}()
	
	private var dataSource: [EventStreamItem] = []
	
	public override func loadWindow() {
		super.loadWindow()
		self.drawer.__setupModernDrawer()
		
		self.window?.appearance = ParrotAppearance.interfaceStyle().appearance()
		self.window?.enableRealTitlebarVibrancy(.withinWindow)
		self.window?.titleVisibility = .hidden
		
		ParrotAppearance.registerVibrancyStyleListener(observer: self, invokeImmediately: true) { style in
			guard let vev = self.window?.contentView as? NSVisualEffectView else { return }
			vev.state = style.visualEffectState()
			guard let vev2 = self.drawer.contentView as? NSVisualEffectView else { return }
			vev2.state = style.visualEffectState()
		}
		
		self.listView.register(nibName: "MessageCell", forClass: MessageCell.self)
		self.listView.register(nibName: "FocusCell", forClass: FocusCell.self)
		self.listView.register(nibName: "LinkPreviewCell", forClass: LinkPreviewCell.self)
		
		// oh lawd pls forgibs my sins
		self.listView.dataSourceProvider = { // watch for invalid Collection: count differed in successive traversals
			return self.dataSource.map { $0 as Any }
		}
		
		self.listView.viewClassProvider = { row in
			let cls = self.listView.dataSourceProvider!()[row]
			if cls is Message {
				return MessageCell.self
			} else if cls is LinkPreviewType {
				return LinkPreviewCell.self
			} else {
				log.debug("\(row) OMG GOT NOTHING \(cls)")
				return ListViewCell.self
			}
		}
		
		self.listView.sizeClass = .dynamic
		self.entryView.delegate = self
		
		if let me = self.conversation?.client.userList.me {
			if let photo = self.imageView, let layer = photo.layer {
				layer.masksToBounds = true
				layer.cornerRadius = photo.bounds.width / 2.0
			}
			self.imageView.image = fetchImage(user: me as! User, conversation: self.conversation!)
		}
		
		// [BUG] [macOS 12] NSTextView doesn't fill width if layer-backed?
		if let text = self.entryView {
			text.frame = text.enclosingScrollView!.bounds
		}
    }
	
	public override func showWindow(_ sender: AnyObject?) {
		super.showWindow(nil)
        self.animatedUpdate(true)
		self.listView.insets = EdgeInsets(top: 36.0, left: 0, bottom: 40.0, right: 0)
		
		/*
		if self.window?.isKeyWindow ?? false {
			self.windowDidBecomeKey(Notification(name: "" as Notification.Name))
		}
		*/
		self.windowDidChangeOcclusionState(Notification(name: "" as Notification.Name))
		
		// Set up dark/light notifications.
		ParrotAppearance.registerInterfaceStyleListener(observer: self, invokeImmediately: true) { interface in
			self.window?.appearance = interface.appearance()
			self.drawer.window?.appearance = interface.appearance()
			
			// NSTextView doesn't automatically change its text color when the
			// backing view's appearance changes, so we need to set it each time.
			// In addition, make sure links aren't blue as usual.
			guard let text = self.entryView else { return }
			text.layer?.masksToBounds = true
			text.layer?.cornerRadius = 2.0
			text.layer?.backgroundColor = NSColor.darkOverlay(forAppearance: self.window!.effectiveAppearance).cgColor
			
			text.textColor = NSColor.labelColor()
			text.font = NSFont.systemFont(ofSize: 12.0)
			text.typingAttributes = [
				NSForegroundColorAttributeName: text.textColor!,
				NSFontAttributeName: text.font!
			]
			text.linkTextAttributes = [
				NSForegroundColorAttributeName: NSColor.labelColor(),
				NSCursorAttributeName: NSCursor.pointingHand(),
				NSUnderlineStyleAttributeName: 1,
			]
			text.selectedTextAttributes = [
				NSBackgroundColorAttributeName: NSColor.lightOverlay(forAppearance: self.window!.effectiveAppearance),
				NSForegroundColorAttributeName: NSColor.labelColor(),
				NSUnderlineStyleAttributeName: 0,
			]
			text.markedTextAttributes = [
				NSBackgroundColorAttributeName: NSColor.lightOverlay(forAppearance: self.window!.effectiveAppearance),
				NSForegroundColorAttributeName: NSColor.labelColor(),
				NSUnderlineStyleAttributeName: 0,
			]
			/*text.placeholderTextAttributes = [
				NSForegroundColorAttributeName: NSColor.tertiaryLabelColor(),
				NSFontAttributeName: text.font!
			]*/
		}
		
		/*
		runSelectionPanel(for: self.window!, fileTypes: ["mp3", "caf", "aiff", "wav"]) {
			log.debug("received \($0)")
		}*/
    }
	
	// Performs a visual refresh of the conversation list.
    // If preconfigure is true, it specifies that this is init stuff.
	private func animatedUpdate(_ preconfigure: Bool = false) {
		DispatchQueue.main.async {
			self.listView.superview!.isHidden = true
			self.indicator.alphaValue = 1.0
			self.indicator.isHidden = false
			self.indicator.startAnimation(nil)
			
            if preconfigure { return }
			self.listView.update(animated: false) {
				self.listView.superview!.animator().isHidden = false
				self.indicator.animator().alphaValue = 0.0
				
				let scaleIn = CAAnimation.scaleIn(forFrame: self.listView.superview!.layer!.frame)
				self.listView.superview!.layer?.add(scaleIn, forKey: "updates")
				
				let durMS = Int(NSAnimationContext.current().duration * 1000.0)
				DispatchQueue.main.after(when: .now() + .milliseconds(durMS)) {
					self.indicator.stopAnimation(nil)
					self.indicator.isHidden = true
                    
                    // Fixes the loss of responder when hiding the view.
                    self.window?.makeFirstResponder(self.entryView)
				}
			}
		}
	}
	
	// NSWindowOcclusionState: 8194 is Visible, 8192 is Occluded
	public func windowDidChangeOcclusionState(_ notification: Notification) {
		self.conversation?.setFocus(self.window!.occlusionState.rawValue == 8194)
	}
	
	public func windowWillClose(_ notification: Notification) {
		ParrotAppearance.unregisterInterfaceStyleListener(observer: self)
	}
	
	var conversation: IConversation? {
		didSet {
			//DispatchQueue.main.sync {<commit_msg>Add window close animation (which needs to be a bit quicker and eased out).<commit_after>import Cocoa
import Hangouts
import ParrotServiceExtension

/* TODO: Re-enable link previews later when they're not terrible... */
/* TODO: Use the PlaceholderMessage for sending messages. */
/* TODO: When selecting text and typing a completion character, wrap the text. */

public class MessageListViewController: NSWindowController, NSTextViewExtendedDelegate, ConversationDelegate {
	
	// This is instantly shown to the user when they send a message. It will
	// be updated automatically when the status of the message is known.
	public struct PlaceholderMessage: Message {
		public let sender: Person?
		public let timestamp: Date
		public let text: String
		public var failed: Bool = false
	}
	
	@IBOutlet var listView: ListView!
	@IBOutlet var indicator: NSProgressIndicator!
    @IBOutlet var entryView: NSTextView!
	@IBOutlet var statusView: NSTextField!
	@IBOutlet var imageView: NSButton!
	@IBOutlet var drawer: NSDrawer!
	@IBOutlet var drawerButton: NSButton!
	@IBOutlet var drawerView: NSView!
	
	// TODO: BEGONE!
	public var sendMessageHandler: (String, ParrotServiceExtension.Conversation) -> Void = {_ in}
	
	var _previews = [String: [LinkPreviewType]]()
	var _focusRow = -1
	var _note: NSObjectProtocol!
	lazy var popover: NSPopover = {
		let p = NSPopover()
		let v = NSViewController()
		v.view = self.statusView!
		p.contentViewController = v
		p.behavior = .applicationDefined
		return p
	}()
	
	private var dataSource: [EventStreamItem] = []
	
	public override func loadWindow() {
		super.loadWindow()
		self.drawer.__setupModernDrawer()
		
		self.window?.appearance = ParrotAppearance.interfaceStyle().appearance()
		self.window?.enableRealTitlebarVibrancy(.withinWindow)
		self.window?.titleVisibility = .hidden
		
		ParrotAppearance.registerVibrancyStyleListener(observer: self, invokeImmediately: true) { style in
			guard let vev = self.window?.contentView as? NSVisualEffectView else { return }
			vev.state = style.visualEffectState()
			guard let vev2 = self.drawer.contentView as? NSVisualEffectView else { return }
			vev2.state = style.visualEffectState()
		}
		
		self.listView.register(nibName: "MessageCell", forClass: MessageCell.self)
		self.listView.register(nibName: "FocusCell", forClass: FocusCell.self)
		self.listView.register(nibName: "LinkPreviewCell", forClass: LinkPreviewCell.self)
		
		// oh lawd pls forgibs my sins
		self.listView.dataSourceProvider = { // watch for invalid Collection: count differed in successive traversals
			return self.dataSource.map { $0 as Any }
		}
		
		self.listView.viewClassProvider = { row in
			let cls = self.listView.dataSourceProvider!()[row]
			if cls is Message {
				return MessageCell.self
			} else if cls is LinkPreviewType {
				return LinkPreviewCell.self
			} else {
				log.debug("\(row) OMG GOT NOTHING \(cls)")
				return ListViewCell.self
			}
		}
		
		self.listView.sizeClass = .dynamic
		self.entryView.delegate = self
		
		if let me = self.conversation?.client.userList.me {
			if let photo = self.imageView, let layer = photo.layer {
				layer.masksToBounds = true
				layer.cornerRadius = photo.bounds.width / 2.0
			}
			self.imageView.image = fetchImage(user: me as! User, conversation: self.conversation!)
		}
		
		// [BUG] [macOS 12] NSTextView doesn't fill width if layer-backed?
		if let text = self.entryView {
			text.frame = text.enclosingScrollView!.bounds
		}
    }
	
	public override func showWindow(_ sender: AnyObject?) {
		super.showWindow(nil)
        self.animatedUpdate(true)
		self.listView.insets = EdgeInsets(top: 36.0, left: 0, bottom: 40.0, right: 0)
		
		/*
		if self.window?.isKeyWindow ?? false {
			self.windowDidBecomeKey(Notification(name: "" as Notification.Name))
		}
		*/
		self.windowDidChangeOcclusionState(Notification(name: "" as Notification.Name))
		
		// Set up dark/light notifications.
		ParrotAppearance.registerInterfaceStyleListener(observer: self, invokeImmediately: true) { interface in
			self.window?.appearance = interface.appearance()
			self.drawer.window?.appearance = interface.appearance()
			
			// NSTextView doesn't automatically change its text color when the
			// backing view's appearance changes, so we need to set it each time.
			// In addition, make sure links aren't blue as usual.
			guard let text = self.entryView else { return }
			text.layer?.masksToBounds = true
			text.layer?.cornerRadius = 2.0
			text.layer?.backgroundColor = NSColor.darkOverlay(forAppearance: self.window!.effectiveAppearance).cgColor
			
			text.textColor = NSColor.labelColor()
			text.font = NSFont.systemFont(ofSize: 12.0)
			text.typingAttributes = [
				NSForegroundColorAttributeName: text.textColor!,
				NSFontAttributeName: text.font!
			]
			text.linkTextAttributes = [
				NSForegroundColorAttributeName: NSColor.labelColor(),
				NSCursorAttributeName: NSCursor.pointingHand(),
				NSUnderlineStyleAttributeName: 1,
			]
			text.selectedTextAttributes = [
				NSBackgroundColorAttributeName: NSColor.lightOverlay(forAppearance: self.window!.effectiveAppearance),
				NSForegroundColorAttributeName: NSColor.labelColor(),
				NSUnderlineStyleAttributeName: 0,
			]
			text.markedTextAttributes = [
				NSBackgroundColorAttributeName: NSColor.lightOverlay(forAppearance: self.window!.effectiveAppearance),
				NSForegroundColorAttributeName: NSColor.labelColor(),
				NSUnderlineStyleAttributeName: 0,
			]
			/*text.placeholderTextAttributes = [
				NSForegroundColorAttributeName: NSColor.tertiaryLabelColor(),
				NSFontAttributeName: text.font!
			]*/
		}
		
		/*
		runSelectionPanel(for: self.window!, fileTypes: ["mp3", "caf", "aiff", "wav"]) {
			log.debug("received \($0)")
		}*/
    }
	
	// Performs a visual refresh of the conversation list.
    // If preconfigure is true, it specifies that this is init stuff.
	private func animatedUpdate(_ preconfigure: Bool = false) {
		DispatchQueue.main.async {
			self.listView.superview!.isHidden = true
			self.indicator.alphaValue = 1.0
			self.indicator.isHidden = false
			self.indicator.startAnimation(nil)
			
            if preconfigure { return }
			self.listView.update(animated: false) {
				self.listView.superview!.animator().isHidden = false
				self.indicator.animator().alphaValue = 0.0
				
				let scaleIn = CAAnimation.scaleIn(forFrame: self.listView.superview!.layer!.frame)
				self.listView.superview!.layer?.add(scaleIn, forKey: "updates")
				
				let durMS = Int(NSAnimationContext.current().duration * 1000.0)
				DispatchQueue.main.after(when: .now() + .milliseconds(durMS)) {
					self.indicator.stopAnimation(nil)
					self.indicator.isHidden = true
                    
                    // Fixes the loss of responder when hiding the view.
                    self.window?.makeFirstResponder(self.entryView)
				}
			}
		}
	}
	
	// NSWindowOcclusionState: 8194 is Visible, 8192 is Occluded
	public func windowDidChangeOcclusionState(_ notification: Notification) {
		self.conversation?.setFocus(self.window!.occlusionState.rawValue == 8194)
	}
	
	public func windowWillClose(_ notification: Notification) {
		ParrotAppearance.unregisterInterfaceStyleListener(observer: self)
        if let w = self.window {
            let old_rect = w.frame
            var rect = w.frame
            rect.origin.y = -(rect.height + 32)
            
            w.setFrame(rect, display: true, animate: true)
            DispatchQueue.main.async {
                w.setFrame(old_rect, display: false)
            }
        }
	}
	
	var conversation: IConversation? {
		didSet {
			//DispatchQueue.main.sync {
{'fraction_non_alphanumeric': 0.100545491278874, 'fraction_numerical': 0.006465081823691831, 'mean_word_length': 3.7535211267605635, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '959713', 'n_tokens_mistral': 5140, 'n_tokens_neox': 4397, 'n_words': 1081}