mod common;

use std::collections::HashMap;

use common::{edge_pdf, load_doc, pdf};
use reap::model::Object;
use reap::parser::PdfDoc;
use reap::text::{
    TextExtractionOptions, extract_pdf_diagnostics, extract_text, extract_text_blocks,
};

fn make_ref(obj_num: u32) -> Object {
    Object::Reference {
        obj_num,
        gen_num: 0,
    }
}

fn make_doc(objects: HashMap<(u32, u16), Object>) -> PdfDoc {
    PdfDoc {
        objects,
        trailer: Some(Object::Dictionary(HashMap::from([(
            "Root".to_string(),
            make_ref(1),
        )]))),
    }
}

#[test]
fn diagnostics_large_image_presence_true_for_coverage_fixture() {
    let doc = load_doc(&edge_pdf("edge_large_image_page_coverage.pdf"));
    let diagnostics = extract_pdf_diagnostics(&doc, None);
    assert!(
        diagnostics.large_image_presence,
        "expected large image presence for coverage fixture"
    );
    assert_eq!(diagnostics.large_image_pages, vec![0]);
}

#[test]
fn diagnostics_large_image_presence_false_for_fw2_fixture() {
    let doc = load_doc(&pdf("fw2.pdf"));
    let diagnostics = extract_pdf_diagnostics(&doc, None);
    assert!(
        !diagnostics.large_image_presence,
        "expected no large-image hit for fw2"
    );
    assert!(diagnostics.large_image_pages.is_empty());
}

#[test]
fn diagnostics_large_image_presence_true_for_synthetic_multi_image_no_text() {
    let objects = HashMap::from([
        (
            (1, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Catalog".to_string())),
                ("Pages".to_string(), make_ref(2)),
            ])),
        ),
        (
            (2, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Pages".to_string())),
                ("Kids".to_string(), Object::Array(vec![make_ref(3)])),
                ("Count".to_string(), Object::Integer(1)),
            ])),
        ),
        (
            (3, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Page".to_string())),
                ("Parent".to_string(), make_ref(2)),
                (
                    "MediaBox".to_string(),
                    Object::Array(vec![
                        Object::Integer(0),
                        Object::Integer(0),
                        Object::Integer(200),
                        Object::Integer(200),
                    ]),
                ),
                ("Resources".to_string(), make_ref(4)),
                ("Contents".to_string(), make_ref(5)),
            ])),
        ),
        (
            (4, 0),
            Object::Dictionary(HashMap::from([(
                "XObject".to_string(),
                Object::Dictionary(HashMap::from([("Im1".to_string(), make_ref(6))])),
            )])),
        ),
        (
            (5, 0),
            Object::Stream {
                dict: HashMap::new(),
                data: b"q 40 0 0 40 0 0 cm /Im1 Do Q q 40 0 0 40 60 0 cm /Im1 Do Q q 40 0 0 40 120 0 cm /Im1 Do Q"
                    .to_vec(),
            },
        ),
        (
            (6, 0),
            Object::Stream {
                dict: HashMap::from([
                    ("Type".to_string(), Object::Name("XObject".to_string())),
                    ("Subtype".to_string(), Object::Name("Image".to_string())),
                    ("Width".to_string(), Object::Integer(1)),
                    ("Height".to_string(), Object::Integer(1)),
                    (
                        "ColorSpace".to_string(),
                        Object::Name("DeviceGray".to_string()),
                    ),
                    ("BitsPerComponent".to_string(), Object::Integer(8)),
                ]),
                data: vec![0x00],
            },
        ),
    ]);
    let doc = make_doc(objects);
    let blocks = extract_text_blocks(&doc);
    assert!(
        blocks.is_empty(),
        "expected no extracted text blocks for synthetic image-only fixture, got {}",
        blocks.len()
    );
    let extraction = extract_text(
        &doc,
        TextExtractionOptions {
            include_chars: true,
            include_page_rects: true,
            include_diagnostics: true,
            ..TextExtractionOptions::default()
        },
    );
    let reap::text::TextExtractionResult {
        blocks: diagnostic_blocks,
        diagnostics,
        ..
    } = extraction;
    let diagnostics =
        diagnostics.expect("diagnostics should be present when include_diagnostics is true");
    assert!(
        diagnostic_blocks.is_empty(),
        "expected no blocks via diagnostics extraction path for synthetic image-only fixture, got {}",
        diagnostic_blocks.len()
    );
    assert!(
        diagnostics.large_image_presence,
        "expected synthetic multi-image no-text fixture to trigger large-image detection"
    );
    assert_eq!(diagnostics.large_image_pages, vec![0]);

    let diagnostics_only = extract_pdf_diagnostics(&doc, None);
    assert_eq!(diagnostics_only, diagnostics);
}

#[test]
fn diagnostics_type3_arbitrary_differences_flagged() {
    let doc = load_doc(&edge_pdf(
        "edge_type3_arbitrary_differences_no_tounicode.pdf",
    ));
    let diagnostics = extract_pdf_diagnostics(&doc, None);
    assert!(
        diagnostics.obfuscated_text_present,
        "expected risky Type3 differences to be flagged"
    );
}

#[test]
fn diagnostics_type3_alphabetic_differences_not_flagged() {
    let doc = load_doc(&edge_pdf("edge_type3_fractional_widths.pdf"));
    let diagnostics = extract_pdf_diagnostics(&doc, None);
    assert!(
        !diagnostics.obfuscated_text_present,
        "expected no Type3 risk for alphabetic differences"
    );
}

