-- allium: 1
-- bDS UI Data Flow Model
-- Scope: cross-cutting (all waves)
-- Distilled from: appStore.ts, PostEditor.tsx, MediaEditor.tsx, Editor.tsx,
--   Sidebar.tsx, NotificationWatcher.ts

-- Describes the reactive data flows that connect sidebar, editor, tab bar,
-- and backend engine operations. Covers: sidebar->editor, editor->sidebar,
-- cross-tab coordination, and backend->UI event propagation.

use "./layout.allium" as layout
use "./tabs.allium" as tabs
use "./sidebar_views.allium" as sidebar

-- ─── External surfaces ──────────────────────────────────────

-- User-initiated navigation and entity management actions.
-- These triggers originate from sidebar clicks, tab operations,
-- dashboard interactions, and save/delete actions in editors.

surface UserNavigation {
    provides: SidebarItemClicked(entity_type, entity_id, click_type)
    provides: SidebarCreateRequested(entity_type)
    provides: SidebarDeleteRequested(entity_type, entity_id)
    provides: DashboardPostClicked(post_id, click_type)
    provides: PostSaved(post_id, updated_post)
    provides: PostStatusTransitioned(post_id, old_status, new_status)
    provides: PostDeletedFromEditor(post_id)
    provides: MediaSavedFromEditor(media_id, updated_media)
    provides: MediaDeletedFromEditor(media_id)
    provides: SettingsRebuildCompleted(entity_type, new_data)
    provides: TransientTabBeingReplaced(old_tab, new_tab)
    provides: TabClosed(tab)
}

-- ─── 1. Sidebar -> Editor flows ──────────────────────────────

-- All UI coordination flows through shared application state.
-- There are no direct calls between sidebar and editor.
-- Both read from and write to the same state model; changes propagate
-- reactively.

rule SidebarEntityClick {
    when: SidebarItemClicked(entity_type, entity_id, click_type)
    -- Single click: open as transient/preview tab
    -- Double click: open as pinned tab
    -- See sidebar_views.allium for per-view click rules
    -- Effect: Editor mounts the matching view keyed by entity_id
    -- Effect: Sidebar item highlights based on activeTabId match
    -- If a prior transient tab of same type exists, it is replaced,
    --   and the old editor unmounts (triggering auto-save if dirty)
}

rule SidebarCreateEntity {
    when: SidebarCreateRequested(entity_type)
    -- Posts/Pages: backend creates post, emits post:created,
    --   store adds to posts list, sidebar re-renders with new item.
    --   Does NOT open a tab automatically. User must click to open.
    --   Sets selectedPostId for highlighting, optionally ensures sidebar visible.
    -- Scripts/Templates: backend creates, emits event, opens tab immediately.
    -- Chat: creates conversation, opens as pinned tab.
    -- Import: creates definition, opens as pinned tab.
}

rule SidebarDeleteEntity {
    when: SidebarDeleteRequested(entity_type, entity_id)
    -- Scripts/Templates/Chat/Import: backend deletes entity,
    --   then closeTab(entity_id) removes its tab,
    --   activates adjacent tab or shows dashboard.
    -- Posts/Media: deletion is triggered from the EDITOR (not sidebar).
    --   See editor_post.allium PostDeleteAction / editor_media.allium MediaDeleteAction.
}

invariant SidebarFilterIsolation {
    -- Sidebar search/filter state is local to the sidebar component.
    -- Filtering never affects: active tab, editor content, selectedPostId.
    -- Only the visible list of items changes.
}

-- ─── 2. Editor -> Sidebar flows ──────────────────────────────

rule PostTitleChanged {
    when: PostSaved(post_id, updated_post)
    -- Auto-save fires after 3s idle, or on Ctrl+S, or on unmount/tab switch
    -- Store's posts array is updated with new title/metadata
    -- Sidebar PostsList re-renders reactively showing new title
    -- TabBar re-renders showing new title
    ensures: dirtyPosts.remove(post_id)
}

rule PostStatusChanged {
    when: PostStatusTransitioned(post_id, old_status, new_status)
    -- Store's posts array is updated with new status
    -- Sidebar detects status change (compares prev/current status maps)
    --   and re-runs search/filter if active
    -- Post moves between draft/published/archived sections in sidebar
}

