use crate::RemoveTableDto;
use anyhow::{Result, anyhow};
use common::database::CommandUnitOfWork;
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::table_repository::TableRelationshipField;
use common::entities::{Block, Document, Frame, Root, Table, TableCell};
use common::snapshot::EntityTreeSnapshot;
use common::types::{EntityId, ROOT_ENTITY_ID};
use common::undo_redo::UndoRedoCommand;
use std::any::Any;
pub trait RemoveTableUnitOfWorkFactoryTrait: Send + Sync {
fn create(&self) -> Box<dyn RemoveTableUnitOfWorkTrait>;
}
#[macros::uow_action(entity = "Root", action = "Get")]
#[macros::uow_action(entity = "Root", action = "GetRelationship")]
#[macros::uow_action(entity = "Document", action = "Get")]
#[macros::uow_action(entity = "Document", action = "Update")]
#[macros::uow_action(entity = "Document", action = "GetRelationship")]
#[macros::uow_action(entity = "Document", action = "Snapshot")]
#[macros::uow_action(entity = "Document", action = "Restore")]
#[macros::uow_action(entity = "Frame", action = "Get")]
#[macros::uow_action(entity = "Frame", action = "GetMulti")]
#[macros::uow_action(entity = "Frame", action = "Update")]
#[macros::uow_action(entity = "Frame", action = "Remove")]
#[macros::uow_action(entity = "Frame", action = "GetRelationship")]
#[macros::uow_action(entity = "Block", action = "GetMulti")]
#[macros::uow_action(entity = "Block", action = "UpdateMulti")]
#[macros::uow_action(entity = "Table", action = "Get")]
#[macros::uow_action(entity = "Table", action = "Remove")]
#[macros::uow_action(entity = "Table", action = "GetRelationship")]
#[macros::uow_action(entity = "TableCell", action = "GetMulti")]
pub trait RemoveTableUnitOfWorkTrait: CommandUnitOfWork {}
pub struct RemoveTableUseCase {
uow_factory: Box<dyn RemoveTableUnitOfWorkFactoryTrait>,
undo_snapshot: Option<EntityTreeSnapshot>,
last_dto: Option<RemoveTableDto>,
}
fn execute_remove_table(
uow: &mut Box<dyn RemoveTableUnitOfWorkTrait>,
dto: &RemoveTableDto,
) -> Result<EntityTreeSnapshot> {
let table_id = dto.table_id as EntityId;
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 document = uow
.get_document(&doc_id)?
.ok_or_else(|| anyhow!("Document not found"))?;
let _table = uow
.get_table(&table_id)?
.ok_or_else(|| anyhow!("Table {} not found", table_id))?;
let snapshot = uow.snapshot_document(&[doc_id])?;
let now = chrono::Utc::now();
let cell_ids = uow.get_table_relationship(&table_id, &TableRelationshipField::Cells)?;
let cells_opt = uow.get_table_cell_multi(&cell_ids)?;
let cells: Vec<TableCell> = cells_opt.into_iter().flatten().collect();
let cell_frame_ids: Vec<EntityId> = cells.iter().filter_map(|c| c.cell_frame).collect();
let mut total_cell_blocks: i64 = 0;
let mut min_cell_position: Option<i64> = None;
for fid in &cell_frame_ids {
let block_ids = uow.get_frame_relationship(fid, &FrameRelationshipField::Blocks)?;
if !block_ids.is_empty() {
let blocks_opt = uow.get_block_multi(&block_ids)?;
for block in blocks_opt.into_iter().flatten() {
total_cell_blocks += 1;
match min_cell_position {
None => min_cell_position = Some(block.document_position),
Some(min) if block.document_position < min => {
min_cell_position = Some(block.document_position);
}
_ => {}
}
}
}
}
let frame_ids = uow.get_document_relationship(&doc_id, &DocumentRelationshipField::Frames)?;
let mut anchor_frame_id: Option<EntityId> = None;
for fid in &frame_ids {
let frame = match uow.get_frame(fid)? {
Some(f) => f,
None => continue,
};
if frame.table == Some(table_id) {
anchor_frame_id = Some(frame.id);
break;
}
}
for fid in &cell_frame_ids {
uow.remove_frame(fid)?;
}
if let Some(anchor_id) = anchor_frame_id {
let frames_opt = uow.get_frame_multi(&frame_ids)?;
for frame in frames_opt.iter().flatten() {
let neg_anchor = -(anchor_id as i64);
if frame.child_order.contains(&neg_anchor) {
let mut updated = frame.clone();
updated.child_order.retain(|&x| x != neg_anchor);
updated.updated_at = now;
uow.update_frame(&updated)?;
break;
}
}
uow.remove_frame(&anchor_id)?;
}
uow.remove_table(&table_id)?;
if let Some(table_start_pos) = min_cell_position {
let remaining_frame_ids =
uow.get_document_relationship(&doc_id, &DocumentRelationshipField::Frames)?;
let mut blocks_to_shift: Vec<Block> = Vec::new();
for fid in &remaining_frame_ids {
let block_ids = uow.get_frame_relationship(fid, &FrameRelationshipField::Blocks)?;
if !block_ids.is_empty() {
let blocks_opt = uow.get_block_multi(&block_ids)?;
for block in blocks_opt.into_iter().flatten() {
if block.document_position >= table_start_pos {
let mut shifted = block;
shifted.document_position -= total_cell_blocks;
shifted.updated_at = now;
blocks_to_shift.push(shifted);
}
}
}
}
if !blocks_to_shift.is_empty() {
uow.update_block_multi(&blocks_to_shift)?;
}
}
let mut updated_doc = document.clone();
updated_doc.block_count -= total_cell_blocks;
updated_doc.updated_at = now;
uow.update_document(&updated_doc)?;
Ok(snapshot)
}
impl RemoveTableUseCase {
pub fn new(uow_factory: Box<dyn RemoveTableUnitOfWorkFactoryTrait>) -> Self {
RemoveTableUseCase {
uow_factory,
undo_snapshot: None,
last_dto: None,
}
}
pub fn execute(&mut self, dto: &RemoveTableDto) -> Result<()> {
let mut uow = self.uow_factory.create();
uow.begin_transaction()?;
let snapshot = execute_remove_table(&mut uow, dto)?;
self.undo_snapshot = Some(snapshot);
self.last_dto = Some(dto.clone());
uow.commit()?;
Ok(())
}
}
impl UndoRedoCommand for RemoveTableUseCase {
fn undo(&mut self) -> Result<()> {
let snapshot = self
.undo_snapshot
.as_ref()
.ok_or_else(|| anyhow!("No snapshot available for undo"))?
.clone();
let mut uow = self.uow_factory.create();
uow.begin_transaction()?;
uow.restore_document(&snapshot)?;
uow.commit()?;
Ok(())
}
fn redo(&mut self) -> Result<()> {
let dto = self
.last_dto
.as_ref()
.ok_or_else(|| anyhow!("No DTO available for redo"))?
.clone();
let mut uow = self.uow_factory.create();
uow.begin_transaction()?;
let snapshot = execute_remove_table(&mut uow, &dto)?;
self.undo_snapshot = Some(snapshot);
uow.commit()?;
Ok(())
}
fn as_any(&self) -> &dyn Any {
self
}
}