import Foundation
import PDFKit

private func pdf_view_info(_ view: PDFView) -> [String: Any] {
    let inMarkupMode: Bool
    if #available(macOS 13.0, *) {
        inMarkupMode = view.isInMarkupMode
    } else {
        inMarkupMode = false
    }

    return [
        "display_mode": view.displayMode.rawValue,
        "display_direction": view.displayDirection.rawValue,
        "displays_page_breaks": view.displaysPageBreaks,
        "page_break_margins": pdf_insets_dict(view.pageBreakMargins),
        "display_box": view.displayBox.rawValue,
        "displays_as_book": view.displaysAsBook,
        "displays_rtl": view.displaysRTL,
        "background_color": pdf_color_dict(view.backgroundColor) as Any,
        "interpolation_quality": view.interpolationQuality.rawValue,
        "page_shadows_enabled": view.pageShadowsEnabled,
        "scale_factor": view.scaleFactor,
        "min_scale_factor": view.minScaleFactor,
        "max_scale_factor": view.maxScaleFactor,
        "auto_scales": view.autoScales,
        "scale_factor_for_size_to_fit": view.scaleFactorForSizeToFit,
        "in_markup_mode": inMarkupMode,
        "has_document_view": view.documentView != nil,
        "visible_page_count": view.visiblePages.count,
        "current_page_label": view.currentPage?.label as Any,
    ]
}

@_cdecl("pdf_view_new")
public func pdf_view_new(
    _ width: Double,
    _ height: Double,
    _ outView: UnsafeMutablePointer<UnsafeMutableRawPointer?>?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let outView else {
            throw PDFBridgeError.invalidArgument("missing view output pointer")
        }
        let view = PDFView(frame: CGRect(x: 0, y: 0, width: width, height: height))
        outView.pointee = pdf_retain_view(view)
    }
}

@_cdecl("pdf_view_info_json")
public func pdf_view_info_json(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<CChar>? {
    guard let view = pdf_view_value(handle) else { return nil }
    return pdf_string(pdf_json_string(from: pdf_view_info(view)) ?? "{}")
}

@_cdecl("pdf_view_document")
public func pdf_view_document(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {
    guard let view = pdf_view_value(handle), let document = view.document else {
        return nil
    }
    return pdf_retain_document(document)
}

@_cdecl("pdf_view_set_document")
public func pdf_view_set_document(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ documentHandle: UnsafeMutableRawPointer?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        view.document = pdf_document_value(documentHandle)
    }
}

@_cdecl("pdf_view_set_delegate")
public func pdf_view_set_delegate(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ delegateHandle: UnsafeMutableRawPointer?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        view.delegate = pdf_view_delegate_value(delegateHandle)
    }
}

@_cdecl("pdf_view_set_page_overlay_view_provider")
public func pdf_view_set_page_overlay_view_provider(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ providerHandle: UnsafeMutableRawPointer?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        if #available(macOS 13.0, *) {
            view.pageOverlayViewProvider = pdf_page_overlay_view_provider_value(providerHandle)
        } else if providerHandle != nil {
            throw PDFBridgeError.framework("PDFView.pageOverlayViewProvider requires macOS 13.0")
        }
    }
}

@_cdecl("pdf_view_current_page")
public func pdf_view_current_page(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {
    guard let view = pdf_view_value(handle), let page = view.currentPage else {
        return nil
    }
    return pdf_retain_page(page)
}

@_cdecl("pdf_view_current_destination")
public func pdf_view_current_destination(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {
    guard let view = pdf_view_value(handle), let destination = view.currentDestination else {
        return nil
    }
    return pdf_retain_destination(destination)
}

@_cdecl("pdf_view_current_selection")
public func pdf_view_current_selection(_ handle: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {
    guard let view = pdf_view_value(handle), let selection = view.currentSelection else {
        return nil
    }
    return pdf_retain_selection(selection)
}

@_cdecl("pdf_view_set_current_selection")
public func pdf_view_set_current_selection(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ selectionHandle: UnsafeMutableRawPointer?,
    _ animate: Int32,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        view.setCurrentSelection(pdf_selection_value(selectionHandle), animate: animate != 0)
    }
}

@_cdecl("pdf_view_clear_selection")
public func pdf_view_clear_selection(_ handle: UnsafeMutableRawPointer?) {
    guard let view = pdf_view_value(handle) else { return }
    view.clearSelection()
}

@_cdecl("pdf_view_go_to_page")
public func pdf_view_go_to_page(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ pageHandle: UnsafeMutableRawPointer?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle), let page = pdf_page_value(pageHandle) else {
            throw PDFBridgeError.invalidArgument("missing view or page handle")
        }
        view.go(to: page)
    }
}

