-- allium: 1
-- bDS Settings and Style Views
-- Scope: UI content area — settings + style editing surfaces
-- Distilled from: SettingsView.tsx, StyleView.tsx

-- Describes the layout and behaviour of the settings and style views.

use "./i18n.allium" as i18n

-- ─── Settings view ────────────────────────────────────────────

value SettingsView {
    search_query: String?               -- filters sections by keyword match
    active_sections: List<String>       -- visible sections after search filter
    project_section: SettingsProjectSection?
    editor_section: SettingsEditorSection?
    ai_section: SettingsAISection?
    technology_section: SettingsTechnologySection?
    publishing_section: SettingsPublishingSection?
    mcp_section: SettingsMCPSection?
    data_section: SettingsDataSection?
}

value SettingsProjectSection {
    project_name: String                -- text input
    project_description: String         -- textarea (3 rows)
    data_path: String                   -- text input + Browse button + Reset button
    public_url: String                  -- url input
    main_language: String               -- select
    blog_languages: List<String>        -- checkboxes (main language disabled)
    default_author: String              -- text input
    max_posts_per_page: Integer         -- number input (1-500, default 50)
    image_import_concurrency: Integer   -- number input (1-8, default 4)
    blogmark_category: String           -- select from categories
}

value SettingsEditorSection {
    default_mode: String                -- select: markdown | preview
    diff_view_style: String             -- select: inline | side-by-side
    wrap_long_lines: Boolean            -- checkbox
    hide_unchanged_regions: Boolean     -- checkbox
}

value SettingsAISection {
    online: SettingsAIEndpoint
    airplane: SettingsAIEndpoint
    system_prompt: String               -- textarea (12 rows) + Save + Reset to Default
}

value SettingsAIEndpoint {
    url: String
    api_key: String?                    -- masked; optional for airplane endpoint
    chat_model: String?
    title_model: String?
    image_model: String?
    chat_supports_tools: Boolean
    image_supports_vision: Boolean
}

value SettingsTechnologySection {
    semantic_similarity_enabled: Boolean -- checkbox: enable duplicate search + related-post embeddings
    -- Scripting runtime is fixed at the application layer; no runtime switch is exposed.
    -- Saved together with project metadata (settings_project form).
}

value SettingsPublishingSection {
    ssh_mode: String                    -- select: scp | rsync
    ssh_host: String                    -- text input
    ssh_username: String                -- text input
    ssh_remote_path: String             -- text input
}

value SettingsMCPSection {
    agents: List<MCPAgentRow>
}

value MCPAgentRow {
    agent_name: String                  -- Claude Code, Claude Desktop, GitHub Copilot, etc.
    is_supported: Boolean               -- false agents show a disabled button + "not supported yet" note
    is_installed: Boolean               -- toggle label: Remove when installed, else Add
    config_path: String?                -- agent config file path (nil when unsupported)
}

value SettingsDataSection {
    -- Action-only section: rebuild buttons + Open Data Folder. No persisted fields.
    rebuild_targets: List<String>       -- posts | media | scripts | templates | links | thumbnails | embedding
}

invariant SettingsMCPAgents {
    -- MCP section has exactly 7 agent rows in this order:
    -- Claude Code, Claude Desktop, GitHub Copilot, Gemini CLI,
    -- OpenCode, Mistral Vibe, OpenAI Codex.
    -- Only Claude Code and GitHub Copilot are supported; the rest render a
    -- disabled toggle and a "not supported in the rewrite yet" note.
}

config {
    settings_max_posts_per_page: Integer = 500
    settings_default_posts_per_page: Integer = 50
    settings_image_import_concurrency_default: Integer = 4
    settings_image_import_concurrency_min: Integer = 1
    settings_image_import_concurrency_max: Integer = 8
    settings_system_prompt_rows: Integer = 12
}

