// Generated by Qleany v1.4.8 from feature_use_case.tera
use crate::GetTextAtPositionDto;
use crate::TextAtPositionDto;
use anyhow::{Result, anyhow};
use common::database::QueryUnitOfWork;
use common::database::rope_helpers::block_char_length;
use common::database::rope_helpers::block_content_via_store;
use common::direct_access::document::document_repository::DocumentRelationshipField;
use common::direct_access::frame::frame_repository::FrameRelationshipField;
use common::direct_access::root::root_repository::RootRelationshipField;
use common::direct_access::table::TableRelationshipField;
use common::entities::{Block, Document, Frame, Root, TableCell};
use common::format_runs::{InlineContent, synth_element_id};
use common::format_runs_query::inline_segments_for_block;
use common::types::{EntityId, ROOT_ENTITY_ID};

pub trait GetTextAtPositionUnitOfWorkFactoryTrait: Send + Sync {
    fn create(&self) -> Box<dyn GetTextAtPositionUnitOfWorkTrait>;
}

#[macros::uow_action(entity = "Root", action = "GetRO")]
#[macros::uow_action(entity = "Root", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Document", action = "GetRO")]
#[macros::uow_action(entity = "Document", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Frame", action = "GetRO")]
#[macros::uow_action(entity = "Frame", action = "GetMultiRO")]
#[macros::uow_action(entity = "Frame", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Block", action = "GetRO")]
#[macros::uow_action(entity = "Block", action = "GetMultiRO")]
#[macros::uow_action(entity = "Block", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "Table", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "TableCell", action = "GetMultiRO")]
pub trait GetTextAtPositionUnitOfWorkTrait: QueryUnitOfWork {}

pub struct GetTextAtPositionUseCase {
    uow_factory: Box<dyn GetTextAtPositionUnitOfWorkFactoryTrait>,
}

impl GetTextAtPositionUseCase {
    pub fn new(uow_factory: Box<dyn GetTextAtPositionUnitOfWorkFactoryTrait>) -> Self {
        GetTextAtPositionUseCase { uow_factory }
    }

