//! C FFI for cpdf page operations.

use super::document::{CPDF_DOCS, CPDF_RANGES};
use super::{clear_error, set_error};
use crate::cpdf::pages;
use crate::ffi::Handle;
use std::ffi::{CStr, c_char, c_int};

macro_rules! get_doc_clone {
    ($handle:expr) => {
        match CPDF_DOCS.get($handle) {
            Some(arc) => arc.lock().unwrap().clone(),
            None => {
                set_error(1, &format!("invalid document handle: {}", $handle));
                return 0;
            }
        }
    };
}

macro_rules! get_range {
    ($handle:expr) => {
        match CPDF_RANGES.get($handle) {
            Some(arc) => arc.lock().unwrap().clone(),
            None => {
                set_error(1, &format!("invalid range handle: {}", $handle));
                return 0;
            }
        }
    };
}

macro_rules! ok_or_error {
    ($result:expr) => {
        match $result {
            Ok(doc) => CPDF_DOCS.insert(doc),
            Err(e) => {
                set_error(1, &e.to_string());
                0
            }
        }
    };
}

// ── Rotation ────────────────────────────────────────────────────────

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_rotate(doc: Handle, range: Handle, degrees: c_int) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::rotate_pages(d, &r, degrees))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_rotateBy(doc: Handle, range: Handle, degrees: c_int) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::rotate_pages_by(d, &r, degrees))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_rotateContents(doc: Handle, range: Handle, angle: f64) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::rotate_contents(d, &r, angle))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_upright(doc: Handle, range: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::upright(d, &r))
}

// ── Scale / shift / flip ────────────────────────────────────────────

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_scale(doc: Handle, range: Handle, sx: f64, sy: f64) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::scale_pages(d, &r, sx, sy))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_scaleToFit(
    doc: Handle,
    range: Handle,
    w: f64,
    h: f64,
    scale_factor: f64,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::scale_to_fit(d, &r, w, h, scale_factor))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_scaleToFitPaper(
    doc: Handle,
    range: Handle,
    papersize_code: c_int,
) -> Handle {
    clear_error();
    let (w, h) = match papersize_code {
        0 => (595.0, 842.0),  // A4
        1 => (612.0, 792.0),  // US Letter
        2 => (612.0, 1008.0), // US Legal
        3 => (420.0, 595.0),  // A5
        4 => (842.0, 1190.0), // A3
        _ => {
            set_error(1, &format!("unknown paper size code: {papersize_code}"));
            return 0;
        }
    };
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::scale_to_fit(d, &r, w, h, 1.0))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_stretch(doc: Handle, range: Handle, w: f64, h: f64) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::stretch_pages(d, &r, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_shift(doc: Handle, range: Handle, dx: f64, dy: f64) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::shift_pages(d, &r, dx, dy))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_vflip(doc: Handle, range: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::vflip_pages(d, &r))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_hflip(doc: Handle, range: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::hflip_pages(d, &r))
}

// ── Box operations ──────────────────────────────────────────────────

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_crop(doc: Handle, range: Handle, x: f64, y: f64, w: f64, h: f64) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::crop_pages(d, &r, x, y, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_setMediaBox(
    doc: Handle,
    range: Handle,
    x: f64,
    y: f64,
    w: f64,
    h: f64,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::set_mediabox(d, &r, x, y, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_setCropBox(
    doc: Handle,
    range: Handle,
    x: f64,
    y: f64,
    w: f64,
    h: f64,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::set_cropbox(d, &r, x, y, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_setTrimBox(
    doc: Handle,
    range: Handle,
    x: f64,
    y: f64,
    w: f64,
    h: f64,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::set_trimbox(d, &r, x, y, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_setBleedBox(
    doc: Handle,
    range: Handle,
    x: f64,
    y: f64,
    w: f64,
    h: f64,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::set_bleedbox(d, &r, x, y, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_setArtBox(
    doc: Handle,
    range: Handle,
    x: f64,
    y: f64,
    w: f64,
    h: f64,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::set_artbox(d, &r, x, y, w, h))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_removeCropBox(doc: Handle, range: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::remove_cropbox(d, &r))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_removeTrimBox(doc: Handle, range: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::remove_trimbox(d, &r))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_removeBleedBox(doc: Handle, range: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::remove_bleedbox(d, &r))
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_copyBox(
    doc: Handle,
    range: Handle,
    from_name: *const c_char,
    to_name: *const c_char,
) -> Handle {
    clear_error();
    if from_name.is_null() || to_name.is_null() {
        set_error(1, "cpdf_copyBox: null name");
        return 0;
    }
    let from = unsafe { CStr::from_ptr(from_name) }.to_string_lossy();
    let to = unsafe { CStr::from_ptr(to_name) }.to_string_lossy();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::copy_box(d, &r, &from, &to))
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn cpdf_hardBox(
    doc: Handle,
    range: Handle,
    box_name: *const c_char,
) -> Handle {
    clear_error();
    if box_name.is_null() {
        set_error(1, "cpdf_hardBox: null name");
        return 0;
    }
    let name = unsafe { CStr::from_ptr(box_name) }.to_string_lossy();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::hard_box(d, &r, &name))
}

// ── Chop ────────────────────────────────────────────────────────────

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_chop(
    doc: Handle,
    range: Handle,
    x_pieces: c_int,
    y_pieces: c_int,
    columns: c_int,
    btt: c_int,
    rtl: c_int,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::chop(
        d,
        &r,
        x_pieces as usize,
        y_pieces as usize,
        columns != 0,
        btt != 0,
        rtl != 0,
    ))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_chopHV(doc: Handle, range: Handle, is_h: c_int, line: f64) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let r = get_range!(range);
    ok_or_error!(pages::chop_hv(d, &r, is_h != 0, line))
}

// ── Two-up ──────────────────────────────────────────────────────────

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_twoUp(doc: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    ok_or_error!(pages::twoup(d))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_twoUpStack(doc: Handle) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    ok_or_error!(pages::twoup_stack(d))
}
