//! Platypus - Page Layout and Typography Using Scripts
//!
//! High-level document composition framework inspired by ReportLab Platypus.
//! Provides automatic pagination, multi-page content flow, and professional layouts.

use super::error::{EnhancedError, Result};
use std::sync::{Arc, Mutex};

/// Document template with automatic pagination
#[derive(Debug)]
pub struct DocTemplate {
    pub filename: String,
    pub page_width: f32,
    pub page_height: f32,
    pub left_margin: f32,
    pub right_margin: f32,
    pub top_margin: f32,
    pub bottom_margin: f32,
    pub page_templates: Vec<PageTemplate>,
    pub show_boundary: bool,
}

impl DocTemplate {
    /// Create new document template
    pub fn new(filename: impl Into<String>) -> Self {
        Self {
            filename: filename.into(),
            page_width: 612.0, // Letter
            page_height: 792.0,
            left_margin: 72.0,
            right_margin: 72.0,
            top_margin: 72.0,
            bottom_margin: 72.0,
            page_templates: vec![],
            show_boundary: false,
        }
    }

    /// Add page template
    pub fn add_page_template(&mut self, template: PageTemplate) {
        self.page_templates.push(template);
    }

    /// Build document from story
    pub fn build(&self, story: &mut Story) -> Result<()> {
        use super::writer::PdfWriter;

        let mut writer = PdfWriter::new();
        let content_height = self.page_height - self.top_margin - self.bottom_margin;
        let content_width = self.page_width - self.left_margin - self.right_margin;

        let frame = self
            .page_templates
            .first()
            .and_then(|pt| pt.frames.first())
            .map(|f| f.clone())
            .unwrap_or_else(|| {
                Frame::new(
                    "main",
                    self.left_margin,
                    self.bottom_margin,
                    content_width,
                    content_height,
                )
            });

        let avail_w = frame.available_width();
        let avail_h = frame.available_height();

        let mut page_content = String::new();
        let mut cursor_y = frame.y1 + frame.height - frame.top_padding;

        for flowable in &mut story.flowables {
            let (w, h) = flowable.wrap(avail_w, avail_h);

            if h == 0.0 && w == 0.0 {
                if !page_content.is_empty() {
                    writer.add_page_with_content(
                        self.page_width,
                        self.page_height,
                        &page_content,
                    )?;
                }
                page_content.clear();
                cursor_y = frame.y1 + frame.height - frame.top_padding;
                continue;
            }

            if cursor_y - h < frame.y1 + frame.bottom_padding {
                if !page_content.is_empty() {
                    writer.add_page_with_content(
                        self.page_width,
                        self.page_height,
                        &page_content,
                    )?;
                }
                page_content.clear();
                cursor_y = frame.y1 + frame.height - frame.top_padding;
            }

            let draw_x = frame.x1 + frame.left_padding;
            let draw_y = cursor_y - h;
            let content = flowable.draw(draw_x, draw_y)?;
            page_content.push_str(&content);
            cursor_y = draw_y;
        }

        if !page_content.is_empty() {
            writer.add_page_with_content(self.page_width, self.page_height, &page_content)?;
        }

        writer.save(&self.filename)?;
        Ok(())
    }
}

/// Page template defining layout
#[derive(Debug, Clone)]
pub struct PageTemplate {
    pub id: String,
    pub frames: Vec<Frame>,
    // Note: Callbacks would be Box<dyn Fn> in real implementation
}

impl PageTemplate {
    /// Create new page template
    pub fn new(id: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            frames: vec![],
        }
    }

    /// Add frame to template
    pub fn add_frame(&mut self, frame: Frame) {
        self.frames.push(frame);
    }
}

/// Frame - rectangular region for content
#[derive(Debug, Clone)]
pub struct Frame {
    pub id: String,
    pub x1: f32,
    pub y1: f32,
    pub width: f32,
    pub height: f32,
    pub left_padding: f32,
    pub bottom_padding: f32,
    pub right_padding: f32,
    pub top_padding: f32,
    pub show_boundary: bool,
}