    pub fn execute(&mut self, dto: &GetTextAtPositionDto) -> Result<TextAtPositionDto> {
        let uow = self.uow_factory.create();
        uow.begin_transaction()?;

        let position = dto.position;
        let length = dto.length;

        if length < 0 {
            uow.end_transaction()?;
            return Ok(TextAtPositionDto {
                text: String::new(),
                block_id: 0,
                element_id: 0,
            });
        }

        // Get Root(1) -> Document -> root frame -> collect blocks recursively
        let root = uow
            .get_root(&ROOT_ENTITY_ID)?
            .ok_or_else(|| anyhow!("Root entity not found"))?;

        let doc_ids = uow.get_root_relationship(&root.id, &RootRelationshipField::Document)?;
        let doc_id = *doc_ids
            .first()
            .ok_or_else(|| anyhow!("Root has no document"))?;

        let frame_ids =
            uow.get_document_relationship(&doc_id, &DocumentRelationshipField::Frames)?;
        let frame_id = *frame_ids
            .first()
            .ok_or_else(|| anyhow!("Document has no frames"))?;

        // Collect all block IDs in document order, traversing into nested frames
        let ordered_block_ids = collect_block_ids(&*uow, &frame_id)?;

        // Get all blocks and assign computed positions
        let blocks_opt = uow.get_block_multi(&ordered_block_ids)?;
        let mut blocks: Vec<Block> = blocks_opt.into_iter().flatten().collect();
        let store = uow.store();
        // Assign on-the-fly positions so they match the true document layout
        let mut running: i64 = 0;
        for block in &mut blocks {
            block.document_position = running;
            running += block_char_length(block, &store) + 1;
        }

        // Find the block containing the requested position.
        // Use <= on the upper bound so that a position at the very end of a block is valid.
        let first_block_idx = blocks
            .iter()
            .position(|b| {
                position >= b.document_position
                    && position <= b.document_position + block_char_length(b, &store)
            })
            .ok_or_else(|| anyhow!("Position {} is out of document range", position))?;

        let first_block = &blocks[first_block_idx];
        let offset_in_first_block = (position - first_block.document_position) as usize;

        // Collect text spanning across blocks if needed
        let mut collected_text = String::new();
        let mut remaining = length as usize;
        let mut first_element_id: EntityId = 0;
        let mut found_first_element = false;

        for (block_idx, block) in blocks.iter().enumerate().skip(first_block_idx) {
            if remaining == 0 {
                break;
            }

            let block_offset = if block_idx == first_block_idx {
                offset_in_first_block
            } else {
                // Add block separator between blocks
                if remaining > 0 {
                    collected_text.push('\n');
                    remaining = remaining.saturating_sub(1);
                }
                0
            };

            if remaining == 0 {
                break;
            }

            // Synthesize this block's segments from format_runs to find the first
            // element ID and extract text.
            let block_text = block_content_via_store(block, &uow.store());
            let owned_segments = inline_segments_for_block(&uow.store(), block.id, &block_text);

            let mut current_char_offset: usize = 0;
            let mut current_byte_offset: u32 = 0;

            for seg in &owned_segments {
                if remaining == 0 {
                    break;
                }

                let elem_text = match &seg.content {
                    InlineContent::Text(s) => s.clone(),
                    InlineContent::Image { .. } => "\u{FFFC}".to_string(),
                    InlineContent::Empty => String::new(),
                };

                let elem_char_len = elem_text.chars().count();
                let elem_chars: Vec<char> = elem_text.chars().collect();

                if current_char_offset + elem_char_len > block_offset {
                    // This element overlaps with our read range
                    let start_in_elem = block_offset.saturating_sub(current_char_offset);
                    let available = elem_char_len - start_in_elem;
                    let take = available.min(remaining);

                    let slice: String = elem_chars[start_in_elem..start_in_elem + take]
                        .iter()
                        .collect();
                    collected_text.push_str(&slice);
                    remaining -= take;

                    if !found_first_element {
                        // Compute synthetic element ID from block_id and byte_start
                        first_element_id = synth_element_id(block.id, current_byte_offset);
                        found_first_element = true;
                    }
                }

                // Advance byte offset (Text contributes its UTF-8 length, Image contributes 0)
                match &seg.content {
                    InlineContent::Text(s) => current_byte_offset += s.len() as u32,
                    InlineContent::Image { .. } => {}
                    InlineContent::Empty => {}
                }

                current_char_offset += elem_char_len;
            }
        }

        uow.end_transaction()?;

        Ok(TextAtPositionDto {
            text: collected_text,
            block_id: blocks[first_block_idx].id as i64,
            element_id: first_element_id as i64,
        })
    }
}

/// Collect all block IDs in document order by recursing into sub-frames.
/// Table anchor frames (where `frame.table` is set) are expanded by traversing
/// Table -> TableCell -> cell_frame in row-major order.
fn collect_block_ids(
    uow: &dyn GetTextAtPositionUnitOfWorkTrait,
    frame_id: &EntityId,
) -> Result<Vec<EntityId>> {
    let frame = uow
        .get_frame(frame_id)?
        .ok_or_else(|| anyhow!("Frame not found"))?;

    if !frame.child_order.is_empty() {
        let mut block_ids = Vec::new();
        for &entry in &frame.child_order {
            if entry > 0 {
                block_ids.push(entry as EntityId);
            } else if entry < 0 {
                let sub_frame_id = (-entry) as EntityId;
                let sub_frame = uow
                    .get_frame(&sub_frame_id)?
                    .ok_or_else(|| anyhow!("Sub-frame not found"))?;
                if let Some(table_id) = sub_frame.table {
                    let cell_ids =
                        uow.get_table_relationship(&table_id, &TableRelationshipField::Cells)?;
                    let cells_opt = uow.get_table_cell_multi(&cell_ids)?;
                    let mut cells: Vec<_> = cells_opt.into_iter().flatten().collect();
                    cells.sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
                    for cell in cells {
                        if let Some(cf_id) = cell.cell_frame {
                            let cf_blocks = collect_block_ids(uow, &cf_id)?;
                            block_ids.extend(cf_blocks);
                        }
                    }
                } else {
                    let sub_ids = collect_block_ids(uow, &sub_frame_id)?;
                    block_ids.extend(sub_ids);
                }
            }
        }
        Ok(block_ids)
    } else {
        uow.get_frame_relationship(frame_id, &FrameRelationshipField::Blocks)
    }
}
