//! Page manipulation helper inspired by qpdf's QPDFPageObjectHelper

use std::collections::HashMap;

use super::object_handle::Rectangle;
use crate::pdf::object::{Dict, Name, ObjRef, Object};

fn extract_rectangle(dict: &Dict, key: &str) -> Option<Rectangle> {
    let obj = dict.get(&Name::new(key))?;
    Rectangle::from_object(obj)
}

fn rectangle_to_array(rect: Rectangle) -> Object {
    rect.to_object()
}

pub struct PageHelper<'a> {
    page: &'a Dict,
    page_ref: ObjRef,
}

impl<'a> PageHelper<'a> {
    pub fn new(page: &'a Dict, page_ref: ObjRef) -> Self {
        Self { page, page_ref }
    }

    pub fn media_box(&self) -> Option<Rectangle> {
        extract_rectangle(self.page, "MediaBox")
    }

    pub fn crop_box(&self) -> Option<Rectangle> {
        extract_rectangle(self.page, "CropBox").or_else(|| self.media_box())
    }

    pub fn bleed_box(&self) -> Option<Rectangle> {
        extract_rectangle(self.page, "BleedBox").or_else(|| self.crop_box())
    }

    pub fn trim_box(&self) -> Option<Rectangle> {
        extract_rectangle(self.page, "TrimBox").or_else(|| self.crop_box())
    }

    pub fn art_box(&self) -> Option<Rectangle> {
        extract_rectangle(self.page, "ArtBox").or_else(|| self.crop_box())
    }

    pub fn rotation(&self) -> i32 {
        let rot = self.page.get(&Name::new("Rotate")).and_then(Object::as_int);
        match rot {
            Some(r) => {
                let r = (r % 360) as i32;
                if r < 0 { r + 360 } else { r }
            }
            None => 0,
        }
    }

    pub fn get_annotations(&self) -> Vec<&Object> {
        self.page
            .get(&Name::new("Annots"))
            .and_then(Object::as_array)
            .map(|arr| arr.iter().collect())
            .unwrap_or_default()
    }

    pub fn get_contents(&self) -> Vec<&Object> {
        let contents = self.page.get(&Name::new("Contents"));
        match contents {
            Some(Object::Array(arr)) => arr.iter().collect(),
            Some(obj) => vec![obj],
            None => vec![],
        }
    }

    pub fn get_resources(&self) -> Option<&Object> {
        self.page.get(&Name::new("Resources"))
    }

    pub fn is_page_object(dict: &Dict) -> bool {
        dict.get(&Name::new("Type"))
            .and_then(Object::as_name)
            .map(|n| n.as_str() == "Page")
            .unwrap_or(false)
    }

    pub fn is_pages_object(dict: &Dict) -> bool {
        dict.get(&Name::new("Type"))
            .and_then(Object::as_name)
            .map(|n| n.as_str() == "Pages")
            .unwrap_or(false)
    }
}

pub struct PageHelperMut<'a> {
    page: &'a mut Dict,
    page_ref: ObjRef,
}

impl<'a> PageHelperMut<'a> {
    pub fn new(page: &'a mut Dict, page_ref: ObjRef) -> Self {
        Self { page, page_ref }
    }

    pub fn set_media_box(&mut self, rect: Rectangle) {
        self.page
            .insert(Name::new("MediaBox"), rectangle_to_array(rect));
    }

    pub fn set_crop_box(&mut self, rect: Rectangle) {
        self.page
            .insert(Name::new("CropBox"), rectangle_to_array(rect));
    }

    pub fn set_rotation(&mut self, angle: i32) {
        if angle % 90 != 0 {
            return;
        }
        let normalized = ((angle % 360) + 360) % 360;
        self.page
            .insert(Name::new("Rotate"), Object::Int(normalized as i64));
    }