surface SettingsViewSurface {
    context settings: SettingsView

    exposes:
        settings.search_query
        settings.active_sections

    provides:
        SettingsSearchChanged(query)
        SettingsProjectSaved(project_data)
        SettingsEditorSaved(editor_data)
        SettingsCategoryAdded(category_name)
        SettingsCategoryUpdated(category_row)
        SettingsCategoryRemoved(category_name)
        SettingsCategoriesResetToDefaults()
        SettingsAIProfileSaved(kind, endpoint)
        SettingsAIModelRefreshRequested(kind, url, api_key)
        SettingsAIChatTestRequested(kind)
        SettingsAISystemPromptSaved(prompt)
        SettingsAISystemPromptReset()
        SettingsPublishingSaved(publishing_data)
        SettingsPublishingCleared()
        SettingsTechnologySaved(technology_data)
        SettingsMCPAgentToggled(agent_name)
        SettingsRebuildRequested(entity_type)
        SettingsRegenerateThumbnailsRequested()
        SettingsEmbeddingIndexRebuildRequested()
        SettingsOpenDataFolderRequested()
        StyleThemeSelected(theme_name)
        StyleApplyRequested(theme_name)

    @guarantee SearchBar
        -- Search input in header filters sections by keyword match.
        -- "No results" message with clear button when no sections match.

    @guarantee ProjectSection
        -- Section 1: Project Name, Description (textarea 3 rows), Data Path (text + Browse + Reset),
        --   Public URL, Main Language (select), Blog Languages (checkbox grid, main disabled),
        --   Default Author, Max Posts Per Page (number 1-500),
        --   Image Import Concurrency (number 1-8, default 4),
        --   Blogmark Category (select), Blogmark Bookmarklet (copy button), Save button.

    @guarantee BookmarkletCopy
        -- Copy button copies the generated bookmarklet JavaScript to the system
        -- clipboard for pasting into a browser bookmark.
        -- The bookmarklet captures the active tab's document.title and
        -- location.href (each URI-encoded) and navigates to a
        -- ruds://new-post?title=...&url=... deep link. RuDS does not register or
        -- accept bds2://, which remains owned by bDS2. When a project is active
        -- its id is appended as &project_id=... so the link targets that
        -- project regardless of which one is currently open.

    @guarantee EditorSection
        -- Section 2: Default Editor Mode (select: Markdown/Preview),
        --   Diff View Style (select: Inline/Side-by-side),
        --   Wrap Long Lines (checkbox), Hide Unchanged Regions (checkbox).

    @guarantee ContentCategoriesSection
        -- Section 3: Table with columns: Category, Title, Render in Lists,
        --   Show Titles, Post Template, List Template, Remove.
        -- Protected categories (article, aside, page, picture) cannot be deleted.
        -- Add Category: text input + Add button, validates non-empty and unique.
        -- "Reset to Defaults" restores default categories set.

    @guarantee AISection
        -- Section 4: independent Online and Airplane endpoint cards. Each has
        --   URL, masked API key (optional locally), Refresh Models, chat/title/image
        --   selectors, explicit tools/vision capability overrides, and Test Chat.
        --   Settings has no airplane-mode toggle; the status-bar switch selects
        --   the active profile and immediately swaps chat/model routing.
        --   System prompt (textarea config.settings_system_prompt_rows rows + Save + Reset).

    @guarantee AIApiKeyValidation
        -- API keys are encrypted via SecureKeyStore and displayed masked.
        -- Online profiles require a key; airplane profiles permit authenticated
        -- or unauthenticated local OpenAI-compatible endpoints.

    @guarantee AIModelRefresh
        -- Refresh Models button per endpoint fetches model list from URL.
        -- Discovery requires URL and online credentials but no preselected model.
        -- All three role selectors are populated independently from the result;
        -- advertised capability metadata prefills tools and vision overrides.

    @guarantee AIChatTest
        -- Test Chat sends a minimal request through every distinct model selected
        -- for chat, title generation, or image analysis in the chosen profile.

    @guarantee TechnologySection
        -- Section 5: Semantic Similarity toggle
        --   ("Enable duplicate search and related-post embeddings").
        --   Scripting Runtime row is read-only descriptive text: scripting capabilities
        --   are configured at the application layer and expose no runtime switch here.
        --   Save button persists with project metadata (settings_project form).

    @guarantee PublishingSection
        -- Section 6: SSH Mode (scp/rsync), Host, Username, Remote Path.
        --   Save + Clear buttons.

    @guarantee MCPSection
        -- Section 7: 7 agent rows, each with the agent label, its config-file path
        --   as a subtitle (or "not supported yet" note), and a toggle button.
        -- Supported agents (Claude Code, GitHub Copilot) toggle Add/Remove,
        --   writing/removing the bDS server entry in the agent config file.
        -- Unsupported agents render a disabled button.

    @guarantee DataMaintenanceSection
        -- Section 8: 7 rebuild buttons (Posts from Files, Media from Files,
        --   Scripts from Files, Templates from Files, Links,
        --   Regenerate Missing Thumbnails, Rebuild Embedding Index).
        -- Open Data Folder button.
        -- Each rebuild executes immediately (no confirmation).
        -- Runs as background task with progress in Tasks panel.

    @guarantee CollapsibleSections
        -- All 8 sections are collapsible.
        -- Section visibility respects search filter.
        -- Each visible section exposes a unique Control+Option or Control+Alt access key.
}