#[test]
fn diagnostics_inline_image_is_tracked() {
    let objects = HashMap::from([
        (
            (1, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Catalog".to_string())),
                ("Pages".to_string(), make_ref(2)),
            ])),
        ),
        (
            (2, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Pages".to_string())),
                ("Kids".to_string(), Object::Array(vec![make_ref(3)])),
                ("Count".to_string(), Object::Integer(1)),
            ])),
        ),
        (
            (3, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Page".to_string())),
                ("Parent".to_string(), make_ref(2)),
                (
                    "MediaBox".to_string(),
                    Object::Array(vec![
                        Object::Integer(0),
                        Object::Integer(0),
                        Object::Integer(200),
                        Object::Integer(200),
                    ]),
                ),
                ("Contents".to_string(), make_ref(4)),
            ])),
        ),
        (
            (4, 0),
            Object::Stream {
                dict: HashMap::new(),
                data: b"q 200 0 0 200 0 0 cm BI /W 1 /H 1 /BPC 1 /CS /G ID \x00 EI Q".to_vec(),
            },
        ),
    ]);
    let doc = make_doc(objects);
    let diagnostics = extract_pdf_diagnostics(&doc, None);
    assert!(
        diagnostics.large_image_presence,
        "expected inline image to trigger large-image detection"
    );
    assert_eq!(diagnostics.large_image_pages, vec![0]);
}

#[test]
fn diagnostics_images_in_form_and_annotation_appearance_are_tracked() {
    let page_dict = Object::Dictionary(HashMap::from([
        ("Type".to_string(), Object::Name("Page".to_string())),
        ("Parent".to_string(), make_ref(2)),
        (
            "MediaBox".to_string(),
            Object::Array(vec![
                Object::Integer(0),
                Object::Integer(0),
                Object::Integer(200),
                Object::Integer(200),
            ]),
        ),
        (
            "Resources".to_string(),
            Object::Dictionary(HashMap::from([(
                "XObject".to_string(),
                Object::Dictionary(HashMap::from([("Fm1".to_string(), make_ref(4))])),
            )])),
        ),
        ("Contents".to_string(), make_ref(5)),
        ("Annots".to_string(), Object::Array(vec![make_ref(7)])),
    ]));
    let form_stream = Object::Stream {
        dict: HashMap::from([
            ("Type".to_string(), Object::Name("XObject".to_string())),
            ("Subtype".to_string(), Object::Name("Form".to_string())),
            (
                "Resources".to_string(),
                Object::Dictionary(HashMap::from([(
                    "XObject".to_string(),
                    Object::Dictionary(HashMap::from([("Im1".to_string(), make_ref(6))])),
                )])),
            ),
        ]),
        data: b"/Im1 Do".to_vec(),
    };
    let page_contents = Object::Stream {
        dict: HashMap::new(),
        data: b"q 150 0 0 150 0 0 cm /Fm1 Do Q".to_vec(),
    };
    let image_stream = Object::Stream {
        dict: HashMap::from([
            ("Type".to_string(), Object::Name("XObject".to_string())),
            ("Subtype".to_string(), Object::Name("Image".to_string())),
            ("Width".to_string(), Object::Integer(1)),
            ("Height".to_string(), Object::Integer(1)),
            (
                "ColorSpace".to_string(),
                Object::Name("DeviceGray".to_string()),
            ),
            ("BitsPerComponent".to_string(), Object::Integer(8)),
        ]),
        data: vec![0x00],
    };
    let annot = Object::Dictionary(HashMap::from([
        ("Subtype".to_string(), Object::Name("Widget".to_string())),
        (
            "Rect".to_string(),
            Object::Array(vec![
                Object::Integer(0),
                Object::Integer(0),
                Object::Integer(200),
                Object::Integer(200),
            ]),
        ),
        (
            "AP".to_string(),
            Object::Dictionary(HashMap::from([("N".to_string(), make_ref(8))])),
        ),
    ]));
    let ap_stream = Object::Stream {
        dict: HashMap::from([
            ("Type".to_string(), Object::Name("XObject".to_string())),
            ("Subtype".to_string(), Object::Name("Form".to_string())),
            (
                "BBox".to_string(),
                Object::Array(vec![
                    Object::Integer(0),
                    Object::Integer(0),
                    Object::Integer(1),
                    Object::Integer(1),
                ]),
            ),
            (
                "Resources".to_string(),
                Object::Dictionary(HashMap::from([(
                    "XObject".to_string(),
                    Object::Dictionary(HashMap::from([("Im1".to_string(), make_ref(6))])),
                )])),
            ),
        ]),
        data: b"/Im1 Do".to_vec(),
    };
    let objects = HashMap::from([
        (
            (1, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Catalog".to_string())),
                ("Pages".to_string(), make_ref(2)),
            ])),
        ),
        (
            (2, 0),
            Object::Dictionary(HashMap::from([
                ("Type".to_string(), Object::Name("Pages".to_string())),
                ("Kids".to_string(), Object::Array(vec![make_ref(3)])),
                ("Count".to_string(), Object::Integer(1)),
            ])),
        ),
        ((3, 0), page_dict),
        ((4, 0), form_stream),
        ((5, 0), page_contents),
        ((6, 0), image_stream),
        ((7, 0), annot),
        ((8, 0), ap_stream),
    ]);
    let doc = make_doc(objects);
    let diagnostics = extract_pdf_diagnostics(&doc, None);
    assert!(
        diagnostics.large_image_presence,
        "expected large-image detection for form/AP image draws"
    );
    assert_eq!(diagnostics.large_image_pages, vec![0]);
}