    pub fn add_content_stream(&mut self, content_ref: ObjRef, first: bool) {
        let contents_key = Name::new("Contents");
        let new_ref = Object::Ref(content_ref);

        let existing = self.page.get(&contents_key).cloned();
        match existing {
            Some(Object::Array(mut arr)) => {
                if first {
                    arr.insert(0, new_ref);
                } else {
                    arr.push(new_ref);
                }
                self.page.insert(contents_key, Object::Array(arr));
            }
            Some(obj) => {
                let arr = if first {
                    vec![new_ref, obj]
                } else {
                    vec![obj, new_ref]
                };
                self.page.insert(contents_key, Object::Array(arr));
            }
            None => {
                self.page.insert(contents_key, new_ref);
            }
        }
    }

    pub fn remove_unreferenced_resources(&mut self) {
        // Placeholder: would scan content streams for resource names
    }

    pub fn shallow_copy(&self) -> Dict {
        self.page.clone()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_page_dict() -> Dict {
        let mut dict = HashMap::new();
        dict.insert(Name::new("Type"), Object::Name(Name::new("Page")));
        dict.insert(
            Name::new("MediaBox"),
            Object::Array(vec![
                Object::Real(0.0),
                Object::Real(0.0),
                Object::Real(612.0),
                Object::Real(792.0),
            ]),
        );
        dict.insert(
            Name::new("CropBox"),
            Object::Array(vec![
                Object::Real(36.0),
                Object::Real(36.0),
                Object::Real(576.0),
                Object::Real(756.0),
            ]),
        );
        dict.insert(Name::new("Rotate"), Object::Int(90));
        dict.insert(Name::new("Contents"), Object::Ref(ObjRef::new(5, 0)));
        dict
    }

    #[test]
    fn test_box_extraction() {
        let dict = make_page_dict();
        let helper = PageHelper::new(&dict, ObjRef::new(1, 0));

        let media = helper.media_box().unwrap();
        assert_eq!(media.llx, 0.0);
        assert_eq!(media.ury, 792.0);

        let crop = helper.crop_box().unwrap();
        assert_eq!(crop.llx, 36.0);
        assert_eq!(crop.ury, 756.0);

        let bleed = helper.bleed_box().unwrap();
        assert_eq!(bleed.llx, 36.0);
    }

    #[test]
    fn test_rotation() {
        let dict = make_page_dict();
        let helper = PageHelper::new(&dict, ObjRef::new(1, 0));
        assert_eq!(helper.rotation(), 90);
    }

    #[test]
    fn test_content_management() {
        let dict = make_page_dict();
        let helper = PageHelper::new(&dict, ObjRef::new(1, 0));
        let contents = helper.get_contents();
        assert_eq!(contents.len(), 1);
    }

    #[test]
    fn test_is_page_object() {
        let dict = make_page_dict();
        assert!(PageHelper::is_page_object(&dict));

        let mut not_page = HashMap::new();
        not_page.insert(Name::new("Type"), Object::Name(Name::new("Pages")));
        assert!(!PageHelper::is_page_object(&not_page));
    }

    #[test]
    fn test_set_boxes_and_rotation() {
        let mut dict = make_page_dict();
        let mut helper = PageHelperMut::new(&mut dict, ObjRef::new(1, 0));

        let rect = Rectangle {
            llx: 10.0,
            lly: 10.0,
            urx: 100.0,
            ury: 100.0,
        };
        helper.set_media_box(rect);
        helper.set_rotation(180);

        let helper = PageHelper::new(&dict, ObjRef::new(1, 0));
        assert_eq!(helper.rotation(), 180);
        let media = helper.media_box().unwrap();
        assert_eq!(media.llx, 10.0);
    }

    #[test]
    fn test_add_content_stream() {
        let mut dict = make_page_dict();
        {
            let mut helper = PageHelperMut::new(&mut dict, ObjRef::new(1, 0));
            helper.add_content_stream(ObjRef::new(10, 0), true);
        }
        let helper = PageHelper::new(&dict, ObjRef::new(1, 0));
        let contents = helper.get_contents();
        assert_eq!(contents.len(), 2);
    }
}
