import AppKit
import QuartzCore
import SwiftUI

extension VoiceWakeOverlayController {
    func present() {
        if !self.enableUI || ProcessInfo.processInfo.isRunningTests {
            if !self.model.isVisible {
                self.model.isVisible = true
            }
            return
        }
        self.ensureWindow()
        self.hostingView?.rootView = VoiceWakeOverlayView(controller: self)
        let target = self.targetFrame()
        let isFirst = !self.model.isVisible
        if isFirst { self.model.isVisible = true }
        OverlayPanelFactory.present(
            window: self.window,
            isFirstPresent: isFirst,
            target: target,
            onFirstPresent: {
                self.logger.log(
                    level: .info,
                    "overlay present windowShown textLen=\(self.model.text.count, privacy: .public)")
                // Keep the status item in “listening” mode until we explicitly dismiss the overlay.
                AppStateStore.shared.triggerVoiceEars(ttl: nil)
            },
            onAlreadyVisible: { window in
                self.updateWindowFrame(animate: true)
                window.orderFrontRegardless()
            })
    }

    private func ensureWindow() {
        if self.window != nil { return }
        let borderPad = self.closeOverflow
        let panel = OverlayPanelFactory.makePanel(
            contentRect: NSRect(x: 0, y: 0, width: self.width + borderPad * 2, height: 60 + borderPad * 2),
            level: Self.preferredWindowLevel,
            hasShadow: false)

        let host = NSHostingView(rootView: VoiceWakeOverlayView(controller: self))
        host.translatesAutoresizingMaskIntoConstraints = false
        panel.contentView = host
        self.hostingView = host
        self.window = panel
    }

    /// Reassert window ordering when other panels are shown.
    func bringToFrontIfVisible() {
        guard self.model.isVisible, let window = self.window else { return }
        window.level = Self.preferredWindowLevel
        window.orderFrontRegardless()
    }

    func targetFrame() -> NSRect {
        guard let screen = NSScreen.main else { return .zero }
        let height = self.measuredHeight()
        let size = NSSize(width: self.width + self.closeOverflow * 2, height: height + self.closeOverflow * 2)
        let visible = screen.visibleFrame
        let origin = CGPoint(
            x: visible.maxX - size.width,
            y: visible.maxY - size.height)
        return NSRect(origin: origin, size: size)
    }

    func updateWindowFrame(animate: Bool = false) {
        OverlayPanelFactory.applyFrame(window: self.window, target: self.targetFrame(), animate: animate)
    }

    func measuredHeight() -> CGFloat {
        let attributed = self.model.attributed.length > 0 ? self.model.attributed : self
            .makeAttributed(from: self.model.text)
        let maxWidth = self.width - (self.padding * 2) - self.spacing - self.buttonWidth

        let textInset = NSSize(width: 2, height: 6)
        let lineFragmentPadding: CGFloat = 0
        let containerWidth = max(1, maxWidth - (textInset.width * 2) - (lineFragmentPadding * 2))

        let storage = NSTextStorage(attributedString: attributed)
        let container = NSTextContainer(containerSize: CGSize(width: containerWidth, height: .greatestFiniteMagnitude))
        container.lineFragmentPadding = lineFragmentPadding
        container.lineBreakMode = .byWordWrapping

        let layout = NSLayoutManager()
        layout.addTextContainer(container)
        storage.addLayoutManager(layout)

        _ = layout.glyphRange(for: container)
        let used = layout.usedRect(for: container)

        let contentHeight = ceil(used.height + (textInset.height * 2))
        let total = contentHeight + self.verticalPadding * 2
        self.model.isOverflowing = total > self.maxHeight
        return max(self.minHeight, min(total, self.maxHeight))
    }

    func dismissTargetFrame(for frame: NSRect, reason: DismissReason, outcome: SendOutcome) -> NSRect? {
        switch (reason, outcome) {
        case (.empty, _):
            let scale: CGFloat = 0.95
            let newSize = NSSize(width: frame.size.width * scale, height: frame.size.height * scale)
            let dx = (frame.size.width - newSize.width) / 2
            let dy = (frame.size.height - newSize.height) / 2
            return NSRect(x: frame.origin.x + dx, y: frame.origin.y + dy, width: newSize.width, height: newSize.height)
        case (.explicit, .sent):
            return frame.offsetBy(dx: 8, dy: 6)
        default:
            return frame
        }
    }
}