rule PostEditorDelete {
    when: PostDeletedFromEditor(post_id)
    ensures: store.removePost(post_id)
    -- Removes from posts array, clears selectedPostId if matching,
    --   removes from dirtyPosts
    ensures: closeTab(post_id)
    -- Sidebar re-renders without the post
}

rule MediaEditorSave {
    when: MediaSavedFromEditor(media_id, updated_media)
    ensures: store.updateMedia(media_id, updated_media)
    -- Sidebar MediaList re-renders with updated metadata
}

rule MediaEditorDelete {
    when: MediaDeletedFromEditor(media_id)
    ensures: store.removeMedia(media_id)
    -- Editor.tsx safety net: detects active media tab references
    --   non-existent item, calls closeTab(activeTab.id)
    -- Sidebar re-renders without the media item
}

rule SettingsRebuild {
    when: SettingsRebuildCompleted(entity_type, new_data)
    -- Wholesale replacement of posts or media array in store
    ensures: store.setPosts(new_data) or store.setMedia(new_data)
    -- Sidebar re-renders entirely with fresh data
}

-- ─── 3. Cross-tab coordination ──────────────────────────────

invariant TabSwitchDoesNotChangeSidebarView {
    -- Switching tabs does NOT change activeView in the sidebar.
    -- The sidebar view is controlled exclusively by the Activity Bar.
    -- Exception: Dashboard post click explicitly sets activeView = "posts".
}

invariant SidebarHighlightFollowsActiveTab {
    -- Sidebar item highlight is based on activeTabId === entity.id,
    -- NOT on selectedPostId/selectedMediaId (which are separate concepts
    -- used only for post creation flow).
}

rule TransientTabReplacement {
    when: TransientTabBeingReplaced(old_tab, new_tab)
    -- Old editor unmounts -> cleanup auto-save fires if dirty
    -- New editor mounts with new entity
    -- Sidebar highlight shifts to newly active entity
}

rule TabCloseCleanup {
    when: TabClosed(tab)
    -- If post tab: PostEditor unmounts, auto-save fires if dirty
    -- Store activates adjacent tab (prefer right, then left, then null)
    -- Sidebar highlight updates to new active tab
    -- If no tabs remain: dashboard shown
}

rule DashboardPostClick {
    when: DashboardPostClicked(post_id, click_type)
    -- This is the ONLY place where opening a tab also switches sidebar view
    ensures: setActiveView("posts")
    ensures: setSelectedPost(post_id)
    if click_type = single:
        ensures: OpenTabRequested(type: post, id: post_id, intent: preview)
    if click_type = double:
        ensures: OpenTabRequested(type: post, id: post_id, intent: pin)
}

-- ─── 4. Backend -> UI event propagation ─────────────────────

-- Backend engines emit events via IPC. The renderer listens and updates
-- the shared store. Both sidebar and editor re-render reactively.
-- Events: post:created, post:updated, post:deleted,
--   media:imported, media:updated, media:deleted,
--   template:created/updated/deleted, script:created/updated/deleted,
--   entity:changed (from CLI/MCP mutations via NotificationWatcher)

-- TabBar also listens directly for:
--   post-updated (title refresh)
--   bds:scripts-changed (script title refresh)
--   BDS_EVENT_TEMPLATES_CHANGED (template title refresh)
--   chat.onTitleUpdated (chat conversation title refresh)
--   importDefinitions.onNameUpdated (import name refresh)

-- Editor.tsx has safety-net useEffect guards that:
--   Close media tabs when referenced media no longer exists in store
--   Clear selectedPostId/selectedMediaId when entity is gone

-- ─── 5. Keyboard shortcut map ─────────────────────────────

-- All shortcuts use Cmd on macOS, Ctrl on other platforms.

-- Global:
--   Ctrl/Cmd+B: toggle sidebar visibility
--   Ctrl/Cmd+W: close active tab

-- Post editor:
--   Ctrl/Cmd+S: save post immediately (resets auto-save timer)
--   Ctrl/Cmd+K: open InsertModal (insert post link)

-- Sidebar lists:
--   Enter: open selected item as pinned tab
--   Space: open selected item as preview/transient tab
