//! C FFI for cpdf stamp, watermark, and text operations.

use super::document::{CPDF_DOCS, CPDF_RANGES};
use super::{clear_error, set_error};
use crate::cpdf::stamps::{self, Colour, Font, Justification, Position};
use crate::ffi::Handle;
use std::ffi::{CStr, c_char, c_double, 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
            }
        }
    };
}

fn decode_position(code: c_int, arg: c_double) -> Position {
    match code {
        0 => Position::TopLeft(arg),
        1 => Position::Top(arg),
        2 => Position::TopRight(arg),
        3 => Position::Left(arg),
        4 => Position::Centre,
        5 => Position::Right(arg),
        6 => Position::BottomLeft(arg),
        7 => Position::Bottom(arg),
        8 => Position::BottomRight(arg),
        9 => Position::Diagonal,
        10 => Position::ReverseDiagonal,
        _ => Position::Centre,
    }
}

fn decode_justification(code: c_int) -> Justification {
    match code {
        0 => Justification::Left,
        1 => Justification::Centre,
        2 => Justification::Right,
        _ => Justification::Left,
    }
}

fn cstr_to_str(ptr: *const c_char) -> &'static str {
    if ptr.is_null() {
        ""
    } else {
        unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("")
    }
}

// ── Stamp ───────────────────────────────────────────────────────────

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_stampOn(
    over: Handle,
    under: Handle,
    range_h: Handle,
    scale_to_fit: c_int,
) -> Handle {
    clear_error();
    let o = get_doc_clone!(over);
    let u = get_doc_clone!(under);
    let r = get_range!(range_h);
    ok_or_error!(stamps::stamp_on(o, u, &r, scale_to_fit != 0))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_stampUnder(
    under: Handle,
    over: Handle,
    range_h: Handle,
    scale_to_fit: c_int,
) -> Handle {
    clear_error();
    let u = get_doc_clone!(under);
    let o = get_doc_clone!(over);
    let r = get_range!(range_h);
    ok_or_error!(stamps::stamp_under(u, o, &r, scale_to_fit != 0))
}

#[unsafe(no_mangle)]
pub extern "C" fn cpdf_combinePages(
    under: Handle,
    over: Handle,
    scale_to_fit: c_int,
    swap: c_int,
) -> Handle {
    clear_error();
    let u = get_doc_clone!(under);
    let o = get_doc_clone!(over);
    ok_or_error!(stamps::combine_pages(u, o, scale_to_fit != 0, swap != 0))
}

// ── Text ────────────────────────────────────────────────────────────

#[unsafe(no_mangle)]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn cpdf_addText(
    doc: Handle,
    range_h: Handle,
    text: *const c_char,
    position_code: c_int,
    position_arg: c_double,
    linespacing: c_double,
    font_size: c_double,
    font_code: c_int,
    r: c_double,
    g: c_double,
    b: c_double,
    underneath: c_int,
    opacity: c_double,
    justification_code: c_int,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let rng = get_range!(range_h);
    let t = cstr_to_str(text);
    let pos = decode_position(position_code, position_arg);
    let font = match Font::from_code(font_code) {
        Ok(f) => f,
        Err(e) => {
            set_error(1, &e.to_string());
            return 0;
        }
    };
    let colour = Colour::Rgb(r, g, b);
    let just = decode_justification(justification_code);
    ok_or_error!(stamps::add_text(
        d,
        &rng,
        t,
        &pos,
        linespacing,
        font_size,
        &font,
        &colour,
        underneath != 0,
        opacity,
        just,
    ))
}

#[unsafe(no_mangle)]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn cpdf_addPageNumbers(
    doc: Handle,
    range_h: Handle,
    format: *const c_char,
    position_code: c_int,
    position_arg: c_double,
    font_size: c_double,
    font_code: c_int,
    r: c_double,
    g: c_double,
    b: c_double,
    start_value: c_int,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let rng = get_range!(range_h);
    let fmt = cstr_to_str(format);
    let pos = decode_position(position_code, position_arg);
    let font = match Font::from_code(font_code) {
        Ok(f) => f,
        Err(e) => {
            set_error(1, &e.to_string());
            return 0;
        }
    };
    let colour = Colour::Rgb(r, g, b);
    ok_or_error!(stamps::add_page_numbers(
        d,
        &rng,
        fmt,
        &pos,
        font_size,
        &font,
        &colour,
        start_value.max(1) as usize,
    ))
}

#[unsafe(no_mangle)]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn cpdf_addWatermarkText(
    doc: Handle,
    text: *const c_char,
    font_code: c_int,
    font_size: c_double,
    r: c_double,
    g: c_double,
    b: c_double,
    opacity: c_double,
    angle_deg: c_double,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let t = cstr_to_str(text);
    let font = match Font::from_code(font_code) {
        Ok(f) => f,
        Err(e) => {
            set_error(1, &e.to_string());
            return 0;
        }
    };
    let colour = Colour::Rgb(r, g, b);
    ok_or_error!(stamps::add_watermark_text(
        d, t, &font, font_size, &colour, opacity, angle_deg,
    ))
}

#[unsafe(no_mangle)]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn cpdf_addRectangle(
    doc: Handle,
    range_h: Handle,
    x: c_double,
    y: c_double,
    w: c_double,
    h: c_double,
    r: c_double,
    g: c_double,
    b: c_double,
    outline: c_int,
    linewidth: c_double,
    opacity: c_double,
) -> Handle {
    clear_error();
    let d = get_doc_clone!(doc);
    let rng = get_range!(range_h);
    let colour = Colour::Rgb(r, g, b);
    ok_or_error!(stamps::add_rectangle(
        d,
        &rng,
        x,
        y,
        w,
        h,
        &colour,
        outline != 0,
        linewidth,
        opacity,
    ))
}
