import CoreGraphics
import Foundation

public enum InputMode: String, Sendable, CaseIterable, Hashable {
    case touch
    case trackpad
}

public struct DisplayedRect: Equatable, Sendable {
    public let rect: CGRect

    public init(rect: CGRect) {
        self.rect = rect
    }
}

public struct InputMapper: Sendable {
    public init() {}

    /// Return the rect where a framebuffer of `framebufferSize` is drawn inside
    /// `viewSize` using aspect-fit (`.resizeAspect`) gravity.
    public func displayedRect(for framebufferSize: CGSize,
                              in viewSize: CGSize) -> CGRect? {
        guard framebufferSize.width > 0, framebufferSize.height > 0,
              viewSize.width > 0, viewSize.height > 0 else {
            return nil
        }
        let viewAspect = viewSize.width / viewSize.height
        let fbAspect = framebufferSize.width / framebufferSize.height
        if viewAspect > fbAspect {
            let displayedWidth = viewSize.height * fbAspect
            let xOffset = (viewSize.width - displayedWidth) / 2
            return CGRect(x: xOffset, y: 0,
                          width: displayedWidth,
                          height: viewSize.height)
        } else {
            let displayedHeight = viewSize.width / fbAspect
            let yOffset = (viewSize.height - displayedHeight) / 2
            return CGRect(x: 0, y: yOffset,
                          width: viewSize.width,
                          height: displayedHeight)
        }
    }

    /// Convert a point in view coordinates to normalized framebuffer coordinates [0,1].
    /// Returns nil if either size is empty.
    public func normalize(viewPoint: CGPoint,
                          in viewSize: CGSize,
                          framebufferSize: CGSize) -> CGPoint? {
        guard let displayed = displayedRect(for: framebufferSize, in: viewSize) else {
            return nil
        }
        let nx = (viewPoint.x - displayed.origin.x) / displayed.width
        let ny = (viewPoint.y - displayed.origin.y) / displayed.height
        return CGPoint(x: clamp(nx), y: clamp(ny))
    }

    /// Convert a normalized framebuffer point [0,1] to view coordinates.
    public func viewPoint(forNormalized normalized: CGPoint,
                          in viewSize: CGSize,
                          framebufferSize: CGSize) -> CGPoint? {
        guard let displayed = displayedRect(for: framebufferSize, in: viewSize) else {
            return nil
        }
        let x = displayed.origin.x + normalized.x * displayed.width
        let y = displayed.origin.y + normalized.y * displayed.height
        return CGPoint(x: x, y: y)
    }

    private func clamp(_ value: CGFloat) -> CGFloat {
        max(0, min(1, value))
    }
}
