// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

#![allow(unsafe_code)]

use alloc::rc::Rc;
use core::ffi::c_void;

use super::DataTransfer;
use crate::SharedString;
#[cfg(feature = "std")]
use crate::SharedVector;
use crate::api::Image;
#[cfg(feature = "std")]
use crate::slice::Slice;

// The unit of a path's native representation, matching C++'s
// `std::filesystem::path::value_type`: UTF-16 code units on Windows, bytes
// elsewhere. Paths cross the FFI as slices of this type so that any valid
// path round-trips losslessly.
#[cfg(all(feature = "std", windows))]
pub type PathValueType = u16;
#[cfg(all(feature = "std", not(windows)))]
pub type PathValueType = u8;

#[cfg(feature = "std")]
fn path_from_units(units: &[PathValueType]) -> std::path::PathBuf {
    #[cfg(unix)]
    {
        use std::os::unix::ffi::OsStrExt;
        std::ffi::OsStr::from_bytes(units).into()
    }
    #[cfg(windows)]
    {
        use std::os::windows::ffi::OsStringExt;
        std::ffi::OsString::from_wide(units).into()
    }
    #[cfg(not(any(unix, windows)))]
    {
        alloc::string::String::from_utf8_lossy(units).into_owned().into()
    }
}
#[cfg(feature = "std")]
fn path_to_units(path: &std::path::Path) -> SharedVector<PathValueType> {
    #[cfg(unix)]
    {
        use std::os::unix::ffi::OsStrExt;
        path.as_os_str().as_bytes().into()
    }
    #[cfg(windows)]
    {
        use std::os::windows::ffi::OsStrExt;
        path.as_os_str().encode_wide().collect()
    }
    #[cfg(not(any(unix, windows)))]
    {
        path.to_string_lossy().as_bytes().into()
    }
}

/// Opaque placeholder used by C++ to reserve storage with the same size and
/// alignment as Rust's `DataTransfer`. The actual `DataTransfer` contains
/// `Option<Rc<...>>` fields whose layout cannot be expressed via cbindgen, so
/// C++ never inspects these fields directly: copy/destruction goes through
/// the `slint_data_transfer_*` FFI functions below, which operate on a real
/// `DataTransfer`.
///
/// The three pointer-sized fields correspond to:
/// - `_rc_inner`: thin `Rc` pointer for `Option<Rc<DataTransferInner>>` (null = `None`)
/// - `_rc_any_0`/`_rc_any_1`: data + vtable pointers of the
///   `Option<Rc<dyn Any>>` user data fat pointer (null data = `None`)
#[repr(C)]
pub struct DataTransferOpaque {
    _rc_inner: *mut core::ffi::c_void,
    _rc_any_0: *mut core::ffi::c_void,
    _rc_any_1: *mut core::ffi::c_void,
}

static_assertions::assert_eq_align!(DataTransferOpaque, DataTransfer);
static_assertions::assert_eq_size!(DataTransferOpaque, DataTransfer);

/// Default-construct a `DataTransfer` in place at `out`.
///
/// # Safety
/// `out` must be valid for writes of `DataTransfer` and must not currently
/// hold an initialized `DataTransfer` (otherwise the previous value is leaked).
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_data_transfer_init_default(out: *mut DataTransfer) {
    unsafe { core::ptr::write(out, DataTransfer::default()) }
}

/// Drop a `DataTransfer` in place.
///
/// # Safety
/// `d` must point to an initialized `DataTransfer` and must not be used after
/// this call returns.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_data_transfer_drop(d: *mut DataTransfer) {
    unsafe { core::ptr::drop_in_place(d) }
}

/// Clone `src` into the uninitialized memory at `out`.
///
/// # Safety
/// `out` must be valid for writes of `DataTransfer` and must not currently
/// hold an initialized `DataTransfer` (otherwise the previous value is leaked).
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_data_transfer_clone(out: *mut DataTransfer, src: &DataTransfer) {
    unsafe { core::ptr::write(out, src.clone()) }
}

/// Compare two `DataTransfer` values for equality (same semantics as
/// `<DataTransfer as PartialEq>::eq`).
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_eq(a: &DataTransfer, b: &DataTransfer) -> bool {
    a == b
}

