//! Annotations — list, get, set, remove PDF annotations.
//!
//! Zero `unsafe` in this file.

use crate::cpdf::bookmarks::rebuild_xref_data;
use crate::cpdf::document::CpdfDocument;
use crate::cpdf::error::{CpdfError, Result};
use crate::cpdf::pages;

/// List annotations on pages in range as (page_number, annotation_text) pairs.
pub fn list_annotations(doc: &CpdfDocument, range: &[usize]) -> Result<Vec<(usize, String)>> {
    let bytes = doc.to_bytes();
    let text = String::from_utf8_lossy(&bytes);
    let mut result = Vec::new();
    let page_count = doc.page_count();

    let page_obj_nums = pages::get_page_obj_nums_pub(&bytes);

    for &page in range {
        if page < 1 || page > page_count {
            continue;
        }
        let idx = page - 1;
        if idx >= page_obj_nums.len() {
            continue;
        }

        let obj_num = page_obj_nums[idx];
        if obj_num < 0 {
            continue;
        }
        let needle = format!("{obj_num} 0 obj");
        let obj_start = match text.find(&needle) {
            Some(i) => i,
            None => continue,
        };

        let obj_region = &text[obj_start..];
        let endobj = obj_region.find("endobj").unwrap_or(obj_region.len());
        let obj_text = &obj_region[..endobj];

        if let Some(annots_idx) = obj_text.find("/Annots") {
            let rest = &obj_text[annots_idx + 7..];
            let trimmed = rest.trim_start();
            if trimmed.starts_with('[') {
                if let Some(close) = trimmed.find(']') {
                    let arr = &trimmed[1..close];
                    let parts: Vec<&str> = arr.split_whitespace().collect();
                    let mut i = 0;
                    while i + 2 < parts.len() {
                        if parts[i + 2].starts_with('R') {
                            if let Ok(ref_num) = parts[i].parse::<usize>() {
                                let annot_needle = format!("{ref_num} 0 obj");
                                if let Some(as_idx) = text.find(&annot_needle) {
                                    let annot_region = &text[as_idx..];
                                    let ae =
                                        annot_region.find("endobj").unwrap_or(annot_region.len());
                                    let annot_text = &annot_region[..ae];
                                    let subtype =
                                        extract_name(annot_text, "/Subtype").unwrap_or_default();
                                    result.push((page, subtype));
                                }
                            }
                            i += 3;
                        } else {
                            i += 1;
                        }
                    }
                }
            }
        }
    }

    Ok(result)
}

fn extract_name(text: &str, key: &str) -> Option<String> {
    let idx = text.find(key)?;
    let rest = text[idx + key.len()..].trim_start();
    if rest.starts_with('/') {
        let end = rest[1..]
            .find(|c: char| c.is_whitespace() || c == '/' || c == '>')
            .unwrap_or(rest.len() - 1);
        Some(rest[1..1 + end].to_string())
    } else {
        None
    }
}

/// Get annotations as JSON.
pub fn get_annotations_json(doc: &CpdfDocument, range: &[usize]) -> Result<String> {
    let annots = list_annotations(doc, range)?;
    let items: Vec<serde_json::Value> = annots
        .iter()
        .map(|(p, t)| serde_json::json!({"page": p, "subtype": t}))
        .collect();
    serde_json::to_string_pretty(&items).map_err(|e| CpdfError::Parse(e.to_string()))
}

/// Remove annotations from pages in range.
pub fn remove_annotations(doc: CpdfDocument, range: &[usize]) -> Result<CpdfDocument> {
    let bytes = doc.to_bytes();
    let text = String::from_utf8_lossy(&bytes);
    let page_count = doc.page_count();

    let page_obj_nums = pages::get_page_obj_nums_pub(&bytes);

    let mut new_text = text.to_string();
    for &page in range.iter().rev() {
        if page < 1 || page > page_count {
            continue;
        }
        let idx = page - 1;
        if idx >= page_obj_nums.len() {
            continue;
        }

        let obj_num = page_obj_nums[idx];
        if obj_num < 0 {
            continue;
        }
        let needle = format!("{obj_num} 0 obj");
        if let Some(obj_start) = new_text.find(&needle) {
            let obj_region = &new_text[obj_start..];
            let endobj = obj_region.find("endobj").unwrap_or(obj_region.len());
            let obj_text = &obj_region[..endobj];

            if let Some(annots_idx) = obj_text.find("/Annots") {
                let rest = &obj_text[annots_idx + 7..];
                let trimmed = rest.trim_start();
                let end = if trimmed.starts_with('[') {
                    trimmed.find(']').map(|i| i + 1).unwrap_or(trimmed.len())
                } else {
                    trimmed
                        .find(|c: char| c == '/' || c == '>')
                        .unwrap_or(trimmed.len())
                };
                let offset = rest.len() - trimmed.len();
                let remove_start = obj_start + (obj_text.len() - obj_text[annots_idx..].len());
                let remove_end = remove_start + 7 + offset + end;
                new_text = format!("{}{}", &new_text[..remove_start], &new_text[remove_end..]);
            }
        }
    }

    let final_bytes = rebuild_xref_data(new_text.as_bytes());
    CpdfDocument::from_bytes(final_bytes)
}

/// Set annotations from JSON. Currently only supports removal + basic replacement.
pub fn set_annotations_from_json(doc: CpdfDocument, _json: &str) -> Result<CpdfDocument> {
    Err(CpdfError::Unsupported(
        "set_annotations_from_json not yet fully implemented".into(),
    ))
}
