//
// MIT License
//
// Copyright (c) 2022 Lukas Romsicki
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Source Code: https://github.com/lfroms/fluid-menu-bar-extra
// Some parts of the code were altered to fit the needs of this application.
// Any modifications that were made are still licensed under the author's
// original license (aka MIT license). Contents of this file are excluded
// from the coverage of the license being applied to the rest of the code.
//

import AppKit
import os

final class CBMenuBarItem: NSObject, NSWindowDelegate {
    private var statusItem: NSStatusItem
    var panel: NSPanel

    private var localEventMonitor: EventMonitor?
    private var localDragEventMonitor: EventMonitor?
    private var globalEventMonitor: EventMonitor?

    override init() {
        self.statusItem = NSStatusBar.system
            .statusItem(withLength: NSStatusItem.variableLength)
        self.statusItem.isVisible = true
        self.panel = PopoverPanel(viewController: CmdViewController())
        self.statusItem.button?.font = NSFont
            .monospacedSystemFont(ofSize: NSFontDescriptor
                .preferredFontDescriptor(forTextStyle: .body).pointSize,
                                         weight: .regular)

        super.init()

        // Events
        // Shows panel and keeps the button highlighted.
        localEventMonitor = LocalEventMonitor(mask: [.leftMouseDown])
            { [weak self] event in
                if let button = self?.statusItem.button,
                    event.window == button.window,
                    !event.modifierFlags.contains(.command)
                {
                    self?.statusButtonPressed(button)
                    return nil
                }

                return event
        }

        // On click and drag, the button should panel should desappear and
        // un-highlight.
        localDragEventMonitor =
            LocalEventMonitor(mask: [.leftMouseDragged, .keyDown])
                { [weak self] event in
                    let modifiers = event.modifierFlags.rawValue

                    if let panel = self?.panel, panel.isKeyWindow {
                        if modsContains(keys: OSCmd, in: modifiers) &&
                            event.type == .leftMouseDragged
                        {
                            self?.panel.resignKey()
                        }
                    }

                    return event
                }

        // NOTE: The need for this seems a bit questionable. Maybe, this
        // works properly without global event monitor in MacOS Sequoia?
        // Resign key whenever clicking outside of panel, thus hiding the
        // panel and un-highlighting the button.
        // In order to receive events, the program needs to be codesigned.
        globalEventMonitor =
            GlobalEventMonitor(mask: [.leftMouseDown, .rightMouseDown])
                { [weak self] event in
                    if let panel = self?.panel, panel.isKeyWindow {
                        panel.resignKey()
                    }
                }

        if let statusButton = statusItem.button,
           let statusButtonWindow = statusButton.window {
            statusButtonWindow.delegate = self
        }

        self.panel.delegate = self
        localEventMonitor?.start()
        localDragEventMonitor?.start()
    }

    deinit {
        persistMenuBar(false)
        localEventMonitor?.stop()
        localDragEventMonitor?.stop()
        globalEventMonitor?.stop()
        panel.close()
        NSStatusBar.system.removeStatusItem(statusItem)
    }

    @objc private func statusButtonPressed(_ sender: NSButton) {
        if panel.isVisible {
            panel.resignKey()
            return
        }
        showPanel()
    }

    private func showPanel() {
        setButtonHighlighted(to: true)
        persistMenuBar(true)
        setPanelPosition()
        panel.makeKeyAndOrderFront(nil)
    }

    private func dismissPanel() {
        NSAnimationContext.runAnimationGroup { context in
            context.duration = 0.3
            context.timingFunction =
                CAMediaTimingFunction(name: .easeInEaseOut)
            panel.animator().alphaValue = 0
        } completionHandler: { [weak self] in
            self?.panel.orderOut(nil)
            self?.panel.alphaValue = 1
            self?.setButtonHighlighted(to: false)
            persistMenuBar(false)
        }
    }

    private func setPanelPosition() {
        guard let scrn = NSScreen.main,
            let iFrame = statusItem.button?.window?.frame
        else { return }

        var x = iFrame.origin.x
        let y = scrn.visibleFrame.height + scrn.visibleFrame.origin.y

        if (iFrame.origin.x + panel.frame.width) >
                (scrn.visibleFrame.origin.x +
                    scrn.visibleFrame.width)
        {
            x = iFrame.origin.x + iFrame.width - panel.frame.width
        }
        if (iFrame.origin.x - panel.frame.width) <
                (scrn.visibleFrame.origin.x)
        {
            x = scrn.visibleFrame.origin.x
        }

        panel.setFrameTopLeftPoint(NSPoint(x: x, y: y))
    }

    private func setButtonHighlighted(to highlight: Bool) {
        self.statusItem.button!.highlight(highlight)
    }

    func setImage(title: String?, description: String) {
        if title != nil {
            statusItem.button!.image =
                NSImage(systemSymbolName: title!,
                        accessibilityDescription: description)
            return
        }
        statusItem.button!.image = nil
    }

    func setTitle(_ title: String) {
        statusItem.button!.title = title
    }

    func setAutosaveName(_ name: String) {
        statusItem.autosaveName = name
    }

    func setContents(to text: String) {
        guard let viewContoller =
            panel.contentViewController as? CmdViewController
        else { return }
        viewContoller.setText(text)
    }

    func setFile(_ file: CmdFile) {
        guard let viewContoller =
            panel.contentViewController as? CmdViewController
        else { return }
        viewContoller.setFile(file)
    }

    func windowDidResize(_ notification: Notification) {
        if let cmdPanel = notification.object as? NSPanel,
            panel == cmdPanel, cmdPanel.isVisible
        {
            setPanelPosition()
        }
    }

    func windowDidMove(_ notification: Notification) {
        if let statusBarButtonWindow = notification.object as? NSWindow,
           let buttonWindow = statusItem.button?.window,
           buttonWindow == statusBarButtonWindow,
           panel.isVisible
        {
            setPanelPosition()
        }
    }

    func windowDidBecomeKey(_ notification: Notification) {
        globalEventMonitor?.start()
    }

    func windowDidResignKey(_ notification: Notification) {
        globalEventMonitor?.stop()
        dismissPanel()
    }
}

// MARK: - Metrics
fileprivate enum Metrics {
    static let windowBorderSize: CGFloat = 2
}