/// Set the plain text representation of `d` to a clone of `text`.
///
/// An empty `text` clears the previously-set plain text instead of storing it.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_set_plain_text(d: &mut DataTransfer, text: &SharedString) {
    d.set_plain_text(text.clone());
}

/// Set the image representation of `d` to a clone of `image`.
///
/// A default-constructed `image` clears the previously-set image instead of storing it.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_set_image(d: &mut DataTransfer, image: &Image) {
    d.set_image(image.clone());
}

/// Set the file path list of `d`. Each path is a slice of its native
/// representation units (see `PathValueType`).
///
/// An empty `paths` clears the previously-set file paths instead of storing it.
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_set_file_paths(
    d: &mut DataTransfer,
    paths: Slice<Slice<PathValueType>>,
) {
    d.set_file_paths_vec(paths.iter().map(|path| path_from_units(path.as_slice())).collect());
}

/// Returns `true` if `d` advertises a plain text representation.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_has_plain_text(d: &DataTransfer) -> bool {
    d.has_plain_text()
}

/// Returns `true` if `d` advertises an image representation.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_has_image(d: &DataTransfer) -> bool {
    d.has_image()
}

/// Returns `true` if `d` advertises a list of file paths.
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_has_file_paths(d: &DataTransfer) -> bool {
    d.has_file_paths()
}

/// Returns `true` if `d` carries no data: no plain text, no image, no file paths,
/// and no user data.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_is_empty(d: &DataTransfer) -> bool {
    d.is_empty()
}

/// If `d` has a plain text representation, write a clone of it into `out` and
/// return `true`. Otherwise leave `out` unchanged and return `false`.
///
/// `out` must point to an initialized `SharedString`.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_plain_text(d: &DataTransfer, out: &mut SharedString) -> bool {
    match d.plain_text() {
        Ok(s) => {
            *out = s;
            true
        }
        Err(_) => false,
    }
}

/// If `d` has an image representation, write a clone of it into `out` and
/// return `true`. Otherwise leave `out` unchanged and return `false`.
///
/// `out` must point to an initialized `Image`.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_image(d: &DataTransfer, out: &mut Image) -> bool {
    match d.image() {
        Ok(i) => {
            *out = i;
            true
        }
        Err(_) => false,
    }
}

/// If `d` has a list of file paths, write them into `out` and return `true`.
/// Otherwise leave `out` unchanged and return `false`. Each path is a vector
/// of its native representation units (see `PathValueType`).
///
/// `out` must point to an initialized `SharedVector<SharedVector<PathValueType>>`.
#[cfg(feature = "std")]
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_file_paths(
    d: &DataTransfer,
    out: &mut SharedVector<SharedVector<PathValueType>>,
) -> bool {
    match d.file_paths() {
        Ok(paths) => {
            *out = paths.map(path_to_units).collect();
            true
        }
        Err(_) => false,
    }
}

/// C++-owned user-data handle stored in a `DataTransfer`.
struct CppUserData {
    handle: *mut c_void,
    drop_fn: unsafe extern "C" fn(*mut c_void),
}

impl Drop for CppUserData {
    fn drop(&mut self) {
        unsafe { (self.drop_fn)(self.handle) }
    }
}

/// Store `handle` as the user data of `d`. `drop_fn(handle)` runs when `d` is dropped.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_data_transfer_set_user_data(
    d: &mut DataTransfer,
    handle: *mut c_void,
    drop_fn: unsafe extern "C" fn(*mut c_void),
) {
    d.set_user_data(Rc::new(CppUserData { handle, drop_fn }));
}

/// Write a borrowed pointer to `d`'s C++ user-data handle into `out_handle`.
/// Returns `false` if `d` has no C++ user data.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_data_transfer_user_data(
    d: &DataTransfer,
    out_handle: *mut *const c_void,
) -> bool {
    let Some(any) = d.user_data() else { return false };
    let Some(cpp) = any.downcast_ref::<CppUserData>() else { return false };
    unsafe { *out_handle = cpp.handle as *const c_void };
    true
}

/// Clear the user data of `d`, if any.
#[unsafe(no_mangle)]
pub extern "C" fn slint_data_transfer_clear_user_data(d: &mut DataTransfer) {
    d.user_data = None;
}
