# Admin Panel & Workspace Flows

This document details the flows, state machine transitions, and UI mechanics of the **TFB Engine Telegram Admin Panel**.

---

## 1. The Sticky Workspace Model (Single-Surface Navigation)

To prevent chat spam and cluttering the admin's chat history, the entire admin panel operates on a **Single-Surface Workspace** model. 

* **Workspace Message:** A single message (`admin_workspace_message_id` stored in `users` table) represents the active "control surface" of the panel.
* **Inline Buttons Only:** All navigation, settings, details, and selection lists are rendered using **InlineKeyboardMarkup** (glass buttons) attached to this single workspace message.
* **In-Place Edits:** Any button tap triggers a callback (`adm:<entity>.<action>:<id>:<id2>`), which is processed by `AdminCallbackDispatcher` and edited in-place via `editMessageText` on the workspace message.
* **Spam Prevention:** The bot *never* sends a new menu message per button click. Back and Home buttons simply edit the same workspace message to the previous or main menu view.

### Workspace Recovery Fallback
If editing the workspace message fails (e.g., if the admin manually deleted the workspace message, or if it is too old), the system automatically:
1. Sends exactly **one** new message to the admin.
2. Captures its new `message_id`.
3. Stores it as the new `admin_workspace_message_id` in the `users` table.
4. Continues navigation seamlessly.

---

## 2. Temporary Prompt & Cleanup Discipline

In v1.6.0 every awaiting text/media guide ID is stored in `context.prompt_ids`. Successful input, invalid retry, Back, Cancel, and stage transition delete the admin input plus all prior guides best-effort. A retry/next-stage guide is the only guide retained, and navigation returns to the single edited workspace.

Test and campaign creation accept an optional `[A-Za-z0-9_]+` slug (`خودکار` selects a unique generated value); detail screens allow unlimited current-slug changes. User and broadcast profile/tag filters always choose a test first, with manual tags under a separate «برچسب سراسری» entry. Test detail also exposes gate configuration and multi-item «محتوای فرعی» management.


Certain admin steps require text or file inputs (e.g., naming a test, entering a threshold, or uploading a voice file). These are the only allowed cases where new messages may be sent.

* **Prompt Messages:** When an input is requested, the bot sends a prompt message (e.g., "لطفاً عنوان را بفرست") using `reply()` or `replyHtml()`.
* **Automatic Tracking:** `AdminReplier` automatically intercepts all `reply()` and `replyHtml()` calls, extracts the sent message's `message_id`, and appends it to a `prompt_ids` array in the admin's state context (`user_states.context_json`).
* **Aggressive Cleanup:** On any subsequent transition (when the admin clicks an inline button OR successfully submits their input), `AdminCallbackDispatcher` or `AdminMessageDispatcher` automatically:
  1. Deletes all prompt messages in `prompt_ids` via `deleteMessage` (best-effort).
  2. Deletes the admin's own input message.
  3. Resets `prompt_ids` to empty.
* **No Orphan Prompts:** This ensures that no "Please enter X" prompts are left behind, keeping the chat exceptionally clean.

### The `/cancel` Cleanup Flow
When an admin types `/cancel` or clicks an "انصراف" button mid-wizard:
1. The active `prompt_ids` are retrieved and deleted.
2. The `/cancel` text message is deleted.
3. The workspace message is automatically edited back to the main menu (or the previous stable screen).

---

## 3. Core Admin Flows

### A. Test Creation & Validation Gate
1. **Title Only:** Creating a test only asks for a Persian Title. The `slug` is auto-generated via `SlugService` (transliterated and sanitized).
2. **Activation Gate:** A test is born inactive (`is_active = 0`). It can only go live by passing `TestValidationService::validate()`.
3. **Blocking Barriers:** Activation is refused if there are:
   - Zero profiles.
   - Zero active questions.
   - Any question with fewer than 2 options.
   - Any option jumping to a non-existent question.
   - Zero results with any content.
   - Invalid threshold for `dual_on_close` mode.

### B. Profile & Question Building
* **Profile Creation:** Title only. ASCII code is auto-generated (`ProfileCodeGenerator`). A matching empty result shell is automatically created.
* **Question Creation:** Text only. Questions are text-based and options are rendered as inline buttons.

### C. Draft-then-Commit Scoring UI
The `profile_id => score` mapping for any option is edited via an interactive draft UI:
1. **Workspace Scoring Keyboard:** Clicking "امتیازدهی" edits the workspace message in-place to show a grid with `➕` and `➖` buttons for every profile.
2. **Draft State:** Taps only mutate a draft in `user_states.context_json` and edit the workspace message in place. No database writes or new messages occur.
3. **Commit:** Tapping "✅ ثبت" saves all scores to the database in a single transaction, resets the state, and edits the workspace back to the option detail view with a "امتیازها ثبت شد" notification at the top.
4. **Cancel:** Tapping "انصراف" discards the draft and edits the workspace back to the option detail view.

### D. Campaign CRUD & Link Rotation
* **Campaign Details with Time Ranges:** Opening a campaign shows its statistics (Starts, New Users, Joins) filtered by time-range buttons (`۲۴ ساعت`, `۷ روز`, `۳۰ روز`, `کل`). Clicking a button edits the same workspace in-place.
* **Link Rotation:** Admins can rotate a campaign's link (`CampaignLinkRepository`). The old link token is marked `'replaced'` but historical campaign attribution is preserved.
* **Test Binding:** Buttons guide the admin to bind a campaign to a specific test, generating a shareable deep link.

### E. Broadcast Queueing
* Bulk broadcasts are never executed in a single request loop. They are created as draft jobs, populated with targets (`populateTargetsAndStart`), and processed in leased, time-bounded batches (`pump`) to respect Telegram's rate limits on shared hosts.

### F. Defensive Backup & Restore
* **Creation:** A single tap exports the DB via pure-PDO SQL generation (no `mysqldump`).
* **Defensive Restore:** Requires:
  1. Explicitly starting the restore flow.
  2. Uploading a `.sql` file matching our `-- TFB_ENGINE_BACKUP_V1` marker.
  3. Typing the exact confirmation phrase "تایید بازیابی" to execute.