@_cdecl("pdf_view_go_to_destination")
public func pdf_view_go_to_destination(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ destinationHandle: UnsafeMutableRawPointer?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle), let destination = pdf_destination_value(destinationHandle) else {
            throw PDFBridgeError.invalidArgument("missing view or destination handle")
        }
        view.go(to: destination)
    }
}

@_cdecl("pdf_view_go_to_selection")
public func pdf_view_go_to_selection(
    _ viewHandle: UnsafeMutableRawPointer?,
    _ selectionHandle: UnsafeMutableRawPointer?,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(viewHandle), let selection = pdf_selection_value(selectionHandle) else {
            throw PDFBridgeError.invalidArgument("missing view or selection handle")
        }
        view.go(to: selection)
    }
}

@_cdecl("pdf_view_set_display_mode")
public func pdf_view_set_display_mode(
    _ handle: UnsafeMutableRawPointer?,
    _ rawValue: Int32,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(handle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        view.displayMode = try pdf_display_mode(rawValue)
    }
}

@_cdecl("pdf_view_set_display_direction")
public func pdf_view_set_display_direction(
    _ handle: UnsafeMutableRawPointer?,
    _ rawValue: Int32,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(handle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        view.displayDirection = try pdf_display_direction(rawValue)
    }
}

@_cdecl("pdf_view_set_display_box")
public func pdf_view_set_display_box(
    _ handle: UnsafeMutableRawPointer?,
    _ rawValue: Int32,
    _ outError: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    pdf_run(outError) {
        guard let view = pdf_view_value(handle) else {
            throw PDFBridgeError.invalidArgument("missing view handle")
        }
        view.displayBox = try pdf_display_box(rawValue)
    }
}

@_cdecl("pdf_view_set_auto_scales")
public func pdf_view_set_auto_scales(_ handle: UnsafeMutableRawPointer?, _ value: Int32) {
    guard let view = pdf_view_value(handle) else { return }
    view.autoScales = value != 0
}

@_cdecl("pdf_view_set_scale_factor")
public func pdf_view_set_scale_factor(_ handle: UnsafeMutableRawPointer?, _ value: Double) {
    guard let view = pdf_view_value(handle) else { return }
    view.scaleFactor = CGFloat(value)
}

@_cdecl("pdf_view_set_min_scale_factor")
public func pdf_view_set_min_scale_factor(_ handle: UnsafeMutableRawPointer?, _ value: Double) {
    guard let view = pdf_view_value(handle) else { return }
    view.minScaleFactor = CGFloat(value)
}

@_cdecl("pdf_view_set_max_scale_factor")
public func pdf_view_set_max_scale_factor(_ handle: UnsafeMutableRawPointer?, _ value: Double) {
    guard let view = pdf_view_value(handle) else { return }
    view.maxScaleFactor = CGFloat(value)
}

@_cdecl("pdf_view_layout_document_view")
public func pdf_view_layout_document_view(_ handle: UnsafeMutableRawPointer?) {
    guard let view = pdf_view_value(handle) else { return }
    view.layoutDocumentView()
}

@_cdecl("pdf_view_area_of_interest_for_point")
public func pdf_view_area_of_interest_for_point(
    _ handle: UnsafeMutableRawPointer?,
    _ x: Double,
    _ y: Double
) -> UInt64 {
    guard let view = pdf_view_value(handle) else { return 0 }
    let rawValue = Int64(view.areaOfInterest(for: CGPoint(x: x, y: y)).rawValue)
    return UInt64(bitPattern: rawValue)
}

@_cdecl("pdf_view_visible_page_count")
public func pdf_view_visible_page_count(_ handle: UnsafeMutableRawPointer?) -> UInt64 {
    guard let view = pdf_view_value(handle) else { return 0 }
    return UInt64(view.visiblePages.count)
}

@_cdecl("pdf_view_visible_page_at")
public func pdf_view_visible_page_at(
    _ handle: UnsafeMutableRawPointer?,
    _ index: UInt64
) -> UnsafeMutableRawPointer? {
    guard let view = pdf_view_value(handle), index < UInt64(view.visiblePages.count) else {
        return nil
    }
    return pdf_retain_page(view.visiblePages[Int(index)])
}
