// Generated by Qleany v1.4.8 from feature_use_case.tera
use crate::BlockInfoDto;
use crate::GetBlockAtPositionDto;
use anyhow::{Result, anyhow};
use common::database::QueryUnitOfWork;
use common::database::block_offset_index::OffsetMarker;
use common::database::rope_helpers::{block_char_length, find_block_at_char_position};
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::types::{EntityId, ROOT_ENTITY_ID};

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

#[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 = "Table", action = "GetRelationshipRO")]
#[macros::uow_action(entity = "TableCell", action = "GetMultiRO")]
pub trait GetBlockAtPositionUnitOfWorkTrait: QueryUnitOfWork {}

pub struct GetBlockAtPositionUseCase {
    uow_factory: Box<dyn GetBlockAtPositionUnitOfWorkFactoryTrait>,
}

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

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

        let position = dto.position;
        let store = uow.store();

        // Fast path: O(log n) via the rope index. Only valid for flat
        // (no-table) documents — table cell content lives at separate
        // rope byte ranges (plan §1.6). Falls back to the slow walk
        // below for tabled docs.
        if let Some((init_block_id, init_char_in_block, init_block_char_start)) =
            find_block_at_char_position(&store, position)
        {
            let mut block_id = init_block_id;
            let mut block_char_start = init_block_char_start;
            let mut block = uow
                .get_block(&block_id)?
                .ok_or_else(|| anyhow!("Block not found"))?;
            let mut block_length = block_char_length(&block, &store);

            // Separator semantic: at the inter-block `\n` (char_in_block
            // == block_char_len of a non-empty block with a successor),
            // belong to the NEXT block. Empty blocks stay put.
            if block_length > 0 && init_char_in_block == block_length {
                let next_block_info: Option<(EntityId, u32)> = {
                    let offsets = store.block_offsets.read();
                    offsets
                        .position_of(OffsetMarker::Block(block_id))
                        .and_then(|cur_idx| offsets.entries.get(cur_idx + 1).copied())
                        .and_then(|(m, bs)| m.as_block().map(|id| (id, bs)))
                };
                if let Some((next_block_id, next_bs)) = next_block_info {
                    let next_char_start = store.rope.read().byte_to_char(next_bs as usize) as i64;
                    block_id = next_block_id;
                    block_char_start = next_char_start;
                    block = uow
                        .get_block(&block_id)?
                        .ok_or_else(|| anyhow!("Block not found"))?;
                    block_length = block_char_length(&block, &store);
                }
            }

            // Document block-order index = number of Block markers preceding
            // this one. Must exclude TableAnchor sentinel entries: the raw
            // entry index counts them, which would inflate every block_number
            // after a table (and run past the real block count).
            let block_number = {
                let offsets = store.block_offsets.read();
                offsets
                    .position_of(OffsetMarker::Block(block_id))
                    .map(|idx| {
                        offsets.entries[..idx]
                            .iter()
                            .filter(|(m, _)| m.is_block())
                            .count()
                    })
                    .unwrap_or(0) as i64
            };
            uow.end_transaction()?;
            return Ok(BlockInfoDto {
                block_id: block_id as i64,
                block_start: block_char_start,
                block_length,
                block_number,
            });
        }

        // Slow path: tabled document — walk the block tree.
        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 by traversing child_order recursively
        let ordered_block_ids = collect_block_ids(&*uow, &frame_id)?;

        // Walk blocks computing positions on the fly.
        // Separator (at block_end) maps to the next block.
        // Empty blocks (text_length == 0) match when position == running_pos.
        let mut running_pos: i64 = 0;
        for (block_number, &block_id) in ordered_block_ids.iter().enumerate() {
            let block = uow
                .get_block(&block_id)?
                .ok_or_else(|| anyhow!("Block not found"))?;
            let block_end = running_pos + block_char_length(&block, &store);

            // Position before this block's start means it was at the separator
            // before this block — separator maps to next block (this one).
            if position < running_pos {
                uow.end_transaction()?;
                return Ok(BlockInfoDto {
                    block_id: block.id as i64,
                    block_start: running_pos,
                    block_length: block_char_length(&block, &store),
                    block_number: block_number as i64,
                });
            }

            // Within block text, or empty block at exactly this position
            if position < block_end
                || (block_char_length(&block, &store) == 0 && position == running_pos)
            {
                uow.end_transaction()?;
                return Ok(BlockInfoDto {
                    block_id: block.id as i64,
                    block_start: running_pos,
                    block_length: block_char_length(&block, &store),
                    block_number: block_number as i64,
                });
            }

            running_pos = block_end + 1;
        }

        // Fallback: position is at or past the end of the document — return last block
        if let Some(&last_id) = ordered_block_ids.last() {
            let block = uow
                .get_block(&last_id)?
                .ok_or_else(|| anyhow!("Block not found"))?;
            // Recompute position for last block
            let mut pos: i64 = 0;
            for &id in &ordered_block_ids[..ordered_block_ids.len() - 1] {
                if let Some(b) = uow.get_block(&id)? {
                    pos += block_char_length(&b, &store) + 1;
                }
            }
            uow.end_transaction()?;
            return Ok(BlockInfoDto {
                block_id: block.id as i64,
                block_start: pos,
                block_length: block_char_length(&block, &store),
                block_number: (ordered_block_ids.len() - 1) as i64,
            });
        }

        uow.end_transaction()?;
        Err(anyhow!("No blocks found in document"))
    }
}

/// 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 GetBlockAtPositionUnitOfWorkTrait,
    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 {
                    // Table anchor frame: collect blocks from cell frames in row-major order
                    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)
    }
}
