use std::sync::Arc;
use common::direct_access::workspace::WorkspaceRelationshipField;
use common::event::{DirectAccessEntity, EntityEvent, HandlingManifestEvent, Origin};
use direct_access::UpdateFeatureDto;
use slint::ComponentHandle;
use crate::app_context::AppContext;
use crate::commands::{feature_commands, undo_redo_commands, workspace_commands};
use crate::event_hub_client::EventHubClient;
use crate::{App, AppState, FeaturesTabState, ListItem};
use super::use_case_handlers::{clear_use_case_form, clear_use_case_list, fill_use_case_list};
pub fn create_new_undo_stack(app: &App, app_context: &Arc<AppContext>) {
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
if let Some(app) = app_weak.upgrade() {
let stack_id = ctx.undo_redo_manager.lock().unwrap().create_new_stack();
log::info!("New undo stack created with ID: {}", stack_id);
app.global::<FeaturesTabState>()
.set_features_undo_stack_id(stack_id as i32);
}
}
pub fn delete_undo_stack(app: &App, app_context: &Arc<AppContext>) {
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
if let Some(app) = app_weak.upgrade() {
let stack_id = app
.global::<FeaturesTabState>()
.get_features_undo_stack_id() as u64;
let result = ctx.undo_redo_manager.lock().unwrap().delete_stack(stack_id);
match result {
Ok(()) => {
log::info!("Undo stack with ID {} deleted", stack_id);
app.global::<FeaturesTabState>()
.set_features_undo_stack_id(-1);
}
Err(e) => {
log::error!("Failed to delete undo stack {}: {}", stack_id, e);
}
}
}
}
pub fn subscribe_close_manifest_event(
event_hub_client: &EventHubClient,
app: &App,
app_context: &Arc<AppContext>,
) {
event_hub_client.subscribe(Origin::HandlingManifest(HandlingManifestEvent::Close), {
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |event| {
log::info!("Manifest closed event received: {:?}", event);
let ctx = Arc::clone(&ctx);
let app_weak = app_weak.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(app) = app_weak.upgrade() {
clear_feature_list(&app, &ctx);
clear_use_case_list(&app, &ctx);
delete_undo_stack(&app, &ctx);
}
});
}
});
}
pub fn subscribe_new_manifest_event(
event_hub_client: &EventHubClient,
app: &App,
app_context: &Arc<AppContext>,
) {
event_hub_client.subscribe(Origin::HandlingManifest(HandlingManifestEvent::Create), {
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |_event| {
log::info!("New manifest created event received");
let ctx = Arc::clone(&ctx);
let app_weak = app_weak.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(app) = app_weak.upgrade()
&& app.global::<AppState>().get_manifest_is_open()
{
fill_feature_list(&app, &ctx);
fill_use_case_list(&app, &ctx);
create_new_undo_stack(&app, &ctx);
}
});
}
});
}
pub fn subscribe_load_manifest_event(
event_hub_client: &EventHubClient,
app: &App,
app_context: &Arc<AppContext>,
) {
event_hub_client.subscribe(Origin::HandlingManifest(HandlingManifestEvent::Load), {
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |_event| {
log::info!("Manifest loaded event received");
let ctx = Arc::clone(&ctx);
let app_weak = app_weak.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(app) = app_weak.upgrade() {
log::info!("Refreshing feature and use case lists after manifest load");
if app.global::<AppState>().get_manifest_is_open() {
log::info!("Manifest is open, scheduling list refresh");
fill_feature_list(&app, &ctx);
fill_use_case_list(&app, &ctx);
log::info!("Feature and Use Case lists refreshed after manifest load");
create_new_undo_stack(&app, &ctx);
}
}
});
}
});
}
pub fn subscribe_workspace_updated_event(
event_hub_client: &EventHubClient,
app: &App,
app_context: &Arc<AppContext>,
) {
event_hub_client.subscribe(
Origin::DirectAccess(DirectAccessEntity::Workspace(EntityEvent::Updated)),
{
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |event| {
log::info!(
"Workspace updated event received (features tab): {:?}",
event
);
let ctx = Arc::clone(&ctx);
let app_weak = app_weak.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(app) = app_weak.upgrade()
&& app.global::<AppState>().get_manifest_is_open()
{
fill_feature_list(&app, &ctx);
app.global::<AppState>().set_manifest_is_saved(false);
}
});
}
},
);
}
pub fn subscribe_feature_updated_event(
event_hub_client: &EventHubClient,
app: &App,
app_context: &Arc<AppContext>,
) {
event_hub_client.subscribe(
Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Updated)),
{
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |event| {
log::info!("Feature updated event received: {:?}", event);
let ctx = Arc::clone(&ctx);
let app_weak = app_weak.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(app) = app_weak.upgrade()
&& app.global::<AppState>().get_manifest_is_open()
{
fill_feature_list(&app, &ctx);
fill_use_case_list(&app, &ctx);
app.global::<AppState>().set_manifest_is_saved(false);
}
});
}
},
)
}
pub fn subscribe_feature_deletion_event(
event_hub_client: &EventHubClient,
app: &App,
app_context: &Arc<AppContext>,
) {
event_hub_client.subscribe(
Origin::DirectAccess(DirectAccessEntity::Feature(EntityEvent::Removed)),
{
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |event| {
log::info!("Feature updated event received: {:?}", event);
let _ctx = Arc::clone(&ctx);
let app_weak = app_weak.clone();
let _ = slint::invoke_from_event_loop(move || {
if let Some(app) = app_weak.upgrade()
&& app.global::<AppState>().get_manifest_is_open()
{
app.global::<AppState>().set_manifest_is_saved(false);
}
});
}
},
)
}
pub fn fill_feature_list(app: &App, app_context: &Arc<AppContext>) {
log::info!("Filling feature list ...");
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
if let Some(app) = app_weak.upgrade() {
let workspace_id = app.global::<AppState>().get_workspace_id() as common::types::EntityId;
if workspace_id > 0 {
let feature_ids_res = workspace_commands::get_workspace_relationship(
&ctx,
&workspace_id,
&WorkspaceRelationshipField::Features,
);
match feature_ids_res {
Ok(feature_ids) => {
if feature_ids.is_empty() {
let model = std::rc::Rc::new(slint::VecModel::from(Vec::<ListItem>::new()));
app.global::<FeaturesTabState>()
.set_feature_cr_list(model.into());
log::info!("Feature list cleared (no features)");
return;
}
match feature_commands::get_feature_multi(&ctx, &feature_ids) {
Ok(features_opt) => {
let mut list: Vec<ListItem> = Vec::new();
for f in features_opt.into_iter().flatten() {
list.push(ListItem {
id: f.id as i32,
text: slint::SharedString::from(f.name),
subtitle: slint::SharedString::from(""),
checked: false,
gradient_color: slint::Color::default(),
});
}
let model = std::rc::Rc::new(slint::VecModel::from(list));
app.global::<FeaturesTabState>()
.set_feature_cr_list(model.into());
log::info!("Feature list refreshed");
}
Err(e) => {
log::error!("Failed to fetch features: {}", e);
}
}
}
Err(e) => {
log::error!("Failed to get workspace features: {}", e);
}
}
}
}
}
pub fn clear_feature_list(app: &App, app_context: &Arc<AppContext>) {
let _ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
if let Some(app) = app_weak.upgrade() {
let model = std::rc::Rc::new(slint::VecModel::from(Vec::<ListItem>::new()));
app.global::<FeaturesTabState>()
.set_feature_cr_list(model.into());
log::info!("Feature list cleared");
}
}
pub fn fill_feature_form(app: &App, feature: &direct_access::FeatureDto) {
let state = app.global::<FeaturesTabState>();
state.set_selected_feature_id(feature.id as i32);
state.set_selected_feature_name(feature.name.clone().into());
}
pub fn clear_feature_form(app: &App) {
let state = app.global::<FeaturesTabState>();
state.set_selected_feature_id(-1);
state.set_selected_feature_name("".into());
}
pub fn setup_features_reorder_callback(app: &App, app_context: &Arc<AppContext>) {
app.global::<FeaturesTabState>()
.on_request_features_reorder({
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |from_index, to_index| {
let from = from_index as usize;
let to = to_index as usize;
if let Some(app) = app_weak.upgrade() {
let workspace_id =
app.global::<AppState>().get_workspace_id() as common::types::EntityId;
let feature_ids_res = workspace_commands::get_workspace_relationship(
&ctx,
&workspace_id,
&WorkspaceRelationshipField::Features,
);
let mut feature_ids = feature_ids_res.unwrap_or_default();
if from == to || from >= feature_ids.len() {
return;
}
let moving_feature_id = feature_ids.remove(from);
let mut insert_at = if to > from { to - 1 } else { to };
if insert_at > feature_ids.len() {
insert_at = feature_ids.len();
}
feature_ids.insert(insert_at, moving_feature_id);
let result = workspace_commands::set_workspace_relationship(
&ctx,
Some(
app.global::<FeaturesTabState>()
.get_features_undo_stack_id() as u64,
),
&direct_access::WorkspaceRelationshipDto {
id: workspace_id,
field: WorkspaceRelationshipField::Features,
right_ids: feature_ids,
},
);
match result {
Ok(()) => {
log::info!("Features reordered successfully");
}
Err(e) => {
log::error!("Failed to reorder features: {}", e);
}
}
}
}
});
}
pub fn setup_feature_deletion_callback(app: &App, app_context: &Arc<AppContext>) {
app.global::<FeaturesTabState>()
.on_request_feature_deletion({
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |feature_id| {
if feature_id < 0 {
return;
}
if let Some(app) = app_weak.upgrade() {
let result = feature_commands::remove_feature(
&ctx,
Some(
app.global::<FeaturesTabState>()
.get_features_undo_stack_id() as u64,
),
&(feature_id as common::types::EntityId),
);
match result {
Ok(()) => {
log::info!("Feature deleted successfully");
clear_feature_form(&app);
clear_use_case_list(&app, &ctx);
clear_use_case_form(&app);
fill_feature_list(&app, &ctx);
}
Err(e) => {
log::error!("Failed to delete feature: {}", e);
}
}
}
}
});
}
pub fn setup_select_feature_callbacks(app: &App, app_context: &Arc<AppContext>) {
app.global::<FeaturesTabState>().on_feature_selected({
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |feature_id| {
if feature_id < 0 {
return;
}
if let Some(app) = app_weak.upgrade() {
let feature_res =
feature_commands::get_feature(&ctx, &(feature_id as common::types::EntityId));
match feature_res {
Ok(Some(feature)) => {
fill_feature_form(&app, &feature);
fill_use_case_list(&app, &ctx);
clear_use_case_form(&app);
log::info!("Feature selected: {}", feature.name);
}
Ok(None) => {
log::warn!("Feature not found: {}", feature_id);
}
Err(e) => {
log::error!("Failed to get feature: {}", e);
}
}
}
}
});
}
pub fn setup_feature_name_callback(app: &App, app_context: &Arc<AppContext>) {
app.global::<FeaturesTabState>().on_feature_name_changed({
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move |name| {
if let Some(app) = app_weak.upgrade() {
let feature_id = app.global::<FeaturesTabState>().get_selected_feature_id();
if feature_id < 0 {
return;
}
let feature_res =
feature_commands::get_feature(&ctx, &(feature_id as common::types::EntityId));
if let Ok(Some(mut feature)) = feature_res.map(|f| f.map(UpdateFeatureDto::from)) {
feature.name = name.to_string();
match feature_commands::update_feature(
&ctx,
Some(
app.global::<FeaturesTabState>()
.get_features_undo_stack_id() as u64,
),
&feature,
) {
Ok(_) => {
log::info!("Feature name updated successfully");
}
Err(e) => {
log::error!("Failed to update feature name: {}", e);
}
}
}
}
}
});
}
pub fn setup_feature_addition_callback(app: &App, app_context: &Arc<AppContext>) {
app.global::<FeaturesTabState>()
.on_request_feature_addition({
let ctx = Arc::clone(app_context);
let app_weak = app.as_weak();
move || {
if let Some(app) = app_weak.upgrade() {
let workspace_id = app.global::<AppState>().get_workspace_id();
if workspace_id <= 0 {
log::warn!("Cannot add feature: no workspace loaded");
return;
}
let stack_id = app
.global::<FeaturesTabState>()
.get_features_undo_stack_id() as u64;
if let Err(e) = undo_redo_commands::begin_composite(&ctx, Some(stack_id)) {
log::error!("Failed to begin composite: {e}");
return;
}
let create_dto = direct_access::CreateFeatureDto {
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
name: "new_feature".to_string(),
use_cases: vec![],
};
match feature_commands::create_orphan_feature(&ctx, Some(stack_id), &create_dto)
{
Ok(new_feature) => {
log::info!("Feature created successfully with id: {}", new_feature.id);
let feature_ids_res = workspace_commands::get_workspace_relationship(
&ctx,
&(workspace_id as common::types::EntityId),
&WorkspaceRelationshipField::Features,
);
match feature_ids_res {
Ok(mut feature_ids) => {
feature_ids.push(new_feature.id);
let relationship_dto =
direct_access::WorkspaceRelationshipDto {
id: workspace_id as common::types::EntityId,
field: WorkspaceRelationshipField::Features,
right_ids: feature_ids,
};
if let Err(e) = workspace_commands::set_workspace_relationship(
&ctx,
Some(
app.global::<FeaturesTabState>()
.get_features_undo_stack_id()
as u64,
),
&relationship_dto,
) {
log::error!(
"Failed to add feature to workspace relationship: {}",
e
);
undo_redo_commands::cancel_composite(&ctx);
} else {
log::info!(
"Feature added to workspace relationship successfully"
);
undo_redo_commands::end_composite(&ctx);
}
}
Err(e) => {
log::error!(
"Failed to get workspace features relationship: {}",
e
);
undo_redo_commands::cancel_composite(&ctx);
}
}
}
Err(e) => {
log::error!("Failed to create feature: {}", e);
undo_redo_commands::cancel_composite(&ctx);
}
}
}
}
});
}