-- ─── Settings view actions ──────────────────────────────────

rule SettingsRebuild {
    when: SettingsRebuildRequested(entity_type)
    -- entity_type: posts | media | scripts | templates | links | thumbnails | embedding
    -- Executes immediately (no confirmation dialog)
    -- Runs as background task with progress visible in Tasks panel
    -- On completion: wholesale replaces store data for that entity type
    -- Sidebar and editors re-render with fresh data
}

-- ─── Style view ───────────────────────────────────────────────

-- The Style view is its OWN singleton tab (tab type `style`, see tabs.allium),
-- NOT one of the SettingsView collapsible sections. It is opened from the
-- Style menu/tab entry and renders the Pico CSS theme editor, distinct from
-- the `settings` tab. Shares the settings_editor code group only because both
-- persist into project metadata.

value StyleView {
    themes: List<StyleTheme>            -- 20 named Pico themes (see metadata.supported_pico_themes)
    selected_theme: String?            -- local selection, defaults to applied_theme
    applied_theme: String?             -- persisted picoTheme (default when none set)
    preview_mode: String                -- auto | light | dark
}

value StyleTheme {
    name: String
    accent_color: String                -- hex
    light_bg_color: String              -- hex
    dark_bg_color: String               -- hex
}

surface StyleViewSurface {
    context style: StyleView

    exposes:
        for t in style.themes:
            t.name
            t.accent_color
            t.light_bg_color
            t.dark_bg_color
        style.selected_theme
        style.applied_theme
        style.preview_mode

    provides:
        StyleThemeSelected(theme_name)
        StyleApplyRequested(style.selected_theme)
            when style.selected_theme != style.applied_theme
        StylePreviewModeChanged(mode)

    @guarantee SeparateTab
        -- Rendered in its own `style` singleton tab (tabs.allium), never inline
        -- in the settings view. Requires an active project; no project => no view.

    @guarantee ThemePicker
        -- Grid of theme buttons (one per Pico CSS theme).
        -- Each button: swatch with 3 colour tones (accent, light bg, dark bg) + theme name.
        -- Theme name shown via display transform: "-" -> " ", first letter capitalised.
        -- Selected theme highlighted with aria-pressed.

    @guarantee ControlsRow
        -- Preview Mode dropdown (Auto/Light/Dark).
        -- Apply Theme button, disabled when selected_theme matches applied_theme.

    @guarantee LivePreview
        -- Iframe at 127.0.0.1:4123/__style-preview with theme and mode query params.
        -- Updates live on selection or preview mode change.

    @guarantee PreviewModeLocal
        -- Preview mode dropdown controls iframe query param only.
        -- Does not persist — local UI state only.
}

rule StyleThemeSelect {
    when: StyleThemeSelected(theme_name)
    -- Updates iframe preview immediately (query param change)
    -- Does NOT persist until Apply is clicked
}

rule StyleApplyTheme {
    when: StyleApplyRequested(theme_name)
    -- Persists picoTheme to project metadata (meta/project.json)
    -- See engine_side_effects.allium UpdateProjectMetadataSideEffects
}