impl Frame {
    /// Create new frame
    pub fn new(id: impl Into<String>, x1: f32, y1: f32, width: f32, height: f32) -> Self {
        Self {
            id: id.into(),
            x1,
            y1,
            width,
            height,
            left_padding: 6.0,
            bottom_padding: 6.0,
            right_padding: 6.0,
            top_padding: 6.0,
            show_boundary: false,
        }
    }

    /// Get available width (excluding padding)
    pub fn available_width(&self) -> f32 {
        self.width - self.left_padding - self.right_padding
    }

    /// Get available height (excluding padding)
    pub fn available_height(&self) -> f32 {
        self.height - self.top_padding - self.bottom_padding
    }
}

/// Story - list of flowable elements
#[derive(Debug, Default)]
pub struct Story {
    pub flowables: Vec<Box<dyn Flowable>>,
}

impl Story {
    /// Create new story
    pub fn new() -> Self {
        Self { flowables: vec![] }
    }

    /// Add flowable to story
    pub fn add<F: Flowable + 'static>(&mut self, flowable: F) {
        self.flowables.push(Box::new(flowable));
    }
}

/// Flowable trait - elements that flow in document
pub trait Flowable: std::fmt::Debug + Send + Sync {
    /// Calculate required width and height
    fn wrap(&mut self, available_width: f32, available_height: f32) -> (f32, f32);

    /// Draw the flowable at position, returns PDF content stream operators
    fn draw(&self, x: f32, y: f32) -> Result<String>;

    /// Split if doesn't fit (returns parts for multiple pages)
    fn split(&self, available_height: f32) -> Vec<Box<dyn Flowable>>;

    /// Get spacing before
    fn get_space_before(&self) -> f32 {
        0.0
    }

    /// Get spacing after
    fn get_space_after(&self) -> f32 {
        0.0
    }

    /// Can this flowable be split?
    fn is_splittable(&self) -> bool {
        false
    }
}

/// Paragraph flowable
#[derive(Debug, Clone)]
pub struct Paragraph {
    pub text: String,
    pub style_name: String,
    width: f32,
    height: f32,
}

impl Paragraph {
    /// Create new paragraph
    pub fn new(text: impl Into<String>, style_name: impl Into<String>) -> Self {
        Self {
            text: text.into(),
            style_name: style_name.into(),
            width: 0.0,
            height: 0.0,
        }
    }
}

impl Flowable for Paragraph {
    fn wrap(&mut self, available_width: f32, _available_height: f32) -> (f32, f32) {
        let avg_char_width = 6.0;
        let line_height = 14.0;
        let words: Vec<&str> = self.text.split_whitespace().collect();
        let mut lines = 1;
        let mut line_width = 0.0;
        for word in &words {
            let w = (word.len() as f32 + 1.0) * avg_char_width;
            if line_width + w > available_width && line_width > 0.0 {
                lines += 1;
                line_width = w;
            } else {
                line_width += w;
            }
        }
        let w = available_width;
        let h = lines as f32 * line_height;
        self.width = w;
        self.height = h;
        (w, h)
    }

    fn draw(&self, x: f32, y: f32) -> Result<String> {
        let escaped = self
            .text
            .replace('\\', "\\\\")
            .replace('(', "\\(")
            .replace(')', "\\)");
        Ok(format!(
            "BT 0 0 0 rg /Helvetica 12 Tf {} {} Td ({}) Tj ET ",
            x, y, escaped
        ))
    }

    fn split(&self, _available_height: f32) -> Vec<Box<dyn Flowable>> {
        vec![]
    }

    fn is_splittable(&self) -> bool {
        true
    }
}

/// Spacer flowable
#[derive(Debug, Clone, Copy)]
pub struct Spacer {
    pub width: f32,
    pub height: f32,
}

impl Spacer {
    /// Create new spacer
    pub fn new(width: f32, height: f32) -> Self {
        Self { width, height }
    }
}

impl Flowable for Spacer {
    fn wrap(&mut self, _available_width: f32, _available_height: f32) -> (f32, f32) {
        (self.width, self.height)
    }

    fn draw(&self, _x: f32, _y: f32) -> Result<String> {
        Ok(String::new())
    }

    fn split(&self, _available_height: f32) -> Vec<Box<dyn Flowable>> {
        vec![]
    }
}

/// Page break flowable
#[derive(Debug, Clone, Copy)]
pub struct PageBreak;

impl Flowable for PageBreak {
    fn wrap(&mut self, _available_width: f32, _available_height: f32) -> (f32, f32) {
        (0.0, 0.0)
    }

    fn draw(&self, _x: f32, _y: f32) -> Result<String> {
        Ok(String::new())
    }

    fn split(&self, _available_height: f32) -> Vec<Box<dyn Flowable>> {
        vec![]
    }
}

/// Conditional page break
#[derive(Debug, Clone, Copy)]
pub struct CondPageBreak {
    pub min_space: f32,
}

impl CondPageBreak {
    /// Create conditional page break
    pub fn new(min_space: f32) -> Self {
        Self { min_space }
    }
}

impl Flowable for CondPageBreak {
    fn wrap(&mut self, _available_width: f32, _available_height: f32) -> (f32, f32) {
        (0.0, 0.0)
    }

    fn draw(&self, _x: f32, _y: f32) -> Result<String> {
        Ok(String::new())
    }

    fn split(&self, _available_height: f32) -> Vec<Box<dyn Flowable>> {
        vec![]
    }
}

/// Keep together flowable
#[derive(Debug)]
pub struct KeepTogether {
    pub flowables: Vec<Box<dyn Flowable>>,
    pub max_height: Option<f32>,
    heights: Vec<f32>,
}

impl KeepTogether {
    /// Create keep together
    pub fn new(flowables: Vec<Box<dyn Flowable>>) -> Self {
        Self {
            flowables,
            max_height: None,
            heights: Vec::new(),
        }
    }
}

impl Flowable for KeepTogether {
    fn wrap(&mut self, available_width: f32, available_height: f32) -> (f32, f32) {
        self.heights.clear();
        let mut total_height = 0.0;
        let mut max_width: f32 = 0.0;

        for flowable in &mut self.flowables {
            let (w, h) = flowable.wrap(available_width, available_height);
            max_width = max_width.max(w);
            total_height += h;
            self.heights.push(h);
        }

        (max_width, total_height)
    }

    fn draw(&self, x: f32, y: f32) -> Result<String> {
        let mut content = String::new();
        let mut cy = y;
        for (flowable, &h) in self.flowables.iter().zip(self.heights.iter()) {
            let c = flowable.draw(x, cy)?;
            content.push_str(&c);
            cy -= h;
        }
        Ok(content)
    }

    fn split(&self, _available_height: f32) -> Vec<Box<dyn Flowable>> {
        vec![]
    }
}

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

    #[test]
    fn test_doc_template_creation() {
        let doc = DocTemplate::new("output.pdf");
        assert_eq!(doc.filename, "output.pdf");
        assert_eq!(doc.page_width, 612.0);
        assert_eq!(doc.page_height, 792.0);
    }

    #[test]
    fn test_frame_creation() {
        let frame = Frame::new("main", 72.0, 72.0, 468.0, 648.0);
        assert_eq!(frame.id, "main");
        assert_eq!(frame.available_width(), 468.0 - 12.0);
        assert_eq!(frame.available_height(), 648.0 - 12.0);
    }

    #[test]
    fn test_paragraph_creation() {
        let para = Paragraph::new("Test content", "Normal");
        assert_eq!(para.text, "Test content");
        assert_eq!(para.style_name, "Normal");
    }

    #[test]
    fn test_spacer() {
        let spacer = Spacer::new(10.0, 20.0);
        assert_eq!(spacer.width, 10.0);
        assert_eq!(spacer.height, 20.0);
    }
}
