use crate::fs_model::{
    absolute_time_label, breadcrumbs, canonical_display_path, create_file, create_folder,
    expand_tilde, filter_entries, home_dir, human_size, known_places, modified_label,
    path_for_display, recursive_search_cancellable, rename_path, scan_directory, trash_files_dir,
    validate_child_name, DirectorySnapshot, EntryKind, FileEntry, NameValidation, Place,
    RecursiveSearchOptions, RecursiveSearchResults, ScanOptions, SortKey,
};
use crate::ops::{
    active_child_reapers, launch_default, launch_new_window, launch_terminal_at, operation_detail,
    operation_percent, start_operation, undo_for_created_file, undo_for_created_folder,
    undo_for_rename, ClipboardMode, ClipboardPayload, CompletedAction, OperationEvent,
    OperationProgress, OperationRequest, UndoAction, CHILD_REAPER_LIMIT, OPERATION_DEPTH_LIMIT,
    OPERATION_ITEM_LIMIT, OPERATION_SOURCE_LIMIT,
};
use crate::plugins::{
    PluginDescriptor, PluginRegistry, KNOTWORK_NAME, MEDIA_PREVIEW_PLUGIN_ID,
    TEXT_PREVIEW_PLUGIN_ID,
};
use crate::theme::{field_label, muted_label, paint_background};
use crate::thumbnails::{
    media_class_for_path, modified_ms as thumbnail_modified_ms, MediaClass, ThumbnailManager,
    ThumbnailView,
};
use eframe::egui::{
    self, Align2, Color32, FontId, Pos2, Rect, RichText, Rounding, Sense, Shadow, Stroke, Vec2,
};
use std::collections::{BTreeSet, VecDeque};
use std::io::{BufRead, Read};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
use std::sync::mpsc::{self, Receiver, TryRecvError};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};

const DIRECTORY_ENTRY_LIMIT: usize = 20_000;
const DIRECTORY_ERROR_LIMIT: usize = 64;
const SEARCH_LIMIT: usize = 4_000;
const SEARCH_DIR_LIMIT: usize = 4_000;
const SEARCH_QUEUE_LIMIT: usize = 8_000;
const PREFERENCE_LINE_LIMIT: usize = 64;
const BACKGROUND_IMAGE_BYTES_LIMIT: u64 = 64 * 1024 * 1024;
const BACKGROUND_IMAGE_PIXELS_LIMIT: u64 = 16_000_000;
const MEDIA_PREVIEW_IMAGE_BYTES_LIMIT: u64 = 128 * 1024 * 1024;
const MEDIA_PREVIEW_IMAGE_PIXELS_LIMIT: u64 = 64_000_000;
const MEDIA_PREVIEW_IMAGE_MAX_EDGE: u32 = 2400;
const MEDIA_PREVIEW_IMAGE_CACHE_LIMIT: usize = 12;
const TEXT_PREVIEW_BYTES_LIMIT: u64 = 2 * 1024 * 1024;
const TEXT_PREVIEW_LINE_LIMIT: usize = 2_000;
const OPEN_SELECTION_LIMIT: usize = CHILD_REAPER_LIMIT;
const HISTORY_LIMIT: usize = 120;
const BOOKMARK_LIMIT: usize = 80;
const UNDO_LIMIT: usize = 40;
const LOG_LIMIT: usize = 220;
const VIDEO_HOVER_INTENT_SECONDS: f64 = 0.16;
const SEARCH_DEBOUNCE: Duration = Duration::from_millis(180);
const FOLDER_ICON_ASPECT: f32 = 1.22;

pub struct GuthApp {
    current_dir: PathBuf,
    path_input: String,
    location_entry_open: bool,
    search_focus_requested: bool,
    search_query: String,
    entries: Vec<FileEntry>,
    snapshot: Option<DirectorySnapshot>,
    deep_results: Option<RecursiveSearchResults>,
    deep_search_pending: Option<DeepSearchPending>,
    deep_search_rx: Option<Receiver<DeepSearchResult>>,
    deep_search_rx_generation: Option<u64>,
    deep_search_generation: Arc<AtomicU64>,
    bookmarks: Vec<PathBuf>,
    selected: BTreeSet<PathBuf>,
    selection_anchor: Option<PathBuf>,
    selection_focus: Option<PathBuf>,
    pin_drag_candidate: Option<PathBuf>,
    pinned_drop_rect: Option<Rect>,
    entry_drag_paths: Vec<PathBuf>,
    history: Vec<PathBuf>,
    history_cursor: usize,
    sort_key: SortKey,
    sort_reverse: bool,
    folders_first: bool,
    view_mode: ViewMode,
    show_hidden: bool,
    recursive_search: bool,
    clipboard: Option<ClipboardPayload>,
    undo_stack: Vec<UndoAction>,
    operation_rx: Option<Receiver<OperationEvent>>,
    operation_title: Option<String>,
    operation_progress: Option<OperationProgress>,
    operation_is_undo: bool,
    thumbnails: ThumbnailManager,
    plugins: PluginRegistry,
    media_preview_plugin_enabled: bool,
    text_preview_plugin_enabled: bool,
    media_preview: MediaPreviewState,
    hover: HoverState,
    hover_animation_active: bool,
    palette: AppPalette,
    motion_enabled: bool,
    hover_effects_enabled: bool,
    theme_opacity: f32,
    background_image_path: String,
    background_image_opacity: f32,
    background_texture: Option<egui::TextureHandle>,
    background_loaded_path: Option<PathBuf>,
    background_error: Option<String>,
    runtime: RuntimeInfo,
    status: String,
    logs: Vec<String>,
    dialogs: Dialogs,
    show_properties: bool,
    show_settings: bool,
    sidebar_collapsed: bool,
}

#[derive(Clone, Debug)]
pub struct RuntimeInfo {
    pub renderer: String,
    pub gpu_mode: String,
    pub shader: String,
    pub wgpu_backends: String,
    pub wgpu_power: String,
    pub frame_latency: String,
    pub vsync: bool,
}

impl RuntimeInfo {
    pub fn glow_default() -> Self {
        Self {
            renderer: "GLOW / OPENGL".to_string(),
            gpu_mode: "REQUIRED".to_string(),
            shader: "AUTO".to_string(),
            wgpu_backends: "N/A".to_string(),
            wgpu_power: "N/A".to_string(),
            frame_latency: "N/A".to_string(),
            vsync: true,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum AppPalette {
    NautilusLight,
    NautilusDark,
    MacosLight,
    MacosDark,
}

impl AppPalette {
    const ALL: [Self; 4] = [
        Self::NautilusLight,
        Self::NautilusDark,
        Self::MacosLight,
        Self::MacosDark,
    ];

    fn label(self) -> &'static str {
        match self {
            Self::NautilusLight => "Nautilus Light",
            Self::NautilusDark => "Nautilus Dark",
            Self::MacosLight => "macOS Light",
            Self::MacosDark => "macOS Dark",
        }
    }

    fn as_config(self) -> &'static str {
        match self {
            Self::NautilusLight => "nautilus_light",
            Self::NautilusDark => "nautilus_dark",
            Self::MacosLight => "macos_light",
            Self::MacosDark => "macos_dark",
        }
    }

    fn from_config(value: &str) -> Self {
        match value {
            "nautilus_dark" | "paper_dark" | "graphite" | "emerald" | "violet" | "amber"
            | "arctic" => Self::NautilusDark,
            "macos_light" | "cupertino_light" => Self::MacosLight,
            "macos_dark" | "cupertino_dark" => Self::MacosDark,
            _ => Self::NautilusDark,
        }
    }

    fn is_light(self) -> bool {
        matches!(self, Self::NautilusLight | Self::MacosLight)
    }

    fn accent(self, phase: f32) -> Color32 {
        let _ = phase;
        match self {
            Self::NautilusLight => Color32::from_rgb(53, 132, 228),
            Self::NautilusDark => Color32::from_rgb(53, 132, 228),
            Self::MacosLight => Color32::from_rgb(0, 122, 255),
            Self::MacosDark => Color32::from_rgb(10, 132, 255),
        }
    }

    fn background(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(246, 245, 244),
            Self::NautilusDark => Color32::from_rgb(12, 14, 24),
            Self::MacosLight => Color32::from_rgb(242, 242, 247),
            Self::MacosDark => Color32::from_rgb(12, 13, 22),
        }
    }

    fn panel_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(255, 255, 255),
            Self::NautilusDark => Color32::from_rgb(25, 27, 42),
            Self::MacosLight => Color32::from_rgb(255, 255, 255),
            Self::MacosDark => Color32::from_rgb(24, 25, 39),
        }
    }

    fn sidebar_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(242, 241, 239),
            Self::NautilusDark => Color32::from_rgb(20, 22, 36),
            Self::MacosLight => Color32::from_rgb(247, 247, 250),
            Self::MacosDark => Color32::from_rgb(19, 20, 34),
        }
    }

    fn content_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(255, 255, 255),
            Self::NautilusDark => Color32::from_rgb(13, 15, 26),
            Self::MacosLight => Color32::from_rgb(255, 255, 255),
            Self::MacosDark => Color32::from_rgb(12, 14, 24),
        }
    }

    fn surface(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(249, 249, 248),
            Self::NautilusDark => Color32::from_rgb(28, 30, 46),
            Self::MacosLight => Color32::from_rgb(247, 247, 249),
            Self::MacosDark => Color32::from_rgb(27, 29, 44),
        }
    }

    fn elevated_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(255, 255, 255),
            Self::NautilusDark => Color32::from_rgb(31, 34, 52),
            Self::MacosLight => Color32::from_rgb(255, 255, 255),
            Self::MacosDark => Color32::from_rgb(31, 33, 52),
        }
    }

    fn recessed_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(244, 244, 243),
            Self::NautilusDark => Color32::from_rgb(14, 16, 28),
            Self::MacosLight => Color32::from_rgb(244, 244, 247),
            Self::MacosDark => Color32::from_rgb(13, 15, 27),
        }
    }

    fn button_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(248, 248, 247),
            Self::NautilusDark => Color32::from_rgb(34, 37, 56),
            Self::MacosLight => Color32::from_rgb(246, 246, 248),
            Self::MacosDark => Color32::from_rgb(34, 36, 55),
        }
    }

    fn hover_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgba_unmultiplied(53, 132, 228, 18),
            Self::NautilusDark => Color32::from_rgba_unmultiplied(53, 132, 228, 34),
            Self::MacosLight => Color32::from_rgba_unmultiplied(0, 122, 255, 16),
            Self::MacosDark => Color32::from_rgba_unmultiplied(10, 132, 255, 34),
        }
    }

    fn selected_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgba_unmultiplied(53, 132, 228, 58),
            Self::NautilusDark => Color32::from_rgba_unmultiplied(53, 132, 228, 66),
            Self::MacosLight => Color32::from_rgba_unmultiplied(0, 122, 255, 48),
            Self::MacosDark => Color32::from_rgba_unmultiplied(10, 132, 255, 70),
        }
    }

    fn accent_soft(self) -> Color32 {
        Color32::from_rgba_unmultiplied(
            self.accent(0.0).r(),
            self.accent(0.0).g(),
            self.accent(0.0).b(),
            if self.is_light() { 18 } else { 34 },
        )
    }

    fn row_alt_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgba_unmultiplied(30, 32, 36, 5),
            Self::NautilusDark => Color32::from_rgba_unmultiplied(255, 255, 255, 5),
            Self::MacosLight => Color32::from_rgba_unmultiplied(0, 0, 0, 5),
            Self::MacosDark => Color32::from_rgba_unmultiplied(255, 255, 255, 5),
        }
    }

    fn hairline(self) -> Color32 {
        Color32::from_rgba_unmultiplied(
            self.border().r(),
            self.border().g(),
            self.border().b(),
            if self.is_light() { 156 } else { 128 },
        )
    }

    fn header_fill(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(240, 239, 237),
            Self::NautilusDark => Color32::from_rgb(17, 19, 32),
            Self::MacosLight => Color32::from_rgb(247, 247, 249),
            Self::MacosDark => Color32::from_rgb(16, 18, 31),
        }
    }

    fn border(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(214, 213, 211),
            Self::NautilusDark => Color32::from_rgb(49, 52, 76),
            Self::MacosLight => Color32::from_rgb(216, 216, 221),
            Self::MacosDark => Color32::from_rgb(50, 52, 76),
        }
    }

    fn text(self) -> Color32 {
        if self.is_light() {
            Color32::from_rgb(32, 33, 36)
        } else {
            Color32::from_rgb(246, 245, 244)
        }
    }

    fn secondary_text(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(94, 92, 100),
            Self::NautilusDark => Color32::from_rgb(204, 207, 228),
            Self::MacosLight => Color32::from_rgb(60, 60, 67),
            Self::MacosDark => Color32::from_rgb(213, 216, 238),
        }
    }

    fn muted(self) -> Color32 {
        match self {
            Self::NautilusLight => Color32::from_rgb(119, 118, 123),
            Self::NautilusDark => Color32::from_rgb(143, 148, 178),
            Self::MacosLight => Color32::from_rgb(99, 99, 102),
            Self::MacosDark => Color32::from_rgb(151, 156, 188),
        }
    }

    fn visuals(self) -> egui::Visuals {
        let mut visuals = if self.is_light() {
            egui::Visuals::light()
        } else {
            egui::Visuals::dark()
        };
        visuals.override_text_color = Some(self.text());
        visuals.panel_fill = self.background();
        visuals.window_fill = self.panel_fill();
        visuals.extreme_bg_color = self.background();
        visuals.faint_bg_color = self.surface();
        visuals.widgets.noninteractive.bg_fill = self.surface();
        visuals.widgets.inactive.bg_fill = self.surface();
        visuals.widgets.noninteractive.bg_stroke = Stroke::new(1.0, self.hairline());
        visuals.widgets.inactive.bg_stroke = Stroke::new(1.0, self.hairline());
        visuals.widgets.hovered.bg_fill = self.hover_fill();
        visuals.widgets.active.bg_fill = self.selected_fill();
        visuals.widgets.inactive.fg_stroke = Stroke::new(1.0, self.secondary_text());
        visuals.widgets.hovered.fg_stroke = Stroke::new(1.0, self.text());
        visuals.widgets.active.fg_stroke = Stroke::new(1.0, self.text());
        visuals.selection.bg_fill = self.accent(0.0);
        visuals.selection.stroke = Stroke::new(1.0, self.text());
        visuals.window_fill = self.panel_fill();
        visuals.window_stroke = Stroke::new(1.0, self.border());
        visuals.window_rounding = Rounding::same(theme_rounding(self) + 8.0);
        visuals.window_shadow = Shadow::NONE;
        visuals.popup_shadow = Shadow::NONE;
        visuals.menu_rounding = Rounding::same(theme_rounding(self) + 8.0);
        visuals
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ViewMode {
    Grid,
    List,
}

impl ViewMode {
    fn as_config(self) -> &'static str {
        match self {
            Self::Grid => "grid",
            Self::List => "list",
        }
    }

    fn from_config(value: &str) -> Self {
        match value {
            "list" => Self::List,
            _ => Self::Grid,
        }
    }
}

#[derive(Default)]
struct Dialogs {
    new_folder_open: bool,
    new_folder_name: String,
    new_folder_focus_requested: bool,
    new_file_open: bool,
    new_file_name: String,
    new_file_focus_requested: bool,
    rename_open: bool,
    rename_path: Option<PathBuf>,
    rename_name: String,
    rename_focus_requested: bool,
    delete_confirm: Option<DeleteIntent>,
    error: Option<String>,
}

impl Dialogs {
    fn is_open(&self) -> bool {
        self.new_folder_open
            || self.new_file_open
            || self.rename_open
            || self.delete_confirm.is_some()
            || self.error.is_some()
    }
}

struct DeepSearchPending {
    generation: u64,
    requested_at: Instant,
    root: PathBuf,
    query: String,
    options: RecursiveSearchOptions,
}

struct DeepSearchResult {
    generation: u64,
    results: RecursiveSearchResults,
}

#[derive(Clone, Debug)]
struct HoverState {
    path: Option<PathBuf>,
    name: String,
    class: MediaClass,
    started_at: f64,
    last_seen_at: f64,
}

impl Default for HoverState {
    fn default() -> Self {
        Self {
            path: None,
            name: String::new(),
            class: MediaClass::File,
            started_at: 0.0,
            last_seen_at: 0.0,
        }
    }
}

impl HoverState {
    fn observe(&mut self, entry: &FileEntry, class: MediaClass, now: f64) {
        if self.path.as_deref() != Some(entry.path.as_path()) {
            self.started_at = now;
        }
        self.path = Some(entry.path.clone());
        self.name = entry.name.clone();
        self.class = class;
        self.last_seen_at = now;
    }

    fn intent_ready(&self, path: &Path, now: f64, delay_seconds: f64) -> bool {
        self.path.as_deref() == Some(path) && now - self.started_at >= delay_seconds
    }

    fn clear_if_stale(&mut self, now: f64) {
        if self.path.is_some() && now - self.last_seen_at > 0.18 {
            self.path = None;
            self.name.clear();
        }
    }
}

#[derive(Clone, Debug)]
struct DeleteIntent {
    paths: Vec<PathBuf>,
    permanent: bool,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct SelectionSummary {
    count: usize,
    folders: usize,
    files: usize,
    file_bytes: u64,
}

struct MediaPreviewState {
    open: bool,
    entry: Option<FileEntry>,
    active_plugin: Option<PreviewPluginKind>,
    image_texture: Option<egui::TextureHandle>,
    loaded_key: Option<MediaPreviewKey>,
    loading_key: Option<MediaPreviewKey>,
    image_worker_key: Option<MediaPreviewKey>,
    image_rx: Option<Receiver<MediaPreviewImageResult>>,
    image_pending: Option<MediaPreviewPending>,
    image_cache: VecDeque<MediaPreviewCachedImage>,
    cache_clock: u64,
    text_document: Option<TextPreviewDocument>,
    text_loaded_key: Option<MediaPreviewKey>,
    text_loading_key: Option<MediaPreviewKey>,
    text_worker_key: Option<MediaPreviewKey>,
    text_rx: Option<Receiver<TextPreviewResult>>,
    text_pending: Option<MediaPreviewPending>,
    text_view_buffer: String,
    text_view_buffer_key: Option<MediaPreviewKey>,
    text_view_buffer_line_numbers: bool,
    text_line_numbers: bool,
    text_find_query: String,
    text_find_last_query: String,
    text_find_loaded_key: Option<MediaPreviewKey>,
    text_find_match_count: usize,
    text_find_focus_requested: bool,
    error: Option<String>,
    zoom: f32,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PreviewPluginKind {
    Media,
    Text,
}

struct MediaPreviewImageResult {
    key: MediaPreviewKey,
    image: Result<egui::ColorImage, String>,
}

struct TextPreviewResult {
    key: MediaPreviewKey,
    document: Result<TextPreviewDocument, String>,
}

struct MediaPreviewPending {
    key: MediaPreviewKey,
    path: PathBuf,
}

struct TextPreviewDocument {
    content: String,
    numbered_content: String,
    bytes_read: usize,
    line_count: usize,
    truncated: bool,
    lossy: bool,
}

struct MediaPreviewCachedImage {
    key: MediaPreviewKey,
    texture: egui::TextureHandle,
    last_used: u64,
}

impl Default for MediaPreviewState {
    fn default() -> Self {
        Self {
            open: false,
            entry: None,
            active_plugin: None,
            image_texture: None,
            loaded_key: None,
            loading_key: None,
            image_worker_key: None,
            image_rx: None,
            image_pending: None,
            image_cache: VecDeque::new(),
            cache_clock: 0,
            text_document: None,
            text_loaded_key: None,
            text_loading_key: None,
            text_worker_key: None,
            text_rx: None,
            text_pending: None,
            text_view_buffer: String::new(),
            text_view_buffer_key: None,
            text_view_buffer_line_numbers: true,
            text_line_numbers: true,
            text_find_query: String::new(),
            text_find_last_query: String::new(),
            text_find_loaded_key: None,
            text_find_match_count: 0,
            text_find_focus_requested: false,
            error: None,
            zoom: 1.0,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct MediaPreviewKey {
    path: PathBuf,
    size: u64,
    modified_ms: u128,
}

impl MediaPreviewKey {
    fn from_entry(entry: &FileEntry) -> Self {
        Self {
            path: entry.path.clone(),
            size: entry.size,
            modified_ms: thumbnail_modified_ms(entry.modified),
        }
    }
}

#[derive(Clone, Copy, Debug)]
enum HistoryMode {
    Push,
    Replace,
}

#[derive(Clone, Debug)]
struct Preferences {
    view_mode: ViewMode,
    show_hidden: bool,
    recursive_search: bool,
    sort_key: SortKey,
    sort_reverse: bool,
    folders_first: bool,
    palette: AppPalette,
    motion_enabled: bool,
    hover_effects_enabled: bool,
    theme_opacity: f32,
    background_image_path: String,
    background_image_opacity: f32,
    sidebar_collapsed: bool,
    media_preview_plugin_enabled: bool,
    text_preview_plugin_enabled: bool,
}

impl Default for Preferences {
    fn default() -> Self {
        Self {
            view_mode: ViewMode::List,
            show_hidden: false,
            recursive_search: false,
            sort_key: SortKey::Name,
            sort_reverse: false,
            folders_first: true,
            palette: AppPalette::NautilusDark,
            motion_enabled: true,
            hover_effects_enabled: true,
            theme_opacity: 1.0,
            background_image_path: String::new(),
            background_image_opacity: 0.22,
            sidebar_collapsed: false,
            media_preview_plugin_enabled: true,
            text_preview_plugin_enabled: true,
        }
    }
}

impl GuthApp {
    pub fn new(
        cc: &eframe::CreationContext<'_>,
        start_path: Option<PathBuf>,
        runtime: RuntimeInfo,
    ) -> Self {
        migrate_legacy_config();
        let preferences = load_preferences();
        cc.egui_ctx.set_visuals(preferences.palette.visuals());
        let bookmarks = load_bookmarks();

        let requested = start_path.unwrap_or_else(home_dir);
        let initial_dir = if requested.is_dir() {
            requested
        } else {
            requested
                .parent()
                .map(Path::to_path_buf)
                .unwrap_or_else(home_dir)
        };
        let current_dir = usable_start_dir(initial_dir);

        let mut app = Self {
            path_input: current_dir.display().to_string(),
            location_entry_open: false,
            search_focus_requested: false,
            current_dir: current_dir.clone(),
            search_query: String::new(),
            entries: Vec::new(),
            snapshot: None,
            deep_results: None,
            deep_search_pending: None,
            deep_search_rx: None,
            deep_search_rx_generation: None,
            deep_search_generation: Arc::new(AtomicU64::new(0)),
            bookmarks,
            selected: BTreeSet::new(),
            selection_anchor: None,
            selection_focus: None,
            pin_drag_candidate: None,
            pinned_drop_rect: None,
            entry_drag_paths: Vec::new(),
            history: vec![current_dir],
            history_cursor: 0,
            sort_key: preferences.sort_key,
            sort_reverse: preferences.sort_reverse,
            folders_first: preferences.folders_first,
            view_mode: preferences.view_mode,
            show_hidden: preferences.show_hidden,
            recursive_search: preferences.recursive_search,
            clipboard: None,
            undo_stack: Vec::new(),
            operation_rx: None,
            operation_title: None,
            operation_progress: None,
            operation_is_undo: false,
            thumbnails: ThumbnailManager::new(),
            plugins: PluginRegistry::builtin(),
            media_preview_plugin_enabled: preferences.media_preview_plugin_enabled,
            text_preview_plugin_enabled: preferences.text_preview_plugin_enabled,
            media_preview: MediaPreviewState::default(),
            hover: HoverState::default(),
            hover_animation_active: false,
            palette: preferences.palette,
            motion_enabled: preferences.motion_enabled,
            hover_effects_enabled: preferences.hover_effects_enabled,
            theme_opacity: preferences.theme_opacity,
            background_image_path: preferences.background_image_path,
            background_image_opacity: preferences.background_image_opacity,
            background_texture: None,
            background_loaded_path: None,
            background_error: None,
            runtime,
            status: "Ready".to_string(),
            logs: vec!["Guth ready".to_string()],
            dialogs: Dialogs::default(),
            show_properties: false,
            show_settings: false,
            sidebar_collapsed: preferences.sidebar_collapsed,
        };
        app.refresh_current_dir();
        app
    }

    fn refresh_current_dir(&mut self) -> bool {
        let options = ScanOptions {
            show_hidden: self.show_hidden,
            sort_key: self.sort_key,
            reverse: self.sort_reverse,
            folders_first: self.folders_first,
            entry_limit: DIRECTORY_ENTRY_LIMIT,
            error_limit: DIRECTORY_ERROR_LIMIT,
        };

        match scan_directory(&self.current_dir, options) {
            Ok(snapshot) => {
                self.entries = snapshot.entries.clone();
                let visible: BTreeSet<PathBuf> = self
                    .entries
                    .iter()
                    .map(|entry| entry.path.clone())
                    .collect();
                self.selected.retain(|path| visible.contains(path));
                if !self
                    .selection_anchor
                    .as_ref()
                    .is_some_and(|path| visible.contains(path))
                {
                    self.selection_anchor = self.selected.iter().next().cloned();
                }
                if !self
                    .selection_focus
                    .as_ref()
                    .is_some_and(|path| visible.contains(path))
                {
                    self.selection_focus = self.selection_anchor.clone();
                }
                self.status = if snapshot.scan_limited {
                    format!(
                        "Ready · showing {} items · scan capped at {} entries",
                        snapshot.entries.len(),
                        snapshot.entry_limit
                    )
                } else {
                    format!(
                        "Ready · {} items · {} hidden",
                        snapshot.entries.len(),
                        snapshot.hidden_entries
                    )
                };
                if snapshot.scan_limited {
                    self.push_log(format!(
                        "SCAN CAPPED // {} // {} entries",
                        self.current_dir.display(),
                        snapshot.entry_limit
                    ));
                }
                if snapshot.error_limited {
                    self.push_log(format!(
                        "SCAN WARNINGS CAPPED // {} warnings shown",
                        snapshot.error_limit
                    ));
                }
                for error in &snapshot.errors {
                    self.push_log(format!("SCAN WARNING // {error}"));
                }
                self.snapshot = Some(snapshot);
                self.update_deep_search();
                true
            }
            Err(error) => {
                self.status = "Could not load folder".to_string();
                self.push_log(format!(
                    "LOAD FAILED // {} // {error}",
                    self.current_dir.display()
                ));
                false
            }
        }
    }

    fn update_deep_search(&mut self) {
        let generation = self
            .deep_search_generation
            .fetch_add(1, AtomicOrdering::Relaxed)
            .wrapping_add(1);
        self.deep_search_pending = None;
        self.deep_results = None;

        if self.recursive_search && !self.search_query.trim().is_empty() {
            self.status = "Searching subfolders".to_string();
            self.deep_search_pending = Some(DeepSearchPending {
                generation,
                requested_at: Instant::now(),
                root: self.current_dir.clone(),
                query: self.search_query.trim().to_string(),
                options: RecursiveSearchOptions {
                    show_hidden: self.show_hidden,
                    sort_key: self.sort_key,
                    reverse: self.sort_reverse,
                    folders_first: self.folders_first,
                    result_limit: SEARCH_LIMIT,
                    dir_limit: SEARCH_DIR_LIMIT,
                    queue_limit: SEARCH_QUEUE_LIMIT,
                },
            });
        }
    }

    fn start_deep_search_if_ready(&mut self) {
        if self.deep_search_rx.is_some() {
            return;
        }
        let Some(pending) = self.deep_search_pending.as_ref() else {
            return;
        };
        if pending.requested_at.elapsed() < SEARCH_DEBOUNCE {
            return;
        }

        let Some(pending) = self.deep_search_pending.take() else {
            return;
        };
        let generation = pending.generation;
        let generation_clock = Arc::clone(&self.deep_search_generation);
        let (tx, rx) = mpsc::sync_channel(1);
        self.deep_search_rx = Some(rx);
        self.deep_search_rx_generation = Some(generation);
        if let Err(error) = thread::Builder::new()
            .name("guth-recursive-search".to_string())
            .spawn(move || {
                let results = recursive_search_cancellable(
                    &pending.root,
                    &pending.query,
                    pending.options,
                    || generation_clock.load(AtomicOrdering::Relaxed) != generation,
                );
                let _ = tx.send(DeepSearchResult {
                    generation,
                    results,
                });
            })
        {
            self.deep_search_rx = None;
            self.deep_search_rx_generation = None;
            self.report_error(
                "Could not search subfolders",
                "SEARCH",
                format!("Could not start recursive search worker: {error}"),
            );
        }
    }

    fn poll_deep_search(&mut self, ctx: &egui::Context) {
        let received = self
            .deep_search_rx
            .as_ref()
            .and_then(|rx| match rx.try_recv() {
                Ok(result) => Some(Ok(result)),
                Err(TryRecvError::Empty) => None,
                Err(TryRecvError::Disconnected) => Some(Err(
                    "Recursive search worker stopped before reporting results".to_string(),
                )),
            });
        let Some(received) = received else {
            return;
        };
        let received_generation = self.deep_search_rx_generation.take();
        self.deep_search_rx = None;

        match received {
            Ok(result)
                if result.generation
                    == self.deep_search_generation.load(AtomicOrdering::Relaxed) =>
            {
                self.status = if result.results.limited() {
                    format!(
                        "{} search matches · capped after {} folders",
                        result.results.entries.len(),
                        result.results.visited_dirs
                    )
                } else {
                    format!("{} search matches", result.results.entries.len())
                };
                self.deep_results = Some(result.results);
                ctx.request_repaint();
            }
            Ok(_) => {}
            Err(error)
                if received_generation
                    == Some(self.deep_search_generation.load(AtomicOrdering::Relaxed)) =>
            {
                self.report_error("Search interrupted", "SEARCH", error);
            }
            Err(_) => {}
        }
    }

    fn deep_search_active(&self) -> bool {
        self.deep_search_pending.is_some()
            || self.deep_search_rx_generation
                == Some(self.deep_search_generation.load(AtomicOrdering::Relaxed))
    }

    fn visible_entries(&self) -> Vec<FileEntry> {
        if let Some(results) = &self.deep_results {
            return results.entries.clone();
        }
        filter_entries(&self.entries, &self.search_query)
    }

    fn navigate_to(&mut self, path: PathBuf, mode: HistoryMode) {
        let path = expand_navigation_path(path, &self.current_dir);
        if path == trash_files_dir() && !path.exists() {
            if let Err(error) = std::fs::create_dir_all(&path) {
                self.status = "Could not open Trash".to_string();
                self.push_log(format!("TRASH OPEN FAILED // {error}"));
                return;
            }
        }

        if !path.exists() {
            self.status = "Path not found".to_string();
            self.push_log(format!("PATH NOT FOUND // {}", path.display()));
            self.path_input = self.current_dir.display().to_string();
            return;
        }

        if !path.is_dir() {
            self.open_path(&path);
            return;
        }

        let normalized = canonical_display_path(&path);
        self.current_dir = normalized.clone();
        self.path_input = normalized.display().to_string();
        self.selected.clear();
        self.selection_anchor = None;
        self.selection_focus = None;
        self.pin_drag_candidate = None;
        self.pinned_drop_rect = None;
        self.entry_drag_paths.clear();
        match mode {
            HistoryMode::Push => self.push_history(normalized),
            HistoryMode::Replace => {
                if let Some(slot) = self.history.get_mut(self.history_cursor) {
                    *slot = normalized;
                }
            }
        }
        self.refresh_current_dir();
        self.push_log(format!("LOCATION // {}", self.current_dir.display()));
    }

    fn push_history(&mut self, path: PathBuf) {
        if self.history.get(self.history_cursor) == Some(&path) {
            return;
        }
        self.history.truncate(self.history_cursor + 1);
        self.history.push(path);
        self.history_cursor = self.history.len().saturating_sub(1);
        self.trim_history();
    }

    fn trim_history(&mut self) {
        if self.history.len() <= HISTORY_LIMIT {
            return;
        }
        let overflow = self.history.len() - HISTORY_LIMIT;
        self.history.drain(0..overflow);
        self.history_cursor = self.history_cursor.saturating_sub(overflow);
    }

    fn set_sort_key(&mut self, key: SortKey) {
        if self.sort_key == key {
            self.sort_reverse = !self.sort_reverse;
        } else {
            self.sort_key = key;
            self.sort_reverse = false;
        }
        self.save_preferences();
        self.refresh_current_dir();
    }

    fn go_back(&mut self) {
        if self.history_cursor > 0 {
            self.history_cursor -= 1;
            if let Some(path) = self.history.get(self.history_cursor).cloned() {
                self.navigate_to(path, HistoryMode::Replace);
            }
        }
    }

    fn go_forward(&mut self) {
        if self.history_cursor + 1 < self.history.len() {
            self.history_cursor += 1;
            if let Some(path) = self.history.get(self.history_cursor).cloned() {
                self.navigate_to(path, HistoryMode::Replace);
            }
        }
    }

    fn go_parent(&mut self) {
        if let Some(parent) = self.current_dir.parent() {
            self.navigate_to(parent.to_path_buf(), HistoryMode::Push);
        }
    }

    fn open_entry(&mut self, entry: &FileEntry) {
        if let Some(plugin_kind) =
            preview_plugin_kind_for_entry(entry).filter(|kind| self.preview_plugin_enabled(*kind))
        {
            self.set_media_preview_entry(entry.clone(), plugin_kind, true);
            return;
        }
        self.open_path(&entry.path);
    }

    fn open_path_external(&mut self, path: &Path) {
        match launch_default(path) {
            Ok(()) => {
                self.status = "Opening with system app".to_string();
                self.push_log(format!("OPEN EXTERNAL // {}", path.display()));
            }
            Err(error) => {
                self.report_error("Could not open externally", "OPEN EXTERNAL", error);
            }
        }
    }

    fn open_path(&mut self, path: &Path) {
        if path.is_dir() {
            self.navigate_to(path.to_path_buf(), HistoryMode::Push);
            return;
        }
        match launch_default(path) {
            Ok(()) => {
                self.status = "Opening file".to_string();
                self.push_log(format!("OPEN // {}", path.display()));
            }
            Err(error) => {
                self.report_error("Could not open file", "OPEN", error);
            }
        }
    }

    fn open_selection_externally(&mut self) {
        let selected = self.selected_entries();
        if selected.is_empty() {
            self.status = "No selection".to_string();
            return;
        }
        if selected.len() > OPEN_SELECTION_LIMIT {
            self.status = format!("Open selection limit reached ({OPEN_SELECTION_LIMIT})");
            self.push_log(format!(
                "OPEN EXTERNAL BLOCKED // {} ITEMS // LIMIT {}",
                selected.len(),
                OPEN_SELECTION_LIMIT
            ));
            return;
        }
        for entry in selected {
            self.open_path_external(&entry.path);
        }
    }

    fn open_selection(&mut self) {
        let selected = self.selected_entries();
        if selected.len() == 1 {
            self.open_entry(&selected[0]);
            return;
        }
        let openable_count = selected
            .iter()
            .filter(|entry| entry.path.is_file() || entry.kind == EntryKind::Symlink)
            .count();
        if openable_count > OPEN_SELECTION_LIMIT {
            self.status = format!("Open selection limit reached ({OPEN_SELECTION_LIMIT})");
            self.push_log(format!(
                "OPEN BLOCKED // {} FILES // LIMIT {}",
                openable_count, OPEN_SELECTION_LIMIT
            ));
            return;
        }
        for entry in selected {
            if entry.path.is_file() || entry.kind == EntryKind::Symlink {
                self.open_path(&entry.path);
            }
        }
    }

    fn toggle_media_preview_plugin(&mut self) {
        if self.media_preview.open {
            self.close_media_preview_plugin();
        } else {
            self.open_media_preview_plugin();
        }
    }

    fn open_media_preview_plugin(&mut self) {
        let Some((entry, plugin_kind)) = self.selected_preview_candidate() else {
            self.status = "Select an image, video, text, or code file to preview".to_string();
            return;
        };
        if !self.preview_plugin_enabled(plugin_kind) {
            self.status = format!(
                "{} plugin is disabled",
                self.preview_plugin_descriptor(plugin_kind).name
            );
            return;
        }
        self.set_media_preview_entry(entry, plugin_kind, true);
    }

    fn set_media_preview_entry(
        &mut self,
        entry: FileEntry,
        plugin_kind: PreviewPluginKind,
        log_plugin: bool,
    ) {
        let key = MediaPreviewKey::from_entry(&entry);
        if self.media_preview.loaded_key.as_ref() != Some(&key) {
            self.media_preview.image_texture = None;
            self.media_preview.loaded_key = None;
            self.media_preview.loading_key = None;
            self.media_preview.image_pending = None;
        }
        if self.media_preview.text_loaded_key.as_ref() != Some(&key) {
            self.media_preview.text_document = None;
            self.media_preview.text_loaded_key = None;
            self.media_preview.text_loading_key = None;
            self.media_preview.text_pending = None;
            self.media_preview.text_view_buffer.clear();
            self.media_preview.text_view_buffer_key = None;
            self.media_preview.text_find_loaded_key = None;
        }
        if self.media_preview.loaded_key.as_ref() != Some(&key)
            && self.media_preview.text_loaded_key.as_ref() != Some(&key)
        {
            self.media_preview.error = None;
        }
        self.media_preview.active_plugin = Some(plugin_kind);
        self.media_preview.entry = Some(entry);
        self.media_preview.open = true;
        if let Some(entry) = self.media_preview.entry.clone() {
            self.status = format!("Previewing {}", entry.name);
            if log_plugin {
                self.push_log(format!(
                    "PLUGIN // {} // {}",
                    self.preview_plugin_descriptor(plugin_kind).name,
                    entry.path.display()
                ));
            }
        }
    }

    fn close_media_preview_plugin(&mut self) {
        self.media_preview.open = false;
        self.media_preview.entry = None;
        self.media_preview.active_plugin = None;
        self.media_preview.image_texture = None;
        self.media_preview.loaded_key = None;
        self.media_preview.loading_key = None;
        self.media_preview.image_pending = None;
        self.media_preview.text_document = None;
        self.media_preview.text_loaded_key = None;
        self.media_preview.text_loading_key = None;
        self.media_preview.text_pending = None;
        self.media_preview.text_view_buffer.clear();
        self.media_preview.text_view_buffer_key = None;
        self.media_preview.text_find_query.clear();
        self.media_preview.text_find_last_query.clear();
        self.media_preview.text_find_loaded_key = None;
        self.media_preview.text_find_match_count = 0;
        self.media_preview.text_find_focus_requested = false;
        self.media_preview.error = None;
        self.media_preview.zoom = 1.0;
    }

    fn step_media_preview_plugin(&mut self, direction: isize) {
        let entries = self.previewable_entries();
        if entries.len() < 2 {
            return;
        }
        let current_path = self
            .media_preview
            .entry
            .as_ref()
            .map(|entry| entry.path.as_path());
        let current_index = current_path
            .and_then(|path| entries.iter().position(|entry| entry.path == path))
            .unwrap_or(0);
        let len = entries.len() as isize;
        let next_index = (current_index as isize + direction).rem_euclid(len) as usize;
        let entry = entries[next_index].clone();
        let Some(plugin_kind) = preview_plugin_kind_for_entry(&entry) else {
            return;
        };
        self.set_single_selection(entry.path.clone());
        self.set_media_preview_entry(entry, plugin_kind, false);
    }

    fn adjust_media_preview_zoom(&mut self, amount: f32) {
        self.media_preview.zoom = (self.media_preview.zoom + amount).clamp(0.5, 2.5);
    }

    fn previewable_selection(&self) -> Option<FileEntry> {
        self.selected_entries()
            .into_iter()
            .find(|entry| self.preview_plugin_kind_enabled_for_entry(entry))
    }

    fn previewable_entries(&self) -> Vec<FileEntry> {
        self.visible_entries()
            .into_iter()
            .filter(|entry| self.preview_plugin_kind_enabled_for_entry(entry))
            .collect()
    }

    fn selected_preview_candidate(&self) -> Option<(FileEntry, PreviewPluginKind)> {
        self.selected_entries()
            .into_iter()
            .find_map(|entry| preview_plugin_kind_for_entry(&entry).map(|kind| (entry, kind)))
    }

    fn preview_plugin_kind_enabled_for_entry(&self, entry: &FileEntry) -> bool {
        preview_plugin_kind_for_entry(entry)
            .map(|kind| self.preview_plugin_enabled(kind))
            .unwrap_or(false)
    }

    fn preview_plugin_enabled(&self, kind: PreviewPluginKind) -> bool {
        match kind {
            PreviewPluginKind::Media => self.media_preview_plugin_enabled,
            PreviewPluginKind::Text => self.text_preview_plugin_enabled,
        }
    }

    fn preview_plugin_descriptor(&self, kind: PreviewPluginKind) -> PluginDescriptor {
        match kind {
            PreviewPluginKind::Media => self.plugins.media_preview(),
            PreviewPluginKind::Text => self.plugins.text_preview(),
        }
    }

    fn selected_entries(&self) -> Vec<FileEntry> {
        self.visible_entries()
            .into_iter()
            .filter(|entry| self.selected.contains(&entry.path))
            .collect()
    }

    fn selected_paths(&self) -> Vec<PathBuf> {
        self.selected_entries()
            .into_iter()
            .map(|entry| entry.path)
            .collect()
    }

    fn selected_paths_for_operation(&mut self) -> Option<Vec<PathBuf>> {
        let paths = self.selected_paths();
        self.check_operation_sources(paths)
    }

    fn copy_selection_text(&mut self, ctx: &egui::Context, names_only: bool) {
        let entries = self.selected_entries();
        if entries.is_empty() {
            return;
        }
        let text = entries
            .iter()
            .map(|entry| {
                if names_only {
                    entry.name.clone()
                } else {
                    entry.path.display().to_string()
                }
            })
            .collect::<Vec<_>>()
            .join("\n");
        ctx.copy_text(text);
        let label = if names_only { "names" } else { "paths" };
        self.status = format!("Copied {} {label}", entries.len());
        self.push_log(format!("COPY TEXT // {} {}", entries.len(), label));
    }

    fn show_entry_in_folder(&mut self, entry: &FileEntry) {
        let Some(parent) = entry.path.parent().map(Path::to_path_buf) else {
            self.status = "Item has no containing folder".to_string();
            return;
        };
        if !parent.is_dir() {
            self.status = "Containing folder is not available".to_string();
            self.push_log(format!("SHOW IN FOLDER FAILED // {}", entry.path.display()));
            return;
        }

        let target = entry.path.clone();
        let name = entry.name.clone();
        self.navigate_to(parent, HistoryMode::Push);
        if self.entries.iter().any(|entry| entry.path == target) {
            self.set_single_selection(target.clone());
        }
        self.status = format!("Showing {name}");
        self.push_log(format!("SHOW IN FOLDER // {}", target.display()));
    }

    fn check_operation_sources(&mut self, paths: Vec<PathBuf>) -> Option<Vec<PathBuf>> {
        if paths.len() > OPERATION_SOURCE_LIMIT {
            self.status = format!("Operation source limit reached ({OPERATION_SOURCE_LIMIT})");
            self.push_log(format!(
                "OPERATION BLOCKED // {} SOURCES // LIMIT {}",
                paths.len(),
                OPERATION_SOURCE_LIMIT
            ));
            return None;
        }
        Some(paths)
    }

    fn primary_selection(&self) -> Option<FileEntry> {
        self.selected_entries().into_iter().next()
    }

    fn set_single_selection(&mut self, path: PathBuf) {
        self.selected.clear();
        self.selected.insert(path.clone());
        self.selection_anchor = Some(path.clone());
        self.selection_focus = Some(path);
    }

    fn toggle_selection(&mut self, path: PathBuf) {
        if !self.selected.remove(&path) {
            self.selected.insert(path.clone());
            self.selection_anchor = Some(path.clone());
            self.selection_focus = Some(path);
        } else if self.selection_anchor.as_ref() == Some(&path) {
            self.selection_anchor = self.selected.iter().next().cloned();
            self.selection_focus = self.selection_anchor.clone();
        } else if self.selection_focus.as_ref() == Some(&path) {
            self.selection_focus = self.selection_anchor.clone();
        }
    }

    fn clear_selection(&mut self) {
        let count = self.selected.len();
        self.reset_selection();
        if count > 0 {
            self.status = "Selection cleared".to_string();
        }
    }

    fn reset_selection(&mut self) {
        self.selected.clear();
        self.selection_anchor = None;
        self.selection_focus = None;
    }

    fn focus_operation_results(&mut self, paths: &[PathBuf]) {
        let paths = operation_results_in_directory(paths, &self.current_dir);
        if paths.is_empty() {
            return;
        }
        self.selection_anchor = paths.first().cloned();
        self.selection_focus = paths.last().cloned();
        self.selected = paths.into_iter().collect();
    }

    fn select_entry_with_modifiers(&mut self, path: PathBuf, modifiers: egui::Modifiers) {
        let toggle = modifiers.command || modifiers.ctrl;
        if modifiers.shift {
            self.select_range_to(path, toggle);
        } else if toggle {
            self.toggle_selection(path);
        } else {
            self.set_single_selection(path);
        }
    }

    fn select_range_to(&mut self, path: PathBuf, additive: bool) {
        let entries = self.visible_entries();
        let Some(target_index) = entries.iter().position(|entry| entry.path == path) else {
            self.set_single_selection(path);
            return;
        };

        let anchor_path = self
            .selection_anchor
            .clone()
            .filter(|anchor| entries.iter().any(|entry| entry.path == *anchor))
            .or_else(|| {
                entries
                    .iter()
                    .find(|entry| self.selected.contains(&entry.path))
                    .map(|entry| entry.path.clone())
            })
            .unwrap_or_else(|| path.clone());
        let anchor_index = entries
            .iter()
            .position(|entry| entry.path == anchor_path)
            .unwrap_or(target_index);

        if !additive {
            self.selected.clear();
        }
        let start = anchor_index.min(target_index);
        let end = anchor_index.max(target_index);
        for entry in &entries[start..=end] {
            self.selected.insert(entry.path.clone());
        }
        self.selection_anchor = Some(anchor_path);
        self.selection_focus = Some(path);
        self.status = format!("{} selected", self.selected.len());
    }

    fn step_selection(&mut self, direction: isize, extend: bool) {
        let entries = self.visible_entries();
        if entries.is_empty() {
            return;
        }

        let current_index = self
            .selection_focus
            .as_ref()
            .and_then(|focus| entries.iter().position(|entry| entry.path == *focus))
            .or_else(|| {
                entries
                    .iter()
                    .position(|entry| self.selected.contains(&entry.path))
            });
        let Some(next_index) = stepped_selection_index(entries.len(), current_index, direction)
        else {
            return;
        };
        let path = entries[next_index].path.clone();
        if extend {
            if self.selection_anchor.is_none() {
                self.selection_anchor =
                    Some(entries[current_index.unwrap_or(next_index)].path.clone());
            }
            self.select_range_to(path, false);
        } else {
            self.set_single_selection(path);
        }
    }

    fn jump_selection(&mut self, to_end: bool, extend: bool) {
        let entries = self.visible_entries();
        let target = if to_end {
            entries.last()
        } else {
            entries.first()
        };
        let Some(target) = target else {
            return;
        };
        let path = target.path.clone();
        if extend {
            if self.selection_anchor.is_none() {
                self.selection_anchor = self
                    .selection_focus
                    .clone()
                    .or_else(|| self.selected.iter().next().cloned())
                    .or_else(|| entries.first().map(|entry| entry.path.clone()));
            }
            self.select_range_to(path, false);
        } else {
            self.set_single_selection(path);
        }
    }

    fn select_all_visible(&mut self) {
        let entries = self.visible_entries();
        self.selection_anchor = entries.first().map(|entry| entry.path.clone());
        self.selection_focus = entries.last().map(|entry| entry.path.clone());
        self.selected = entries.into_iter().map(|entry| entry.path).collect();
        self.status = format!("{} selected", self.selected.len());
    }

    fn invert_visible_selection(&mut self) {
        let entries = self.visible_entries();
        self.selected = entries
            .iter()
            .filter(|entry| !self.selected.contains(&entry.path))
            .map(|entry| entry.path.clone())
            .collect();
        self.selection_anchor = entries
            .iter()
            .find(|entry| self.selected.contains(&entry.path))
            .map(|entry| entry.path.clone());
        self.selection_focus = entries
            .iter()
            .rev()
            .find(|entry| self.selected.contains(&entry.path))
            .map(|entry| entry.path.clone());
        self.status = format!("{} selected", self.selected.len());
    }

    fn select_visible_kind(&mut self, directories: bool) {
        let entries = self.visible_entries();
        self.selected = entries
            .iter()
            .filter(|entry| (entry.kind == EntryKind::Directory) == directories)
            .map(|entry| entry.path.clone())
            .collect();
        self.selection_anchor = entries
            .iter()
            .find(|entry| self.selected.contains(&entry.path))
            .map(|entry| entry.path.clone());
        self.selection_focus = entries
            .iter()
            .rev()
            .find(|entry| self.selected.contains(&entry.path))
            .map(|entry| entry.path.clone());
        self.status = if directories {
            format!("{} folders selected", self.selected.len())
        } else {
            format!("{} files selected", self.selected.len())
        };
    }

    fn is_bookmarked(&self, path: &Path) -> bool {
        let normalized = canonical_display_path(path);
        self.bookmarks
            .iter()
            .any(|bookmark| canonical_display_path(bookmark) == normalized)
    }

    fn add_bookmark(&mut self, path: PathBuf) {
        let normalized = canonical_display_path(&path);
        if !normalized.is_dir() {
            self.status = "Only folders can be pinned".to_string();
            return;
        }
        if self.is_bookmarked(&normalized) {
            self.status = "Folder is already pinned".to_string();
            return;
        }
        if self.bookmarks.len() >= BOOKMARK_LIMIT {
            self.status = format!("Pinned folder limit reached ({BOOKMARK_LIMIT})");
            return;
        }
        self.bookmarks.push(normalized.clone());
        self.save_bookmarks();
        self.status = "Folder pinned".to_string();
        self.push_log(format!("PIN // {}", normalized.display()));
    }

    fn remove_bookmark(&mut self, path: &Path) {
        let normalized = canonical_display_path(path);
        let previous_len = self.bookmarks.len();
        self.bookmarks
            .retain(|bookmark| canonical_display_path(bookmark) != normalized);
        if self.bookmarks.len() == previous_len {
            self.status = "Pinned folder not found".to_string();
            return;
        }
        self.save_bookmarks();
        self.status = "Folder unpinned".to_string();
        self.push_log(format!("UNPIN // {}", normalized.display()));
    }

    fn move_bookmark(&mut self, path: &Path, direction: isize) {
        let Some((from, to)) = move_bookmark_path(&mut self.bookmarks, path, direction) else {
            self.status = "Pinned folder cannot move further".to_string();
            return;
        };
        self.save_bookmarks();
        self.status = if to < from {
            "Pinned folder moved up".to_string()
        } else {
            "Pinned folder moved down".to_string()
        };
        self.push_log(format!(
            "PIN ORDER // {} // {} -> {}",
            path.display(),
            from + 1,
            to + 1
        ));
    }

    fn pin_selected_folders(&mut self) {
        let folders = self
            .selected_entries()
            .into_iter()
            .filter(|entry| entry.kind == EntryKind::Directory)
            .map(|entry| entry.path)
            .collect::<Vec<_>>();
        if folders.is_empty() {
            self.status = "Select folders to pin".to_string();
            return;
        }

        let mut pinned = 0;
        for folder in folders {
            let previous_len = self.bookmarks.len();
            self.add_bookmark(folder);
            if self.bookmarks.len() > previous_len {
                pinned += 1;
            }
        }
        if pinned > 0 {
            self.status = format!("Pinned {pinned} folders");
        }
    }

    fn observe_entry_drag_source(&mut self, response: &egui::Response, entry: &FileEntry) {
        if response.dragged() {
            let paths = if self.selected.contains(&entry.path) {
                self.selected_paths()
            } else {
                vec![entry.path.clone()]
            };
            self.pin_drag_candidate = paths.iter().find(|path| path.is_dir()).cloned();
            self.entry_drag_paths = paths;
        }
    }

    fn trash_dragged_entries(&mut self) {
        if self.operation_rx.is_some() {
            self.status = "Operation in progress".to_string();
            return;
        }
        let paths = std::mem::take(&mut self.entry_drag_paths);
        self.pin_drag_candidate = None;
        let Some(paths) = self.check_operation_sources(paths) else {
            return;
        };
        if paths.is_empty() {
            return;
        }
        self.start_operation(OperationRequest::Trash { sources: paths }, false);
    }

    fn register_pinned_drop_rect(&mut self, rect: Rect) {
        self.pinned_drop_rect = Some(if let Some(existing) = self.pinned_drop_rect {
            Rect::from_min_max(
                Pos2::new(
                    existing.min.x.min(rect.min.x),
                    existing.min.y.min(rect.min.y),
                ),
                Pos2::new(
                    existing.max.x.max(rect.max.x),
                    existing.max.y.max(rect.max.y),
                ),
            )
        } else {
            rect
        });
    }

    fn dropped_on_pinned_folders(&self, ctx: &egui::Context) -> bool {
        let Some(rect) = self.pinned_drop_rect else {
            return false;
        };
        ctx.input(|input| {
            input
                .pointer
                .interact_pos()
                .or_else(|| input.pointer.hover_pos())
                .is_some_and(|pos| rect.contains(pos))
        })
    }

    fn pin_dropped_folders(&mut self, paths: Vec<PathBuf>) {
        let mut pinned = 0;
        let mut folders = 0;
        let mut ignored = 0;
        for path in paths {
            if path.is_dir() {
                folders += 1;
                let previous_len = self.bookmarks.len();
                self.add_bookmark(path);
                if self.bookmarks.len() > previous_len {
                    pinned += 1;
                }
            } else {
                ignored += 1;
            }
        }

        if pinned > 0 {
            self.status = if ignored > 0 {
                format!("Pinned {pinned} folders · ignored {ignored} files")
            } else {
                format!("Pinned {pinned} folders")
            };
        } else if folders == 0 {
            self.status = "Drop folders onto Pinned Folders to pin them".to_string();
            if ignored > 0 {
                self.push_log(format!("PIN DROP IGNORED // {ignored} NON-FOLDERS"));
            }
        }
    }

    fn pinned_drop_target(&mut self, ui: &mut egui::Ui, compact: bool) {
        let external_hover = ui.ctx().input(|input| !input.raw.hovered_files.is_empty());
        let path = self.pin_drag_candidate.clone();
        if path.is_none() && !external_hover {
            return;
        }
        if compact {
            let (rect, response) = ui.allocate_exact_size(Vec2::new(34.0, 32.0), Sense::hover());
            self.register_pinned_drop_rect(rect);
            let painter = ui.painter_at(rect);
            let active = response.hovered();
            let row = rect.shrink2(egui::vec2(2.0, 1.0));
            painter.rect_filled(
                row,
                Rounding::same(theme_rounding(self.palette) + 7.0),
                if active {
                    self.palette.selected_fill()
                } else {
                    self.palette.elevated_fill()
                },
            );
            painter.rect_stroke(
                row,
                Rounding::same(theme_rounding(self.palette) + 7.0),
                Stroke::new(1.1, self.palette.accent(0.0)),
            );
            draw_chrome_icon(
                &painter,
                Rect::from_center_size(row.center(), Vec2::splat(15.0)),
                ChromeIcon::Pin,
                self.palette.accent(0.0),
            );
            if active && ui.input(|input| input.pointer.any_released()) {
                if let Some(path) = path {
                    self.add_bookmark(path);
                }
                self.pin_drag_candidate = None;
            }
            return;
        }

        let (rect, response) =
            ui.allocate_exact_size(Vec2::new(ui.available_width(), 42.0), Sense::hover());
        self.register_pinned_drop_rect(rect);
        let painter = ui.painter_at(rect);
        let active = response.hovered();
        let row = rect.shrink2(egui::vec2(2.0, 2.0));
        painter.rect_filled(
            row,
            Rounding::same(theme_rounding(self.palette) + 7.0),
            if active {
                self.palette.selected_fill()
            } else {
                self.palette.elevated_fill()
            },
        );
        painter.rect_stroke(
            row,
            Rounding::same(theme_rounding(self.palette) + 7.0),
            Stroke::new(1.1, self.palette.accent(0.0)),
        );
        let icon_rect =
            Rect::from_center_size(row.left_center() + egui::vec2(18.0, 0.0), Vec2::splat(18.0));
        draw_chrome_icon(
            &painter,
            icon_rect,
            ChromeIcon::Pin,
            self.palette.accent(0.0),
        );
        let title = match &path {
            Some(path) if self.is_bookmarked(path) => "Already pinned",
            Some(_) if active => "Release to pin folder",
            Some(_) => "Drop to pin folder",
            None if active => "Release to pin folders",
            None => "Drop folders to pin",
        };
        painter.text(
            row.left_center() + egui::vec2(38.0, -6.0),
            Align2::LEFT_CENTER,
            title,
            FontId::proportional(12.0),
            self.palette.text(),
        );
        painter.text(
            row.left_center() + egui::vec2(38.0, 9.0),
            Align2::LEFT_CENTER,
            path.as_ref()
                .map(|path| truncate_middle(&bookmark_label(path), 22))
                .unwrap_or_else(|| "External folders".to_string()),
            FontId::proportional(10.0),
            self.palette.muted(),
        );
        if active && ui.input(|input| input.pointer.any_released()) {
            if let Some(path) = path {
                self.add_bookmark(path);
            }
            self.pin_drag_candidate = None;
        }
    }

    fn save_bookmarks(&self) {
        if let Err(error) = write_bookmarks(&self.bookmarks) {
            eprintln!("guth: could not save bookmarks: {error}");
        }
    }

    fn ensure_background_texture(&mut self, ctx: &egui::Context) {
        let trimmed = self.background_image_path.trim();
        if trimmed.is_empty() {
            self.background_texture = None;
            self.background_loaded_path = None;
            self.background_error = None;
            return;
        }

        let path = canonical_display_path(&expand_tilde(trimmed));
        if self.background_loaded_path.as_ref() == Some(&path) {
            return;
        }
        self.background_loaded_path = Some(path.clone());
        match load_background_image(&path) {
            Ok(image) => {
                self.background_texture = Some(ctx.load_texture(
                    format!("background:{}", path.display()),
                    image,
                    egui::TextureOptions::LINEAR,
                ));
                self.background_error = None;
            }
            Err(error) => {
                self.background_texture = None;
                self.background_error = Some(error);
            }
        }
    }

    fn bookmark_places(&self) -> Vec<Place> {
        self.bookmarks
            .iter()
            .take(BOOKMARK_LIMIT)
            .map(|path| Place {
                label: bookmark_label(path),
                path: path.clone(),
                detail: if path.is_dir() {
                    "Pinned".to_string()
                } else {
                    "Missing".to_string()
                },
            })
            .collect()
    }

    fn copy_selection(&mut self, mode: ClipboardMode) {
        let Some(paths) = self.selected_paths_for_operation() else {
            return;
        };
        if paths.is_empty() {
            self.status = "No selection".to_string();
            return;
        }
        let count = paths.len();
        self.clipboard = Some(ClipboardPayload { mode, paths });
        self.status = match mode {
            ClipboardMode::Copy => format!("Copied {count} items"),
            ClipboardMode::Cut => format!("Cut {count} items"),
        };
    }

    fn paste_clipboard(&mut self) {
        let Some(clipboard) = self.clipboard.clone() else {
            self.status = "Clipboard is empty".to_string();
            return;
        };
        if clipboard.paths.is_empty() {
            self.status = "Clipboard is empty".to_string();
            return;
        }
        if target_inside_any_source(&clipboard.paths, &self.current_dir) {
            self.status = "Paste blocked".to_string();
            self.push_log("PASTE BLOCKED // TARGET INSIDE SOURCE".to_string());
            return;
        }
        if clipboard.mode == ClipboardMode::Cut
            && clipboard
                .paths
                .iter()
                .all(|path| path.parent() == Some(self.current_dir.as_path()))
        {
            self.status = "Nothing to move".to_string();
            return;
        }

        let request = match clipboard.mode {
            ClipboardMode::Copy => OperationRequest::Copy {
                sources: clipboard.paths,
                target_dir: self.current_dir.clone(),
            },
            ClipboardMode::Cut => {
                self.clipboard = None;
                OperationRequest::Move {
                    sources: clipboard.paths,
                    target_dir: self.current_dir.clone(),
                }
            }
        };
        self.start_operation(request, false);
    }

    fn duplicate_selection(&mut self) {
        let Some(sources) = self.selected_paths_for_operation() else {
            return;
        };
        if sources.is_empty() {
            self.status = "No selection".to_string();
            return;
        }
        self.start_operation(OperationRequest::Duplicate { sources }, false);
    }

    fn trash_selection(&mut self) {
        let Some(sources) = self.selected_paths_for_operation() else {
            return;
        };
        if sources.is_empty() {
            self.status = "No selection".to_string();
            return;
        }
        self.start_operation(OperationRequest::Trash { sources }, false);
    }

    fn request_delete_selection(&mut self, permanent: bool) {
        let Some(paths) = self.selected_paths_for_operation() else {
            return;
        };
        if paths.is_empty() {
            self.status = "No selection".to_string();
            return;
        }
        self.dialogs.delete_confirm = Some(DeleteIntent { paths, permanent });
    }

    fn start_operation(&mut self, request: OperationRequest, undo: bool) {
        if self.operation_rx.is_some() {
            self.status = "Operation in progress".to_string();
            return;
        }
        self.status = format!("{} started", title_case(request.title()));
        self.operation_title = Some(request.title().to_string());
        self.operation_progress = None;
        self.operation_is_undo = undo;
        self.operation_rx = Some(start_operation(request));
    }

    fn undo_last(&mut self) {
        if self.operation_rx.is_some() {
            self.status = "Operation in progress".to_string();
            return;
        }
        let Some(action) = self.undo_stack.pop() else {
            self.status = "Nothing to undo".to_string();
            return;
        };
        self.push_log(format!("UNDO // {}", action.label()));
        self.start_operation(OperationRequest::Undo(action), true);
    }

    fn drain_operation_events(&mut self) {
        let Some(rx) = self.operation_rx.take() else {
            return;
        };

        let mut keep_rx = true;
        loop {
            let event = match rx.try_recv() {
                Ok(event) => event,
                Err(TryRecvError::Empty) => break,
                Err(TryRecvError::Disconnected) => {
                    if keep_rx {
                        self.handle_operation_disconnect();
                        keep_rx = false;
                    }
                    break;
                }
            };
            match event {
                OperationEvent::Started {
                    title,
                    total_items,
                    total_bytes,
                } => {
                    self.operation_title = Some(title.clone());
                    self.status = format!(
                        "{} · {} items · {}",
                        title_case(&title),
                        total_items,
                        human_size(total_bytes)
                    );
                }
                OperationEvent::Progress(progress) => {
                    self.status = pretty_status(&operation_detail(&progress));
                    self.operation_progress = Some(progress);
                }
                OperationEvent::Finished(result) => {
                    keep_rx = false;
                    self.operation_title = None;
                    self.operation_progress = None;
                    let was_undo = self.operation_is_undo;
                    self.operation_is_undo = false;
                    match result {
                        Ok(outcome) => {
                            self.handle_operation_outcome(outcome, was_undo);
                        }
                        Err(error) => {
                            self.status = if was_undo {
                                "Undo failed".to_string()
                            } else {
                                "Operation failed".to_string()
                            };
                            self.push_log(format!("OPERATION FAILED // {error}"));
                            self.dialogs.error = Some(error);
                        }
                    }
                    let operation_status = self.status.clone();
                    if self.refresh_current_dir() {
                        self.status = operation_status;
                    }
                }
            }
        }

        if keep_rx {
            self.operation_rx = Some(rx);
        }
    }

    fn handle_operation_disconnect(&mut self) {
        let was_undo = self.operation_is_undo;
        self.operation_is_undo = false;
        self.operation_title = None;
        self.operation_progress = None;

        let message = "Operation worker stopped before reporting completion".to_string();
        self.status = if was_undo {
            "Undo interrupted".to_string()
        } else {
            "Operation interrupted".to_string()
        };
        self.push_log(format!("OPERATION INTERRUPTED // {message}"));
        self.dialogs.error = Some(message);
        let operation_status = self.status.clone();
        if self.refresh_current_dir() {
            self.status = operation_status;
        }
    }

    fn handle_operation_outcome(&mut self, outcome: crate::ops::OperationOutcome, was_undo: bool) {
        self.status = pretty_status(&outcome.summary);
        self.push_log(outcome.summary);
        if was_undo {
            return;
        }

        match outcome.action {
            CompletedAction::Copied(paths) => {
                self.focus_operation_results(&paths);
                self.push_undo(UndoAction::RemoveCreated {
                    paths,
                    label: "UNDO COPY".to_string(),
                });
            }
            CompletedAction::Duplicated(paths) => {
                self.focus_operation_results(&paths);
                self.push_undo(UndoAction::RemoveCreated {
                    paths,
                    label: "UNDO DUPLICATE".to_string(),
                });
            }
            CompletedAction::Moved(moves) => {
                let destinations = moves.iter().map(|item| item.to.clone()).collect::<Vec<_>>();
                self.focus_operation_results(&destinations);
                self.push_undo(UndoAction::MoveBack {
                    moves,
                    label: "UNDO MOVE".to_string(),
                });
            }
            CompletedAction::Trashed(records) => {
                self.reset_selection();
                self.push_undo(UndoAction::RestoreTrash {
                    records,
                    label: "UNDO TRASH".to_string(),
                });
            }
            CompletedAction::Deleted(_) => self.reset_selection(),
            CompletedAction::UndoApplied => {}
        }
    }

    fn push_undo(&mut self, action: UndoAction) {
        self.undo_stack.push(action);
        if self.undo_stack.len() > UNDO_LIMIT {
            let overflow = self.undo_stack.len() - UNDO_LIMIT;
            self.undo_stack.drain(0..overflow);
        }
    }

    fn push_log(&mut self, line: String) {
        self.logs.push(line.to_ascii_uppercase());
        if self.logs.len() > LOG_LIMIT {
            let overflow = self.logs.len() - LOG_LIMIT;
            self.logs.drain(0..overflow);
        }
    }

    fn report_error(&mut self, status: &str, context: &str, error: String) {
        self.status = status.to_string();
        self.push_log(format!("{context} FAILED // {error}"));
        self.dialogs.error = Some(error);
    }

    fn save_preferences(&self) {
        if let Err(error) = write_preferences(Preferences {
            view_mode: self.view_mode,
            show_hidden: self.show_hidden,
            recursive_search: self.recursive_search,
            sort_key: self.sort_key,
            sort_reverse: self.sort_reverse,
            folders_first: self.folders_first,
            palette: self.palette,
            motion_enabled: self.motion_enabled,
            hover_effects_enabled: self.hover_effects_enabled,
            theme_opacity: self.theme_opacity,
            background_image_path: self.background_image_path.clone(),
            background_image_opacity: self.background_image_opacity,
            sidebar_collapsed: self.sidebar_collapsed,
            media_preview_plugin_enabled: self.media_preview_plugin_enabled,
            text_preview_plugin_enabled: self.text_preview_plugin_enabled,
        }) {
            eprintln!("guth: could not save preferences: {error}");
        }
    }

    fn handle_shortcuts(&mut self, ctx: &egui::Context) {
        let wants_keyboard = ctx.wants_keyboard_input();
        let shortcuts = ctx.input(|input| {
            let command = input.modifiers.command || input.modifiers.ctrl;
            let unmodified = !input.modifiers.alt
                && !input.modifiers.command
                && !input.modifiers.ctrl
                && !input.modifiers.shift;
            ShortcutState {
                refresh: input.key_pressed(egui::Key::F5)
                    || (command && input.key_pressed(egui::Key::R)),
                back: input.modifiers.alt && input.key_pressed(egui::Key::ArrowLeft),
                forward: input.modifiers.alt && input.key_pressed(egui::Key::ArrowRight),
                parent: input.modifiers.alt && input.key_pressed(egui::Key::ArrowUp),
                open: !input.modifiers.alt
                    && !input.modifiers.shift
                    && input.key_pressed(egui::Key::Enter),
                open_external: !input.modifiers.alt
                    && input.modifiers.shift
                    && input.key_pressed(egui::Key::Enter),
                select_all: command && input.key_pressed(egui::Key::A),
                copy: command && input.key_pressed(egui::Key::C),
                duplicate: command && input.key_pressed(egui::Key::D),
                search: command && input.key_pressed(egui::Key::F),
                cut: command && input.key_pressed(egui::Key::X),
                paste: command && input.key_pressed(egui::Key::V),
                undo: command && input.key_pressed(egui::Key::Z),
                new_folder: command && input.key_pressed(egui::Key::N),
                hidden: command && input.key_pressed(egui::Key::H),
                location: command && input.key_pressed(egui::Key::L),
                cancel_location: input.key_pressed(egui::Key::Escape),
                settings: command && input.key_pressed(egui::Key::Comma),
                properties: (command && input.key_pressed(egui::Key::I))
                    || (input.modifiers.alt && input.key_pressed(egui::Key::Enter)),
                media_preview: unmodified && input.key_pressed(egui::Key::Space),
                media_preview_next: unmodified && input.key_pressed(egui::Key::ArrowRight),
                media_preview_previous: unmodified && input.key_pressed(egui::Key::ArrowLeft),
                media_preview_zoom_in: unmodified && input.key_pressed(egui::Key::ArrowUp),
                media_preview_zoom_out: unmodified && input.key_pressed(egui::Key::ArrowDown),
                selection_previous: !input.modifiers.alt && input.key_pressed(egui::Key::ArrowUp),
                selection_next: !input.modifiers.alt && input.key_pressed(egui::Key::ArrowDown),
                selection_first: !input.modifiers.alt && input.key_pressed(egui::Key::Home),
                selection_last: !input.modifiers.alt && input.key_pressed(egui::Key::End),
                selection_extend: input.modifiers.shift,
                rename: input.key_pressed(egui::Key::F2),
                delete: input.key_pressed(egui::Key::Delete),
                permanent_delete: input.modifiers.shift && input.key_pressed(egui::Key::Delete),
            }
        });

        if shortcuts.refresh {
            self.refresh_current_dir();
        }
        if shortcuts.back {
            self.go_back();
        }
        if shortcuts.forward {
            self.go_forward();
        }
        if shortcuts.parent
            || (!wants_keyboard && ctx.input(|input| input.key_pressed(egui::Key::Backspace)))
        {
            self.go_parent();
        }
        if shortcuts.location {
            self.path_input = self.current_dir.display().to_string();
            self.location_entry_open = true;
        }
        if shortcuts.search {
            if self.media_preview.open
                && self.media_preview.active_plugin == Some(PreviewPluginKind::Text)
            {
                self.location_entry_open = false;
                self.media_preview.text_find_focus_requested = true;
            } else {
                self.location_entry_open = false;
                self.search_focus_requested = true;
            }
        }
        if self.media_preview.open
            && self.media_preview.active_plugin == Some(PreviewPluginKind::Text)
            && shortcuts.cancel_location
            && !self.media_preview.text_find_query.is_empty()
            && !self.location_entry_open
        {
            self.media_preview.text_find_query.clear();
            self.media_preview.text_find_loaded_key = None;
            self.media_preview.text_find_focus_requested = false;
            return;
        }
        if self.location_entry_open && shortcuts.cancel_location {
            self.path_input = self.current_dir.display().to_string();
            self.location_entry_open = false;
            return;
        }
        if self.media_preview.open && shortcuts.cancel_location {
            self.close_media_preview_plugin();
            return;
        }
        if wants_keyboard {
            return;
        }
        if self.media_preview.open && shortcuts.media_preview_previous {
            self.step_media_preview_plugin(-1);
            return;
        }
        if self.media_preview.open && shortcuts.media_preview_next {
            self.step_media_preview_plugin(1);
            return;
        }
        if self.media_preview.open && shortcuts.media_preview_zoom_in {
            self.adjust_media_preview_zoom(0.10);
            return;
        }
        if self.media_preview.open && shortcuts.media_preview_zoom_out {
            self.adjust_media_preview_zoom(-0.10);
            return;
        }
        if shortcuts.cancel_location && !self.selected.is_empty() {
            self.clear_selection();
            return;
        }
        if shortcuts.open {
            self.open_selection();
        }
        if shortcuts.open_external {
            self.open_selection_externally();
        }
        if shortcuts.media_preview {
            self.toggle_media_preview_plugin();
        }
        if shortcuts.selection_previous {
            self.step_selection(-1, shortcuts.selection_extend);
            return;
        }
        if shortcuts.selection_next {
            self.step_selection(1, shortcuts.selection_extend);
            return;
        }
        if shortcuts.selection_first {
            self.jump_selection(false, shortcuts.selection_extend);
            return;
        }
        if shortcuts.selection_last {
            self.jump_selection(true, shortcuts.selection_extend);
            return;
        }
        if shortcuts.select_all {
            self.select_all_visible();
        }
        if shortcuts.copy {
            self.copy_selection(ClipboardMode::Copy);
        }
        if shortcuts.duplicate {
            self.duplicate_selection();
        }
        if shortcuts.cut {
            self.copy_selection(ClipboardMode::Cut);
        }
        if shortcuts.paste {
            self.paste_clipboard();
        }
        if shortcuts.undo {
            self.undo_last();
        }
        if shortcuts.new_folder {
            self.open_new_folder_dialog();
        }
        if shortcuts.hidden {
            self.show_hidden = !self.show_hidden;
            self.save_preferences();
            self.refresh_current_dir();
        }
        if shortcuts.properties {
            self.show_properties = !self.show_properties;
        }
        if shortcuts.settings {
            self.show_settings = !self.show_settings;
        }
        if shortcuts.rename {
            self.open_rename_dialog();
        }
        if shortcuts.permanent_delete {
            self.request_delete_selection(true);
        } else if shortcuts.delete {
            self.trash_selection();
        }
    }

    fn handle_dropped_files(&mut self, ctx: &egui::Context) {
        let dropped: Vec<PathBuf> = ctx.input(|input| {
            input
                .raw
                .dropped_files
                .iter()
                .filter_map(|file| file.path.clone())
                .take(OPERATION_SOURCE_LIMIT + 1)
                .collect()
        });
        if dropped.is_empty() || self.operation_rx.is_some() {
            return;
        }
        let Some(dropped) = self.check_operation_sources(dropped) else {
            return;
        };
        if self.dropped_on_pinned_folders(ctx) {
            self.pin_dropped_folders(dropped);
            return;
        }
        if target_inside_any_source(&dropped, &self.current_dir) {
            self.push_log("DROP BLOCKED // TARGET INSIDE SOURCE".to_string());
            return;
        }
        self.start_operation(
            OperationRequest::Copy {
                sources: dropped,
                target_dir: self.current_dir.clone(),
            },
            false,
        );
    }

    fn drag_feedback_overlay(&self, ctx: &egui::Context) {
        let external_count = ctx.input(|input| input.raw.hovered_files.len());
        let internal_count = self.entry_drag_paths.len();
        if external_count == 0 && internal_count == 0 {
            return;
        }

        let Some(pointer) = ctx.input(|input| input.pointer.hover_pos()) else {
            return;
        };

        let over_pins = self.dropped_on_pinned_folders(ctx);
        let can_pin = self.pin_drag_candidate.is_some() || external_count > 0;
        let count = internal_count.max(external_count);
        let count_label = if count == 1 {
            "1 item".to_string()
        } else {
            format!("{count} items")
        };
        let (icon, title, detail) = if over_pins && can_pin {
            (
                ChromeIcon::Pin,
                "Release to Pin",
                if external_count > 0 {
                    "Folders will be pinned"
                } else {
                    "Folder will be pinned"
                },
            )
        } else if external_count > 0 {
            (
                ChromeIcon::Copy,
                "Drop to Copy",
                "Drop on Pinned Folders to pin folders",
            )
        } else {
            (
                ChromeIcon::List,
                "Dragging Selection",
                "Drop on Trash to move to trash",
            )
        };

        egui::Area::new(egui::Id::new("drag-feedback-overlay"))
            .order(egui::Order::Tooltip)
            .fixed_pos(pointer + egui::vec2(18.0, 18.0))
            .interactable(false)
            .show(ctx, |ui| {
                egui::Frame::none()
                    .fill(themed_alpha(
                        self.palette.elevated_fill(),
                        self.theme_opacity,
                    ))
                    .stroke(Stroke::new(1.0, self.palette.accent(0.0)))
                    .rounding(Rounding::same(theme_rounding(self.palette) + 10.0))
                    .inner_margin(egui::Margin::symmetric(10.0, 8.0))
                    .show(ui, |ui| {
                        ui.horizontal(|ui| {
                            inline_chrome_icon(ui, self.palette, icon, self.palette.accent(0.0));
                            ui.vertical(|ui| {
                                ui.label(
                                    RichText::new(format!("{title} · {count_label}"))
                                        .size(12.0)
                                        .strong()
                                        .color(self.palette.text()),
                                );
                                ui.label(
                                    RichText::new(detail).size(10.0).color(self.palette.muted()),
                                );
                            });
                        });
                    });
            });
    }

    fn open_new_folder_dialog(&mut self) {
        self.dialogs.new_folder_open = true;
        self.dialogs.new_folder_name = "New Folder".to_string();
        self.dialogs.new_folder_focus_requested = true;
    }

    fn open_new_file_dialog(&mut self) {
        self.dialogs.new_file_open = true;
        self.dialogs.new_file_name = "New File.txt".to_string();
        self.dialogs.new_file_focus_requested = true;
    }

    fn open_rename_dialog(&mut self) {
        let selected = self.selected_entries();
        if selected.len() != 1 {
            self.status = "Select one item to rename".to_string();
            return;
        }
        let entry = selected[0].clone();
        self.dialogs.rename_open = true;
        self.dialogs.rename_path = Some(entry.path);
        self.dialogs.rename_name = entry.name;
        self.dialogs.rename_focus_requested = true;
    }
}

#[derive(Default)]
struct ShortcutState {
    refresh: bool,
    back: bool,
    forward: bool,
    parent: bool,
    open: bool,
    open_external: bool,
    select_all: bool,
    copy: bool,
    duplicate: bool,
    search: bool,
    cut: bool,
    paste: bool,
    undo: bool,
    new_folder: bool,
    hidden: bool,
    location: bool,
    cancel_location: bool,
    settings: bool,
    properties: bool,
    media_preview: bool,
    media_preview_next: bool,
    media_preview_previous: bool,
    media_preview_zoom_in: bool,
    media_preview_zoom_out: bool,
    selection_previous: bool,
    selection_next: bool,
    selection_first: bool,
    selection_last: bool,
    selection_extend: bool,
    rename: bool,
    delete: bool,
    permanent_delete: bool,
}

impl eframe::App for GuthApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        ctx.set_visuals(self.palette.visuals());
        if !self.dialogs.is_open() {
            self.handle_shortcuts(ctx);
        }
        if !self.dialogs.is_open() {
            self.handle_dropped_files(ctx);
        }
        self.drain_operation_events();
        self.thumbnails.poll(ctx);
        self.poll_media_preview_image(ctx);
        self.poll_media_preview_text(ctx);
        self.poll_deep_search(ctx);
        self.start_deep_search_if_ready();
        self.ensure_background_texture(ctx);

        let raw_phase = ctx.input(|input| input.time) as f32;
        let phase = if self.motion_enabled { raw_phase } else { 0.0 };
        let now = raw_phase as f64;
        self.hover_animation_active = false;

        egui::TopBottomPanel::top("headerbar")
            .frame(panel_frame(
                self.palette,
                self.palette.header_fill(),
                egui::Margin::symmetric(8.0, 6.0),
                self.theme_opacity,
            ))
            .exact_height(54.0)
            .show(ctx, |ui| self.header(ui));

        egui::TopBottomPanel::bottom("statusbar")
            .frame(panel_frame(
                self.palette,
                self.palette.header_fill(),
                egui::Margin::symmetric(8.0, 4.0),
                self.theme_opacity,
            ))
            .exact_height(30.0)
            .show(ctx, |ui| self.activity_panel(ui, phase));

        let sidebar_width = if self.sidebar_collapsed { 58.0 } else { 196.0 };
        let sidebar_range = if self.sidebar_collapsed {
            54.0..=70.0
        } else {
            168.0..=260.0
        };

        egui::SidePanel::left("places-sidebar")
            .frame(panel_frame(
                self.palette,
                self.palette.sidebar_fill(),
                egui::Margin::symmetric(8.0, 6.0),
                self.theme_opacity,
            ))
            .default_width(sidebar_width)
            .width_range(sidebar_range)
            .show(ctx, |ui| self.places_panel(ui));

        egui::CentralPanel::default()
            .frame(egui::Frame::none().fill(themed_alpha(
                self.palette.content_fill(),
                self.theme_opacity,
            )))
            .show(ctx, |ui| {
                paint_background(
                    ui,
                    phase,
                    themed_alpha(self.palette.content_fill(), self.theme_opacity),
                    self.palette.row_alt_fill(),
                    self.palette.accent_soft(),
                    self.background_texture.as_ref(),
                    self.background_image_opacity,
                );
                self.toolbar(ui);
                ui.add_space(8.0);
                self.file_panel(ui, phase, now);
            });

        self.properties_overlay(ctx, phase);
        self.settings_overlay(ctx, phase);
        self.media_preview_overlay(ctx, phase);
        self.dialogs(ctx, phase);
        self.drag_feedback_overlay(ctx);
        self.hover.clear_if_stale(now);
        if (self.pin_drag_candidate.is_some() || !self.entry_drag_paths.is_empty())
            && ctx.input(|input| input.pointer.any_released())
        {
            self.pin_drag_candidate = None;
            self.entry_drag_paths.clear();
        }

        let repaint_ms = if self.operation_rx.is_some()
            || (self.motion_enabled && self.hover_animation_active)
            || self.pin_drag_candidate.is_some()
            || !self.entry_drag_paths.is_empty()
        {
            16
        } else if self.thumbnails.has_work()
            || self.media_preview.image_rx.is_some()
            || self.media_preview.text_rx.is_some()
            || self.deep_search_active()
        {
            33
        } else {
            120
        };
        ctx.request_repaint_after(Duration::from_millis(repaint_ms));
    }
}

impl GuthApp {
    fn properties_overlay(&mut self, ctx: &egui::Context, phase: f32) {
        if !self.show_properties {
            return;
        }

        let mut open = self.show_properties;
        egui::Window::new("Properties")
            .id(egui::Id::new("properties-overlay"))
            .open(&mut open)
            .collapsible(false)
            .resizable(true)
            .default_width(340.0)
            .min_width(300.0)
            .max_width(460.0)
            .anchor(Align2::RIGHT_TOP, egui::vec2(-24.0, 86.0))
            .frame(window_frame(self.palette, self.theme_opacity))
            .vscroll(true)
            .show(ctx, |ui| self.properties_panel(ui, phase));
        self.show_properties = open;
    }

    fn settings_overlay(&mut self, ctx: &egui::Context, phase: f32) {
        if !self.show_settings {
            return;
        }

        let mut open = self.show_settings;
        egui::Window::new("View Options")
            .id(egui::Id::new("view-options-overlay"))
            .open(&mut open)
            .collapsible(false)
            .resizable(true)
            .default_width(430.0)
            .min_width(340.0)
            .max_width(560.0)
            .anchor(Align2::CENTER_TOP, egui::vec2(0.0, 86.0))
            .frame(window_frame(self.palette, self.theme_opacity))
            .vscroll(true)
            .show(ctx, |ui| self.rendering_panel(ui, phase));
        self.show_settings = open;
    }

    fn media_preview_overlay(&mut self, ctx: &egui::Context, phase: f32) {
        if !self.media_preview.open {
            return;
        }

        let Some(plugin_kind) = self.media_preview.active_plugin else {
            self.close_media_preview_plugin();
            return;
        };
        if !self.preview_plugin_enabled(plugin_kind) {
            self.close_media_preview_plugin();
            return;
        }

        let plugin = self.preview_plugin_descriptor(plugin_kind);

        let mut open = self.media_preview.open;
        egui::Window::new(plugin.name)
            .id(egui::Id::new(plugin.id))
            .open(&mut open)
            .collapsible(false)
            .resizable(true)
            .default_size(Vec2::new(780.0, 560.0))
            .min_width(420.0)
            .min_height(360.0)
            .anchor(Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
            .frame(window_frame(self.palette, self.theme_opacity))
            .show(ctx, |ui| self.media_preview_panel(ui, phase));
        self.media_preview.open = open;
        if !open {
            self.close_media_preview_plugin();
        }
    }

    fn media_preview_panel(&mut self, ui: &mut egui::Ui, phase: f32) {
        let Some(entry) = self.media_preview.entry.clone() else {
            muted_label(ui, "Select an image, video, text, or code file to preview.");
            return;
        };
        let Some(plugin_kind) = self.media_preview.active_plugin else {
            muted_label(ui, "No preview plugin is active.");
            return;
        };
        let plugin = self.preview_plugin_descriptor(plugin_kind);
        let media_class = media_class_for_entry(&entry);
        let preview_entries = self.previewable_entries();
        let preview_position = preview_entries
            .iter()
            .position(|candidate| candidate.path == entry.path)
            .map(|index| (index + 1, preview_entries.len()));
        let can_step = preview_entries.len() > 1;
        let header_detail = if let Some((index, total)) = preview_position {
            format!(
                "{} overlay · {} · {} of {}",
                KNOTWORK_NAME,
                entry.type_label(),
                index,
                total
            )
        } else {
            format!("{} overlay · {}", KNOTWORK_NAME, entry.type_label())
        };
        overlay_header(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Preview,
            plugin.name,
            &header_detail,
        );
        if self.media_preview_control_bar(ui, &entry, can_step, preview_position) {
            self.close_media_preview_plugin();
            return;
        }
        ui.add_space(8.0);

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Preview,
            "Inline Viewer",
            |ui| match plugin_kind {
                PreviewPluginKind::Media => match media_class {
                    MediaClass::Image => self.media_preview_image(ui, &entry),
                    MediaClass::Video => self.media_preview_video(ui, &entry, phase),
                    _ => muted_label(ui, "Knotlook Media previews images and videos."),
                },
                PreviewPluginKind::Text => match media_class {
                    MediaClass::Code | MediaClass::Text => self.media_preview_text(ui, &entry),
                    _ => muted_label(ui, "Knotread Text previews text and code files."),
                },
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Settings,
            "Plugin",
            |ui| {
                property_line(ui, "System", KNOTWORK_NAME);
                property_line(ui, "Plugin", plugin.name);
                property_line(ui, "Shortcut", plugin.shortcut);
                property_line(ui, "File", &path_for_display(&entry.path));
                if let Some((index, total)) = preview_position {
                    property_line(ui, "Position", &format!("{} of {}", index, total));
                }
                if plugin_kind == PreviewPluginKind::Text {
                    if let Some(document) = &self.media_preview.text_document {
                        property_line(ui, "Preview", &format!("{} lines", document.line_count));
                        property_line(ui, "Bytes", &human_size(document.bytes_read as u64));
                        property_line(
                            ui,
                            "Encoding",
                            if document.lossy {
                                "UTF-8 lossy"
                            } else {
                                "UTF-8"
                            },
                        );
                        if document.truncated {
                            property_line(ui, "Limit", "truncated");
                        }
                    }
                }
                property_line(
                    ui,
                    "Zoom",
                    &format!("{}%", preview_zoom_percent(self.media_preview.zoom)),
                );
                property_line(ui, "Keys", "Space/Esc close · arrows navigate/zoom");
            },
        );
    }

    fn media_preview_control_bar(
        &mut self,
        ui: &mut egui::Ui,
        entry: &FileEntry,
        can_step: bool,
        preview_position: Option<(usize, usize)>,
    ) -> bool {
        let mut close_clicked = false;
        egui::Frame::none()
            .fill(themed_alpha(
                self.palette.elevated_fill(),
                self.theme_opacity,
            ))
            .stroke(Stroke::new(1.0, self.palette.hairline()))
            .rounding(Rounding::same(theme_rounding(self.palette) + 9.0))
            .inner_margin(egui::Margin::symmetric(9.0, 6.0))
            .show(ui, |ui| {
                ui.horizontal_wrapped(|ui| {
                    if let Some((index, total)) = preview_position {
                        info_chip(ui, self.palette, &format!("{} of {}", index, total), true);
                    }
                    info_chip(ui, self.palette, entry.type_label(), false);
                    info_chip(
                        ui,
                        self.palette,
                        &format!("{}%", preview_zoom_percent(self.media_preview.zoom)),
                        false,
                    );
                    command_separator(ui, self.palette);
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Back,
                        Some("Prev"),
                        false,
                        can_step,
                    )
                    .clicked()
                    {
                        self.step_media_preview_plugin(-1);
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Forward,
                        Some("Next"),
                        false,
                        can_step,
                    )
                    .clicked()
                    {
                        self.step_media_preview_plugin(1);
                    }
                    let slider_width = (ui.available_width() * 0.22).clamp(110.0, 190.0);
                    if ui
                        .add_sized(
                            Vec2::new(slider_width, 22.0),
                            egui::Slider::new(&mut self.media_preview.zoom, 0.5..=2.5)
                                .show_value(false),
                        )
                        .changed()
                    {
                        self.media_preview.zoom = self.media_preview.zoom.clamp(0.5, 2.5);
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Search,
                        Some("Reset"),
                        false,
                        (self.media_preview.zoom - 1.0).abs() > f32::EPSILON,
                    )
                    .clicked()
                    {
                        self.media_preview.zoom = 1.0;
                    }
                    command_separator(ui, self.palette);
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Open,
                        Some("External"),
                        false,
                        true,
                    )
                    .clicked()
                    {
                        self.open_path_external(&entry.path);
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Properties,
                        Some("Info"),
                        self.show_properties,
                        true,
                    )
                    .clicked()
                    {
                        self.show_properties = !self.show_properties;
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Back,
                        Some("Close"),
                        false,
                        true,
                    )
                    .clicked()
                    {
                        close_clicked = true;
                    }
                });
            });
        close_clicked
    }

    fn media_preview_image(&mut self, ui: &mut egui::Ui, entry: &FileEntry) {
        self.request_media_preview_image(entry);
        let canvas_height = (ui.available_width() * 0.62).clamp(260.0, 620.0);
        let (rect, _) = ui.allocate_exact_size(
            Vec2::new(ui.available_width(), canvas_height),
            Sense::hover(),
        );
        let painter = ui.painter_at(rect);
        let canvas = draw_preview_canvas(&painter, rect, self.palette);

        if let Some(texture) = self.media_preview.image_texture.clone() {
            let [width, height] = texture.size();
            let fitted_rect =
                fit_rect_to_aspect(canvas.shrink(12.0), width as f32 / height.max(1) as f32);
            let image_rect = zoom_rect(fitted_rect, self.media_preview.zoom);
            icon_shadow(&painter, image_rect, 8.0, 34);
            painter.image(
                texture.id(),
                image_rect,
                Rect::from_min_max(Pos2::ZERO, Pos2::new(1.0, 1.0)),
                Color32::WHITE,
            );
            painter.rect_stroke(
                image_rect,
                Rounding::same(5.0),
                Stroke::new(1.0, self.palette.hairline()),
            );
        } else {
            let icon_rect =
                Rect::from_center_size(canvas.center() + egui::vec2(0.0, -18.0), Vec2::splat(74.0));
            draw_file_type_icon(
                &painter,
                icon_rect,
                MediaClass::Image,
                false,
                false,
                self.palette,
            );
            painter.text(
                canvas.center() + egui::vec2(0.0, 44.0),
                Align2::CENTER_CENTER,
                self.media_preview
                    .error
                    .as_deref()
                    .unwrap_or("Loading image preview"),
                FontId::proportional(12.0),
                self.palette.muted(),
            );
        }
    }

    fn media_preview_video(&mut self, ui: &mut egui::Ui, entry: &FileEntry, phase: f32) {
        let modified_ms = thumbnail_modified_ms(entry.modified);
        self.thumbnails
            .request_poster(&entry.path, entry.size, modified_ms);
        if self.hover_effects_enabled {
            self.thumbnails
                .request_hover_strip(&entry.path, entry.size, modified_ms);
            self.hover_animation_active = true;
        }
        let view = self.thumbnails.thumbnail_for(
            &entry.path,
            entry.size,
            modified_ms,
            self.hover_effects_enabled,
            phase,
        );
        let canvas_height = (ui.available_width() * 0.56).clamp(250.0, 580.0);
        let (rect, _) = ui.allocate_exact_size(
            Vec2::new(ui.available_width(), canvas_height),
            Sense::hover(),
        );
        let painter = ui.painter_at(rect);
        let canvas = draw_preview_canvas(&painter, rect, self.palette);

        if let Some(view) = view {
            let fitted_rect = fit_rect_to_aspect(canvas.shrink(12.0), view.aspect);
            let image_rect = zoom_rect(fitted_rect, self.media_preview.zoom);
            icon_shadow(&painter, image_rect, 8.0, 36);
            painter.image(
                view.texture_id,
                image_rect,
                Rect::from_min_max(Pos2::ZERO, Pos2::new(1.0, 1.0)),
                Color32::WHITE,
            );
            painter.rect_stroke(
                image_rect,
                Rounding::same(5.0),
                Stroke::new(1.0, self.palette.hairline()),
            );
            draw_video_badge(&painter, image_rect, view.animated);
            painter.text(
                canvas.left_bottom() + egui::vec2(14.0, -14.0),
                Align2::LEFT_CENTER,
                if view.animated {
                    "Video hover strip"
                } else {
                    "Video poster"
                },
                FontId::proportional(11.0),
                self.palette.muted(),
            );
        } else {
            let icon_rect =
                Rect::from_center_size(canvas.center() + egui::vec2(0.0, -18.0), Vec2::splat(74.0));
            draw_file_type_icon(
                &painter,
                icon_rect,
                MediaClass::Video,
                false,
                self.thumbnails.is_loading(&entry.path),
                self.palette,
            );
            painter.text(
                canvas.center() + egui::vec2(0.0, 44.0),
                Align2::CENTER_CENTER,
                "Generating video preview with ffmpeg",
                FontId::proportional(12.0),
                self.palette.muted(),
            );
        }
    }

    fn media_preview_text(&mut self, ui: &mut egui::Ui, entry: &FileEntry) {
        self.request_media_preview_text(entry);
        let media_class = media_class_for_entry(entry);
        self.ensure_text_preview_buffer();
        self.update_text_find_summary();
        let canvas_height = (ui.available_width() * 0.58).clamp(260.0, 620.0);
        egui::Frame::none()
            .fill(self.palette.recessed_fill())
            .stroke(Stroke::new(1.0, self.palette.hairline()))
            .rounding(Rounding::same(theme_rounding(self.palette) + 8.0))
            .inner_margin(egui::Margin::same(10.0))
            .show(ui, |ui| {
                ui.set_min_height(canvas_height);
                if let Some((line_count, bytes_read, lossy, truncated)) =
                    self.media_preview.text_document.as_ref().map(|document| {
                        (
                            document.line_count,
                            document.bytes_read,
                            document.lossy,
                            document.truncated,
                        )
                    })
                {
                    ui.horizontal_wrapped(|ui| {
                        info_chip(ui, self.palette, &format!("{} lines", line_count), false);
                        info_chip(ui, self.palette, &human_size(bytes_read as u64), false);
                        if lossy {
                            info_chip(ui, self.palette, "lossy utf-8", true);
                        }
                        if truncated {
                            info_chip(ui, self.palette, "truncated", true);
                        }
                        if icon_button(
                            ui,
                            self.palette,
                            ChromeIcon::List,
                            Some("Line numbers"),
                            self.media_preview.text_line_numbers,
                            true,
                        )
                        .clicked()
                        {
                            self.media_preview.text_line_numbers =
                                !self.media_preview.text_line_numbers;
                            self.media_preview.text_view_buffer_key = None;
                            self.ensure_text_preview_buffer();
                        }
                    });
                    ui.add_space(6.0);
                    ui.horizontal_wrapped(|ui| {
                        field_label(ui, "Find");
                        let response = ui.add(
                            egui::TextEdit::singleline(&mut self.media_preview.text_find_query)
                                .hint_text("Find in preview")
                                .desired_width(220.0),
                        );
                        if self.media_preview.text_find_focus_requested {
                            response.request_focus();
                            self.media_preview.text_find_focus_requested = false;
                        }
                        if response.changed() {
                            self.media_preview.text_find_loaded_key = None;
                            self.update_text_find_summary();
                        }
                        if self.media_preview.text_find_query.trim().is_empty() {
                            muted_label(ui, "Case-insensitive search");
                        } else {
                            info_chip(
                                ui,
                                self.palette,
                                &format!("{} matches", self.media_preview.text_find_match_count),
                                self.media_preview.text_find_match_count > 0,
                            );
                        }
                    });
                    ui.add_space(8.0);
                    let text_height = (canvas_height - 82.0).max(180.0);
                    let font_size = (13.0 * self.media_preview.zoom).clamp(10.0, 22.0);
                    ui.scope(|ui| {
                        ui.style_mut()
                            .text_styles
                            .insert(egui::TextStyle::Monospace, FontId::monospace(font_size));
                        ui.add_sized(
                            Vec2::new(ui.available_width(), text_height),
                            egui::TextEdit::multiline(&mut self.media_preview.text_view_buffer)
                                .font(egui::TextStyle::Monospace)
                                .code_editor()
                                .desired_width(f32::INFINITY)
                                .interactive(false),
                        );
                    });
                } else {
                    ui.vertical_centered(|ui| {
                        ui.add_space((canvas_height * 0.32).max(40.0));
                        let (icon_rect, _) =
                            ui.allocate_exact_size(Vec2::splat(74.0), Sense::hover());
                        let painter = ui.painter_at(icon_rect);
                        draw_file_type_icon(
                            &painter,
                            icon_rect,
                            media_class,
                            false,
                            self.media_preview.text_rx.is_some(),
                            self.palette,
                        );
                        ui.add_space(12.0);
                        muted_label(
                            ui,
                            self.media_preview
                                .error
                                .as_deref()
                                .unwrap_or("Loading text preview"),
                        );
                    });
                }
            });
    }

    fn ensure_text_preview_buffer(&mut self) {
        let key = self.media_preview.text_loaded_key.clone();
        let line_numbers = self.media_preview.text_line_numbers;
        if self.media_preview.text_view_buffer_key == key
            && self.media_preview.text_view_buffer_line_numbers == line_numbers
        {
            return;
        }

        self.media_preview.text_view_buffer = self
            .media_preview
            .text_document
            .as_ref()
            .map(|document| {
                if line_numbers {
                    document.numbered_content.clone()
                } else {
                    document.content.clone()
                }
            })
            .unwrap_or_default();
        self.media_preview.text_view_buffer_key = key;
        self.media_preview.text_view_buffer_line_numbers = line_numbers;
    }

    fn update_text_find_summary(&mut self) {
        let key = self.media_preview.text_loaded_key.clone();
        if self.media_preview.text_find_loaded_key == key
            && self.media_preview.text_find_last_query == self.media_preview.text_find_query
        {
            return;
        }

        self.media_preview.text_find_match_count = self
            .media_preview
            .text_document
            .as_ref()
            .map(|document| {
                count_text_matches(&document.content, &self.media_preview.text_find_query)
            })
            .unwrap_or_default();
        self.media_preview.text_find_last_query = self.media_preview.text_find_query.clone();
        self.media_preview.text_find_loaded_key = key;
    }

    fn poll_media_preview_image(&mut self, ctx: &egui::Context) {
        let Some(result) =
            self.media_preview
                .image_rx
                .as_ref()
                .and_then(|rx| match rx.try_recv() {
                    Ok(result) => Some(Ok(result)),
                    Err(TryRecvError::Empty) => None,
                    Err(TryRecvError::Disconnected) => {
                        Some(Err("Preview loader stopped".to_string()))
                    }
                })
        else {
            return;
        };

        self.media_preview.image_rx = None;
        let worker_key = self.media_preview.image_worker_key.take();
        let current_key = self
            .media_preview
            .entry
            .as_ref()
            .map(MediaPreviewKey::from_entry);

        match result {
            Ok(result) => {
                if current_key.as_ref() == Some(&result.key) {
                    match result.image {
                        Ok(image) => {
                            let texture = ctx.load_texture(
                                format!(
                                    "media-preview:{}:{}",
                                    result.key.path.display(),
                                    result.key.modified_ms
                                ),
                                image,
                                egui::TextureOptions::LINEAR,
                            );
                            self.cache_media_preview_texture(result.key.clone(), texture.clone());
                            self.media_preview.image_texture = Some(texture);
                            self.media_preview.loaded_key = Some(result.key);
                            self.media_preview.error = None;
                        }
                        Err(error) => {
                            self.media_preview.loaded_key = Some(result.key);
                            self.media_preview.image_texture = None;
                            self.media_preview.error = Some(error);
                        }
                    }
                    ctx.request_repaint();
                }
            }
            Err(error) if current_key.as_ref() == worker_key.as_ref() => {
                self.media_preview.error = Some(error);
            }
            Err(_) => {}
        }

        if let Some(pending) = self.media_preview.image_pending.take() {
            if current_key.as_ref() == Some(&pending.key) {
                self.start_media_preview_image(pending);
                return;
            }
        }
        self.media_preview.loading_key = None;
    }

    fn request_media_preview_image(&mut self, entry: &FileEntry) {
        let key = MediaPreviewKey::from_entry(entry);
        if self.media_preview.loaded_key.as_ref() == Some(&key)
            && (self.media_preview.image_texture.is_some() || self.media_preview.error.is_some())
        {
            return;
        }
        if self.media_preview.loading_key.as_ref() == Some(&key) {
            return;
        }

        if let Some(texture) = self.cached_media_preview_texture(&key) {
            self.media_preview.image_texture = Some(texture);
            self.media_preview.loaded_key = Some(key);
            self.media_preview.loading_key = None;
            self.media_preview.image_pending = None;
            self.media_preview.error = None;
            return;
        }

        self.media_preview.image_texture = None;
        self.media_preview.loaded_key = None;
        self.media_preview.loading_key = Some(key.clone());
        self.media_preview.error = None;

        let pending = MediaPreviewPending {
            key,
            path: entry.path.clone(),
        };
        if self.media_preview.image_rx.is_some() {
            self.media_preview.image_pending = Some(pending);
        } else {
            self.start_media_preview_image(pending);
        }
    }

    fn start_media_preview_image(&mut self, pending: MediaPreviewPending) {
        let key = pending.key;
        let path = pending.path;
        let (tx, rx) = mpsc::sync_channel(1);
        self.media_preview.image_worker_key = Some(key.clone());
        self.media_preview.image_rx = Some(rx);
        if let Err(error) = thread::Builder::new()
            .name("guth-media-preview".to_string())
            .spawn(move || {
                let image = load_media_preview_image(&path);
                let _ = tx.send(MediaPreviewImageResult { key, image });
            })
        {
            self.media_preview.loading_key = None;
            self.media_preview.image_worker_key = None;
            self.media_preview.image_rx = None;
            self.media_preview.error = Some(format!("Preview worker failed: {error}"));
        }
    }

    fn poll_media_preview_text(&mut self, ctx: &egui::Context) {
        let Some(result) = self
            .media_preview
            .text_rx
            .as_ref()
            .and_then(|rx| match rx.try_recv() {
                Ok(result) => Some(Ok(result)),
                Err(TryRecvError::Empty) => None,
                Err(TryRecvError::Disconnected) => {
                    Some(Err("Text preview loader stopped".to_string()))
                }
            })
        else {
            return;
        };

        self.media_preview.text_rx = None;
        let worker_key = self.media_preview.text_worker_key.take();
        let current_key = self
            .media_preview
            .entry
            .as_ref()
            .map(MediaPreviewKey::from_entry);

        match result {
            Ok(result) => {
                if current_key.as_ref() == Some(&result.key) {
                    match result.document {
                        Ok(document) => {
                            self.media_preview.text_document = Some(document);
                            self.media_preview.text_loaded_key = Some(result.key);
                            self.media_preview.text_view_buffer_key = None;
                            self.media_preview.text_find_loaded_key = None;
                            self.media_preview.error = None;
                        }
                        Err(error) => {
                            self.media_preview.text_loaded_key = Some(result.key);
                            self.media_preview.text_document = None;
                            self.media_preview.text_view_buffer.clear();
                            self.media_preview.text_view_buffer_key = None;
                            self.media_preview.text_find_loaded_key = None;
                            self.media_preview.error = Some(error);
                        }
                    }
                    ctx.request_repaint();
                }
            }
            Err(error) if current_key.as_ref() == worker_key.as_ref() => {
                self.media_preview.error = Some(error);
            }
            Err(_) => {}
        }

        if let Some(pending) = self.media_preview.text_pending.take() {
            if current_key.as_ref() == Some(&pending.key) {
                self.start_media_preview_text(pending);
                return;
            }
        }
        self.media_preview.text_loading_key = None;
    }

    fn request_media_preview_text(&mut self, entry: &FileEntry) {
        let key = MediaPreviewKey::from_entry(entry);
        if self.media_preview.text_loaded_key.as_ref() == Some(&key)
            && (self.media_preview.text_document.is_some() || self.media_preview.error.is_some())
        {
            return;
        }
        if self.media_preview.text_loading_key.as_ref() == Some(&key) {
            return;
        }

        self.media_preview.text_document = None;
        self.media_preview.text_loaded_key = None;
        self.media_preview.text_loading_key = Some(key.clone());
        self.media_preview.text_view_buffer.clear();
        self.media_preview.text_view_buffer_key = None;
        self.media_preview.text_find_loaded_key = None;
        self.media_preview.error = None;

        let pending = MediaPreviewPending {
            key,
            path: entry.path.clone(),
        };
        if self.media_preview.text_rx.is_some() {
            self.media_preview.text_pending = Some(pending);
        } else {
            self.start_media_preview_text(pending);
        }
    }

    fn start_media_preview_text(&mut self, pending: MediaPreviewPending) {
        let key = pending.key;
        let path = pending.path;
        let (tx, rx) = mpsc::sync_channel(1);
        self.media_preview.text_worker_key = Some(key.clone());
        self.media_preview.text_rx = Some(rx);
        if let Err(error) = thread::Builder::new()
            .name("guth-text-preview".to_string())
            .spawn(move || {
                let document = load_text_preview(&path);
                let _ = tx.send(TextPreviewResult { key, document });
            })
        {
            self.media_preview.text_loading_key = None;
            self.media_preview.text_worker_key = None;
            self.media_preview.text_rx = None;
            self.media_preview.error = Some(format!("Text preview worker failed: {error}"));
        }
    }

    fn cached_media_preview_texture(
        &mut self,
        key: &MediaPreviewKey,
    ) -> Option<egui::TextureHandle> {
        let index = self
            .media_preview
            .image_cache
            .iter()
            .position(|cached| &cached.key == key)?;
        self.media_preview.cache_clock = self.media_preview.cache_clock.saturating_add(1);
        let cached = self.media_preview.image_cache.get_mut(index)?;
        cached.last_used = self.media_preview.cache_clock;
        Some(cached.texture.clone())
    }

    fn cache_media_preview_texture(&mut self, key: MediaPreviewKey, texture: egui::TextureHandle) {
        self.media_preview.cache_clock = self.media_preview.cache_clock.saturating_add(1);
        if let Some(cached) = self
            .media_preview
            .image_cache
            .iter_mut()
            .find(|cached| cached.key == key)
        {
            cached.texture = texture;
            cached.last_used = self.media_preview.cache_clock;
            return;
        }

        self.media_preview
            .image_cache
            .push_back(MediaPreviewCachedImage {
                key,
                texture,
                last_used: self.media_preview.cache_clock,
            });
        while self.media_preview.image_cache.len() > MEDIA_PREVIEW_IMAGE_CACHE_LIMIT {
            let Some((index, _)) = self
                .media_preview
                .image_cache
                .iter()
                .enumerate()
                .min_by_key(|(_, cached)| cached.last_used)
            else {
                break;
            };
            self.media_preview.image_cache.remove(index);
        }
    }

    fn header(&mut self, ui: &mut egui::Ui) {
        ui.horizontal(|ui| {
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Sidebar,
                None,
                self.sidebar_collapsed,
                true,
            )
            .on_hover_text("Toggle sidebar")
            .clicked()
            {
                self.sidebar_collapsed = !self.sidebar_collapsed;
                self.save_preferences();
            }
            ui.add_space(4.0);
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Back,
                None,
                false,
                self.history_cursor > 0,
            )
            .on_hover_text("Back")
            .clicked()
            {
                self.go_back();
            }
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Forward,
                None,
                false,
                self.history_cursor + 1 < self.history.len(),
            )
            .on_hover_text("Forward")
            .clicked()
            {
                self.go_forward();
            }
            if icon_button(ui, self.palette, ChromeIcon::Up, None, false, true)
                .on_hover_text("Parent folder")
                .clicked()
            {
                self.go_parent();
            }
            if icon_button(ui, self.palette, ChromeIcon::Home, None, false, true)
                .on_hover_text("Home")
                .clicked()
            {
                self.navigate_to(home_dir(), HistoryMode::Push);
            }
            ui.add_space(8.0);
            if self.location_entry_open {
                self.header_location_entry(ui);
            } else {
                self.header_breadcrumbs(ui);
            }
            ui.add_space(8.0);
            let search_changed = self.header_search_box(ui);
            if search_changed {
                self.update_deep_search();
            }
        });
    }

    fn header_search_box(&mut self, ui: &mut egui::Ui) -> bool {
        let width = (ui.available_width() * 0.30).clamp(190.0, 360.0);
        let mut changed = false;
        egui::Frame::none()
            .fill(self.palette.elevated_fill())
            .stroke(Stroke::new(1.0, self.palette.hairline()))
            .rounding(Rounding::same(theme_rounding(self.palette) + 9.0))
            .inner_margin(egui::Margin::symmetric(8.0, 3.0))
            .show(ui, |ui| {
                ui.set_width(width);
                ui.horizontal(|ui| {
                    let search_has_text = !self.search_query.is_empty();
                    let (icon_rect, _) = ui.allocate_exact_size(Vec2::splat(18.0), Sense::hover());
                    draw_chrome_icon(
                        ui.painter(),
                        icon_rect.shrink(2.0),
                        ChromeIcon::Search,
                        self.palette.muted(),
                    );
                    let response = ui.add(
                        egui::TextEdit::singleline(&mut self.search_query)
                            .hint_text(format!("Search {}", folder_title(&self.current_dir)))
                            .desired_width(
                                (width - if search_has_text { 58.0 } else { 36.0 }).max(90.0),
                            )
                            .frame(false),
                    );
                    if self.search_focus_requested {
                        response.request_focus();
                    }
                    changed = response.changed();
                    if response.has_focus()
                        && ui.input(|input| input.key_pressed(egui::Key::Escape))
                        && !self.search_query.is_empty()
                    {
                        self.search_query.clear();
                        changed = true;
                    }
                    if search_has_text {
                        let (clear_rect, clear_response) =
                            ui.allocate_exact_size(Vec2::splat(18.0), Sense::click());
                        let painter = ui.painter_at(clear_rect);
                        let clear_fill = if clear_response.hovered() {
                            self.palette.hover_fill()
                        } else {
                            self.palette.surface()
                        };
                        painter.circle_filled(clear_rect.center(), 8.0, clear_fill);
                        painter.circle_stroke(
                            clear_rect.center(),
                            8.0,
                            Stroke::new(1.0, self.palette.hairline()),
                        );
                        let clear_color = if clear_response.hovered() {
                            self.palette.text()
                        } else {
                            self.palette.muted()
                        };
                        painter.line_segment(
                            [
                                clear_rect.center() + egui::vec2(-3.0, -3.0),
                                clear_rect.center() + egui::vec2(3.0, 3.0),
                            ],
                            Stroke::new(1.2, clear_color),
                        );
                        painter.line_segment(
                            [
                                clear_rect.center() + egui::vec2(3.0, -3.0),
                                clear_rect.center() + egui::vec2(-3.0, 3.0),
                            ],
                            Stroke::new(1.2, clear_color),
                        );
                        if clear_response.clicked() {
                            self.search_query.clear();
                            changed = true;
                        }
                    }
                });
            });
        self.search_focus_requested = false;
        changed
    }

    fn header_location_entry(&mut self, ui: &mut egui::Ui) {
        let width = (ui.available_width() * 0.48).clamp(220.0, 560.0);
        let mut navigate = false;
        let mut close = false;
        egui::Frame::none()
            .fill(self.palette.accent_soft())
            .stroke(Stroke::new(1.0, self.palette.accent(0.0)))
            .rounding(Rounding::same(theme_rounding(self.palette) + 9.0))
            .inner_margin(egui::Margin::symmetric(8.0, 3.0))
            .show(ui, |ui| {
                ui.set_width(width);
                ui.horizontal(|ui| {
                    let (icon_rect, _) = ui.allocate_exact_size(Vec2::splat(18.0), Sense::hover());
                    draw_chrome_icon(
                        ui.painter(),
                        icon_rect.shrink(2.0),
                        ChromeIcon::Open,
                        self.palette.accent(0.0),
                    );
                    let response = ui.add(
                        egui::TextEdit::singleline(&mut self.path_input)
                            .hint_text("Type a folder path")
                            .desired_width((width - 36.0).max(120.0))
                            .frame(false),
                    );
                    response.request_focus();
                    navigate = response.has_focus()
                        && ui.input(|input| input.key_pressed(egui::Key::Enter));
                    close = ui.input(|input| input.key_pressed(egui::Key::Escape));
                });
            });

        if close {
            self.path_input = self.current_dir.display().to_string();
            self.location_entry_open = false;
        } else if navigate {
            let path = self.path_input.trim();
            if path.is_empty() {
                self.status = "Enter a folder path".to_string();
                self.path_input = self.current_dir.display().to_string();
            } else {
                self.location_entry_open = false;
                self.navigate_to(expand_tilde(path), HistoryMode::Push);
            }
        }
    }

    fn header_breadcrumbs(&mut self, ui: &mut egui::Ui) {
        egui::Frame::none()
            .fill(self.palette.elevated_fill())
            .stroke(Stroke::new(1.0, self.palette.hairline()))
            .rounding(Rounding::same(theme_rounding(self.palette) + 9.0))
            .inner_margin(egui::Margin::symmetric(5.0, 2.0))
            .show(ui, |ui| {
                ui.horizontal(|ui| {
                    let crumbs = breadcrumbs(&self.current_dir);
                    let last_index = crumbs.len().saturating_sub(1);
                    for (index, (label, path)) in crumbs.into_iter().enumerate() {
                        if index > 0 {
                            ui.label(RichText::new(">").size(11.0).color(self.palette.muted()));
                        }
                        let label = if path == home_dir() {
                            "Home".to_string()
                        } else {
                            label
                        };
                        let current = index == last_index;
                        if ui
                            .add(pathbar_button(self.palette, &label, current))
                            .clicked()
                            && !current
                        {
                            self.navigate_to(path, HistoryMode::Push);
                        }
                    }
                });
            });
    }

    fn toolbar(&mut self, ui: &mut egui::Ui) {
        let compact = ui.available_width() < 1_160.0;
        egui::Frame::none()
            .fill(themed_alpha(
                self.palette.elevated_fill(),
                self.theme_opacity,
            ))
            .stroke(Stroke::new(1.0, self.palette.hairline()))
            .rounding(Rounding::same(theme_rounding(self.palette) + 9.0))
            .inner_margin(egui::Margin::symmetric(8.0, 5.0))
            .show(ui, |ui| {
                ui.spacing_mut().item_spacing = Vec2::new(5.0, 5.0);
                ui.horizontal_wrapped(|ui| {
                    if icon_button(ui, self.palette, ChromeIcon::Refresh, None, false, true)
                        .on_hover_text("Refresh")
                        .clicked()
                    {
                        self.refresh_current_dir();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::FolderPlus,
                        if compact { None } else { Some("New Folder") },
                        false,
                        true,
                    )
                    .on_hover_text("New folder")
                    .clicked()
                    {
                        self.open_new_folder_dialog();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::FilePlus,
                        if compact { None } else { Some("New File") },
                        false,
                        true,
                    )
                    .on_hover_text("New file")
                    .clicked()
                    {
                        self.open_new_file_dialog();
                    }
                    command_separator(ui, self.palette);
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Cut,
                        None,
                        false,
                        !self.selected.is_empty(),
                    )
                    .on_hover_text("Cut")
                    .clicked()
                    {
                        self.copy_selection(ClipboardMode::Cut);
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Copy,
                        None,
                        false,
                        !self.selected.is_empty(),
                    )
                    .on_hover_text("Copy")
                    .clicked()
                    {
                        self.copy_selection(ClipboardMode::Copy);
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Paste,
                        None,
                        false,
                        self.clipboard.is_some(),
                    )
                    .on_hover_text("Paste")
                    .clicked()
                    {
                        self.paste_clipboard();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Trash,
                        None,
                        false,
                        !self.selected.is_empty(),
                    )
                    .on_hover_text("Move to trash")
                    .clicked()
                    {
                        self.trash_selection();
                    }
                    let current_bookmarked = self.is_bookmarked(&self.current_dir);
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Pin,
                        if compact {
                            None
                        } else {
                            Some(if current_bookmarked { "Unpin" } else { "Pin" })
                        },
                        current_bookmarked,
                        true,
                    )
                    .on_hover_text(if current_bookmarked {
                        "Unpin current folder"
                    } else {
                        "Pin current folder"
                    })
                    .clicked()
                    {
                        let current_dir = self.current_dir.clone();
                        if current_bookmarked {
                            self.remove_bookmark(&current_dir);
                        } else {
                            self.add_bookmark(current_dir);
                        }
                    }
                    command_separator(ui, self.palette);
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Search,
                        if compact { None } else { Some("Subfolders") },
                        self.recursive_search,
                        true,
                    )
                    .on_hover_text("Search subfolders")
                    .clicked()
                    {
                        self.recursive_search = !self.recursive_search;
                        self.save_preferences();
                        self.update_deep_search();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Hidden,
                        if compact { None } else { Some("Hidden") },
                        self.show_hidden,
                        true,
                    )
                    .on_hover_text("Show hidden files")
                    .clicked()
                    {
                        self.show_hidden = !self.show_hidden;
                        self.save_preferences();
                        self.refresh_current_dir();
                    }
                    command_separator(ui, self.palette);
                    egui::ComboBox::from_id_salt("sort-key")
                        .selected_text(self.sort_key.label())
                        .show_ui(ui, |ui| {
                            for key in SortKey::ALL {
                                let selected = self.sort_key == key;
                                if ui.selectable_label(selected, key.label()).clicked() && !selected
                                {
                                    self.set_sort_key(key);
                                }
                            }
                        });
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Sort,
                        if compact { None } else { Some("Desc") },
                        self.sort_reverse,
                        true,
                    )
                    .on_hover_text("Reverse sort order")
                    .clicked()
                    {
                        self.sort_reverse = !self.sort_reverse;
                        self.save_preferences();
                        self.refresh_current_dir();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::FolderPlus,
                        if compact { None } else { Some("Folders") },
                        self.folders_first,
                        true,
                    )
                    .on_hover_text("Folders first")
                    .clicked()
                    {
                        self.folders_first = !self.folders_first;
                        self.save_preferences();
                        self.refresh_current_dir();
                    }
                    command_separator(ui, self.palette);
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::List,
                        None,
                        self.view_mode == ViewMode::List,
                        true,
                    )
                    .on_hover_text("List view")
                    .clicked()
                    {
                        self.view_mode = ViewMode::List;
                        self.save_preferences();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Grid,
                        None,
                        self.view_mode == ViewMode::Grid,
                        true,
                    )
                    .on_hover_text("Grid view")
                    .clicked()
                    {
                        self.view_mode = ViewMode::Grid;
                        self.save_preferences();
                    }
                    ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                        if icon_button(
                            ui,
                            self.palette,
                            ChromeIcon::Settings,
                            if compact { None } else { Some("Settings") },
                            self.show_settings,
                            true,
                        )
                        .on_hover_text("Settings")
                        .clicked()
                        {
                            self.show_settings = !self.show_settings;
                        }
                        if icon_button(
                            ui,
                            self.palette,
                            ChromeIcon::Properties,
                            if compact { None } else { Some("Properties") },
                            self.show_properties,
                            true,
                        )
                        .on_hover_text("Properties")
                        .clicked()
                        {
                            self.show_properties = !self.show_properties;
                        }
                    });
                });
            });
    }

    fn places_panel(&mut self, ui: &mut egui::Ui) {
        self.pinned_drop_rect = None;
        if self.sidebar_collapsed {
            self.compact_places_panel(ui);
            return;
        }

        ui.vertical(|ui| {
            sidebar_section_header(ui, self.palette, "Places", "NAV");
            ui.add_space(6.0);
            let current_bookmarked = self.is_bookmarked(&self.current_dir);
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Pin,
                Some(if current_bookmarked {
                    "Unpin current"
                } else {
                    "Pin current"
                }),
                current_bookmarked,
                true,
            )
            .clicked()
            {
                let current_dir = self.current_dir.clone();
                if current_bookmarked {
                    self.remove_bookmark(&current_dir);
                } else {
                    self.add_bookmark(current_dir);
                }
            }
            ui.add_space(6.0);
            for place in known_places(&self.current_dir)
                .into_iter()
                .filter(|place| place.label != "Current")
            {
                self.place_row(ui, place, false);
            }
            let bookmarks = self.bookmark_places();
            ui.add_space(8.0);
            let pinned_detail = format!("{}/{}", bookmarks.len(), BOOKMARK_LIMIT);
            sidebar_section_header(ui, self.palette, "Pinned Folders", &pinned_detail);
            ui.add_space(6.0);
            self.pinned_drop_target(ui, false);
            if bookmarks.is_empty() && self.pin_drag_candidate.is_none() {
                sidebar_pin_empty_state(ui, self.palette);
            } else {
                for place in bookmarks {
                    self.place_row(ui, place, true);
                }
            }
            ui.with_layout(egui::Layout::bottom_up(egui::Align::Min), |ui| {
                ui.add_space(4.0);
                let thumbnail_stats = self.thumbnails.stats();
                sidebar_meter(
                    ui,
                    self.palette,
                    "Thumbnail cache",
                    &format!(
                        "{} of {} cached",
                        thumbnail_stats.cache_entries, thumbnail_stats.cache_limit
                    ),
                    ratio(thumbnail_stats.cache_entries, thumbnail_stats.cache_limit),
                );
                sidebar_meter(
                    ui,
                    self.palette,
                    "Current folder",
                    &format!(
                        "{} of {} scan cap",
                        self.entries.len(),
                        DIRECTORY_ENTRY_LIMIT
                    ),
                    ratio(self.entries.len(), DIRECTORY_ENTRY_LIMIT),
                );
                sidebar_section_header(ui, self.palette, "Session", "LIVE");
            });
        });
    }

    fn compact_places_panel(&mut self, ui: &mut egui::Ui) {
        ui.vertical_centered(|ui| {
            if icon_button(ui, self.palette, ChromeIcon::Forward, None, false, true).clicked() {
                self.sidebar_collapsed = false;
                self.save_preferences();
            }
            ui.separator();
            for place in known_places(&self.current_dir)
                .into_iter()
                .filter(|place| place.label != "Current")
            {
                self.compact_place_row(ui, place, false);
            }
            let bookmarks = self.bookmark_places();
            if !bookmarks.is_empty() {
                ui.add_space(8.0);
                ui.separator();
                compact_section_icon(ui, self.palette, ChromeIcon::Pin);
                for place in bookmarks {
                    self.compact_place_row(ui, place, true);
                }
            } else if self.pin_drag_candidate.is_some() {
                ui.add_space(8.0);
                ui.separator();
                compact_section_icon(ui, self.palette, ChromeIcon::Pin);
            }
            self.pinned_drop_target(ui, true);
            ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
                ui.add_space(2.0);
                let (rect, _) = ui.allocate_exact_size(Vec2::new(30.0, 42.0), Sense::hover());
                let painter = ui.painter_at(rect);
                let stats = self.thumbnails.stats();
                compact_meter(
                    &painter,
                    rect,
                    self.palette,
                    ratio(stats.cache_entries, stats.cache_limit),
                );
                let (rect, _) = ui.allocate_exact_size(Vec2::new(30.0, 42.0), Sense::hover());
                let painter = ui.painter_at(rect);
                compact_meter(
                    &painter,
                    rect,
                    self.palette,
                    ratio(self.entries.len(), DIRECTORY_ENTRY_LIMIT),
                );
            });
        });
    }

    fn compact_place_row(&mut self, ui: &mut egui::Ui, place: Place, removable: bool) {
        let active = self.current_dir == canonical_display_path(&place.path);
        let trash_drop_target =
            place.path == trash_files_dir() && !self.entry_drag_paths.is_empty();
        let (rect, response) = ui.allocate_exact_size(Vec2::new(34.0, 30.0), Sense::click());
        let painter = ui.painter_at(rect);
        let row_rect = rect.shrink2(egui::vec2(2.0, 1.0));
        let trash_drop_hovered = trash_drop_target && response.hovered();
        painter.rect_filled(
            row_rect,
            Rounding::same(theme_rounding(self.palette) + 6.0),
            if trash_drop_hovered || active {
                self.palette.selected_fill()
            } else if response.hovered() {
                self.palette.hover_fill()
            } else {
                Color32::TRANSPARENT
            },
        );
        if active {
            painter.line_segment(
                [
                    row_rect.left_center() + egui::vec2(0.0, -7.0),
                    row_rect.left_center() + egui::vec2(0.0, 7.0),
                ],
                Stroke::new(3.0, self.palette.accent(0.0)),
            );
        }
        if trash_drop_hovered {
            painter.rect_stroke(
                row_rect.shrink(1.0),
                Rounding::same(theme_rounding(self.palette) + 6.0),
                Stroke::new(1.3, self.palette.accent(0.0)),
            );
        } else if response.hovered() && !active {
            painter.rect_stroke(
                row_rect.shrink(1.0),
                Rounding::same(theme_rounding(self.palette) + 6.0),
                Stroke::new(1.0, self.palette.hairline()),
            );
        }
        let icon_rect = Rect::from_center_size(row_rect.center(), Vec2::new(17.0, 17.0));
        draw_place_icon(&painter, icon_rect, &place, removable, active, self.palette);
        let response = response.on_hover_text(format!("{} · {}", place.label, place.detail));
        if response.clicked() {
            self.navigate_to(place.path.clone(), HistoryMode::Push);
        }
        if trash_drop_hovered && ui.input(|input| input.pointer.any_released()) {
            self.trash_dragged_entries();
        }
        response.context_menu(|ui| self.place_context_menu(ui, &place, removable));
        ui.add_space(1.0);
    }

    fn place_row(&mut self, ui: &mut egui::Ui, place: Place, removable: bool) {
        let active = self.current_dir == canonical_display_path(&place.path);
        let trash_drop_target =
            place.path == trash_files_dir() && !self.entry_drag_paths.is_empty();
        let (rect, response) =
            ui.allocate_exact_size(Vec2::new(ui.available_width(), 28.0), Sense::click());
        let painter = ui.painter_at(rect);
        let row_rect = rect.shrink2(egui::vec2(3.0, 0.0));
        let trash_drop_hovered = trash_drop_target && response.hovered();
        painter.rect_filled(
            row_rect,
            Rounding::same(theme_rounding(self.palette)),
            if trash_drop_hovered || active {
                self.palette.selected_fill()
            } else if response.hovered() {
                self.palette.hover_fill()
            } else {
                Color32::TRANSPARENT
            },
        );
        if active {
            painter.line_segment(
                [
                    row_rect.left_center() + egui::vec2(0.0, -7.0),
                    row_rect.left_center() + egui::vec2(0.0, 7.0),
                ],
                Stroke::new(3.0, self.palette.accent(0.0)),
            );
        }
        if trash_drop_hovered {
            painter.rect_stroke(
                row_rect.shrink(1.0),
                Rounding::same(theme_rounding(self.palette)),
                Stroke::new(1.3, self.palette.accent(0.0)),
            );
        } else if response.hovered() && !active {
            painter.rect_stroke(
                row_rect.shrink(1.0),
                Rounding::same(theme_rounding(self.palette)),
                Stroke::new(1.0, self.palette.hairline()),
            );
        }
        let icon_rect = Rect::from_center_size(
            row_rect.left_center() + egui::vec2(16.0, 0.0),
            Vec2::new(18.0, 18.0),
        );
        draw_place_icon(&painter, icon_rect, &place, removable, active, self.palette);
        let label_limit = if row_rect.width() > 205.0 { 22 } else { 16 };
        painter.text(
            row_rect.left_center() + egui::vec2(34.0, 0.0),
            Align2::LEFT_CENTER,
            truncate_middle(&place.label, label_limit),
            FontId::proportional(12.5),
            if place.path.is_dir() {
                self.palette.text()
            } else {
                self.palette.muted()
            },
        );
        if row_rect.width() > 168.0 {
            if removable {
                let marker_color = if place.path.is_dir() {
                    self.palette.accent(0.0)
                } else {
                    self.palette.muted()
                };
                let marker = if place.path.is_dir() {
                    "Pinned"
                } else {
                    "Missing"
                };
                draw_chrome_icon(
                    &painter,
                    Rect::from_center_size(
                        row_rect.right_center() + egui::vec2(-48.0, 0.0),
                        Vec2::splat(10.5),
                    ),
                    ChromeIcon::Pin,
                    marker_color,
                );
                painter.text(
                    row_rect.right_center() + egui::vec2(-8.0, 0.0),
                    Align2::RIGHT_CENTER,
                    marker,
                    FontId::proportional(10.0),
                    marker_color,
                );
            } else {
                painter.text(
                    row_rect.right_center() + egui::vec2(-8.0, 0.0),
                    Align2::RIGHT_CENTER,
                    if trash_drop_hovered {
                        "Release".to_string()
                    } else {
                        truncate_middle(&place.detail, 10)
                    },
                    FontId::monospace(9.0),
                    if active {
                        self.palette.accent(0.0)
                    } else {
                        self.palette.muted()
                    },
                );
            }
        }
        if response.clicked() {
            self.navigate_to(place.path.clone(), HistoryMode::Push);
        }
        if trash_drop_hovered && ui.input(|input| input.pointer.any_released()) {
            self.trash_dragged_entries();
        }
        response.context_menu(|ui| self.place_context_menu(ui, &place, removable));
        ui.add_space(1.0);
    }

    fn place_context_menu(&mut self, ui: &mut egui::Ui, place: &Place, removable: bool) {
        context_menu_chrome(ui, self.palette, &place.label, &place.detail);
        if removable {
            let bookmark_index = bookmark_position(&self.bookmarks, &place.path);
            let can_move_up = bookmark_index.is_some_and(|index| index > 0);
            let can_move_down = bookmark_index
                .is_some_and(|index| index + 1 < self.bookmarks.len().min(BOOKMARK_LIMIT));
            context_section(ui, self.palette, "Pinned");
            if context_action(
                ui,
                self.palette,
                ChromeIcon::Up,
                "Move Up",
                "Order",
                can_move_up,
                false,
            ) {
                self.move_bookmark(&place.path, -1);
                ui.close_menu();
            }
            if context_action(
                ui,
                self.palette,
                ChromeIcon::Forward,
                "Move Down",
                "Order",
                can_move_down,
                false,
            ) {
                self.move_bookmark(&place.path, 1);
                ui.close_menu();
            }
            if context_action(
                ui,
                self.palette,
                ChromeIcon::Pin,
                "Unpin Folder",
                "Pinned",
                true,
                false,
            ) {
                self.remove_bookmark(&place.path);
                ui.close_menu();
            }
            return;
        }

        if context_action(
            ui,
            self.palette,
            ChromeIcon::Open,
            "Open",
            "Enter",
            place.path.is_dir(),
            false,
        ) {
            self.navigate_to(place.path.clone(), HistoryMode::Push);
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Pin,
            "Pin This Folder",
            "Quick Access",
            place.path.is_dir() && !self.is_bookmarked(&place.path),
            false,
        ) {
            self.add_bookmark(place.path.clone());
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Terminal,
            "Open Terminal Here",
            "Shell",
            place.path.is_dir(),
            false,
        ) {
            match launch_terminal_at(&place.path) {
                Ok(()) => self.push_log(format!("TERMINAL // {}", place.path.display())),
                Err(error) => self.report_error("Could not open terminal", "TERMINAL", error),
            }
            ui.close_menu();
        }
    }

    fn activity_panel(&mut self, ui: &mut egui::Ui, _phase: f32) {
        let available_width = ui.available_width();
        let compact = available_width < 1_050.0;
        let narrow = available_width < 760.0;
        ui.horizontal(|ui| {
            let visible_count = self.visible_entries().len();
            status_item(ui, self.palette, format!("{visible_count} items"), false);
            let selected = self.selected_entries();
            if !selected.is_empty() {
                let summary = selection_summary(&selected);
                let detail = if narrow {
                    format!("{} selected", summary.count)
                } else if compact {
                    format!(
                        "{} selected · {}",
                        summary.count,
                        human_size(summary.file_bytes)
                    )
                } else if summary.folders > 0 {
                    format!(
                        "{} selected · {} files · {} folders · {}",
                        summary.count,
                        summary.files,
                        summary.folders,
                        human_size(summary.file_bytes)
                    )
                } else {
                    format!(
                        "{} selected · {}",
                        summary.count,
                        human_size(summary.file_bytes)
                    )
                };
                status_item(ui, self.palette, detail, true);
            }
            if let Some(snapshot) = &self.snapshot {
                if !compact && snapshot.hidden_entries > 0 {
                    status_item(
                        ui,
                        self.palette,
                        format!("{} hidden", snapshot.hidden_entries),
                        false,
                    );
                }
                if snapshot.scan_limited {
                    status_item(ui, self.palette, "scan capped", true);
                }
            }
            if !compact {
                if let Some(clipboard) = &self.clipboard {
                    status_item(
                        ui,
                        self.palette,
                        format!(
                            "{} {} items",
                            pretty_value(clipboard.label()),
                            clipboard.paths.len()
                        ),
                        false,
                    );
                }
            }
            if let Some(action) = self.undo_stack.last() {
                let action_label = pretty_value(action.label());
                if !compact {
                    status_item(ui, self.palette, &action_label, false);
                }
                let button_label = if compact { &action_label } else { "Undo" };
                if ui
                    .add_enabled(
                        self.operation_rx.is_none(),
                        status_button(self.palette, button_label),
                    )
                    .on_disabled_hover_text("Wait for the current operation to finish")
                    .clicked()
                {
                    self.undo_last();
                }
            }
            if let Some(progress) = &self.operation_progress {
                let percent = operation_percent(progress.done_items, progress.total_items) * 100.0;
                let detail = if narrow {
                    format!(
                        "{}% {}",
                        percent.round() as u32,
                        pretty_value(self.operation_title.as_deref().unwrap_or("Working"))
                    )
                } else {
                    format!("{}% {}", percent.round() as u32, operation_detail(progress))
                };
                status_item(ui, self.palette, detail, true);
            }
            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                if !narrow && ui.add(status_button(self.palette, "Terminal")).clicked() {
                    match launch_terminal_at(&self.current_dir) {
                        Ok(()) => self.push_log("TERMINAL // CURRENT DIRECTORY".to_string()),
                        Err(error) => {
                            self.report_error("Could not open terminal", "TERMINAL", error)
                        }
                    }
                }
                if !compact {
                    status_item(ui, self.palette, self.palette.label(), false);
                }
                if self.operation_progress.is_none() && !self.status.is_empty() {
                    status_item(ui, self.palette, &self.status, false);
                }
            });
        });
    }

    fn rendering_panel(&mut self, ui: &mut egui::Ui, _phase: f32) {
        overlay_header(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Settings,
            "View Options",
            "Appearance, renderer, resources, and background",
        );
        ui.add_space(10.0);

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::List,
            "View",
            |ui| {
                ui.horizontal_wrapped(|ui| {
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::List,
                        Some("List"),
                        self.view_mode == ViewMode::List,
                        true,
                    )
                    .clicked()
                    {
                        self.view_mode = ViewMode::List;
                        self.save_preferences();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Grid,
                        Some("Grid"),
                        self.view_mode == ViewMode::Grid,
                        true,
                    )
                    .clicked()
                    {
                        self.view_mode = ViewMode::Grid;
                        self.save_preferences();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::FolderPlus,
                        Some("Folders first"),
                        self.folders_first,
                        true,
                    )
                    .clicked()
                    {
                        self.folders_first = !self.folders_first;
                        self.save_preferences();
                        self.refresh_current_dir();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Hidden,
                        Some("Hidden"),
                        self.show_hidden,
                        true,
                    )
                    .clicked()
                    {
                        self.show_hidden = !self.show_hidden;
                        self.save_preferences();
                        self.refresh_current_dir();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Search,
                        Some("Subfolders"),
                        self.recursive_search,
                        true,
                    )
                    .clicked()
                    {
                        self.recursive_search = !self.recursive_search;
                        self.save_preferences();
                        self.update_deep_search();
                    }
                });
                ui.horizontal_wrapped(|ui| {
                    ui.label(RichText::new("Sort").size(12.0).color(self.palette.muted()));
                    egui::ComboBox::from_id_salt("settings-sort-key")
                        .selected_text(self.sort_key.label())
                        .show_ui(ui, |ui| {
                            for key in SortKey::ALL {
                                let selected = self.sort_key == key;
                                if ui.selectable_label(selected, key.label()).clicked() && !selected
                                {
                                    self.set_sort_key(key);
                                }
                            }
                        });
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Sort,
                        Some("Descending"),
                        self.sort_reverse,
                        true,
                    )
                    .clicked()
                    {
                        self.sort_reverse = !self.sort_reverse;
                        self.save_preferences();
                        self.refresh_current_dir();
                    }
                });
                muted_label(
                    ui,
                    "Folders first keeps directories grouped above files unless you turn it off.",
                );
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Settings,
            "Appearance",
            |ui| {
                ui.horizontal_wrapped(|ui| {
                    egui::ComboBox::from_id_salt("activity-palette")
                        .selected_text(self.palette.label())
                        .show_ui(ui, |ui| {
                            for palette in AppPalette::ALL {
                                if ui
                                    .selectable_value(&mut self.palette, palette, palette.label())
                                    .clicked()
                                {
                                    self.save_preferences();
                                }
                            }
                        });
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Settings,
                        Some("Motion"),
                        self.motion_enabled,
                        true,
                    )
                    .clicked()
                    {
                        self.motion_enabled = !self.motion_enabled;
                        self.save_preferences();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Search,
                        Some("Hover preview"),
                        self.hover_effects_enabled,
                        true,
                    )
                    .clicked()
                    {
                        self.hover_effects_enabled = !self.hover_effects_enabled;
                        self.save_preferences();
                    }
                });
                if ui
                    .add(
                        egui::Slider::new(&mut self.theme_opacity, 0.55..=1.0)
                            .text("Theme opacity"),
                    )
                    .changed()
                {
                    self.save_preferences();
                }
                muted_label(
                    ui,
                    "Dark is the default startup style. Opacity affects panels and overlays.",
                );
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Preview,
            "Knotwork Plugins",
            |ui| {
                property_line(ui, "System", KNOTWORK_NAME);
                property_line(ui, "Installed", &self.plugins.all().len().to_string());
                for plugin in self.plugins.all() {
                    ui.separator();
                    property_line(ui, "Plugin", plugin.name);
                    property_line(ui, "Slot", "Overlay");
                    property_line(ui, "Shortcut", plugin.shortcut);
                    muted_label(ui, plugin.description);
                    ui.add_space(8.0);
                    let enabled = match plugin.id {
                        MEDIA_PREVIEW_PLUGIN_ID => self.media_preview_plugin_enabled,
                        TEXT_PREVIEW_PLUGIN_ID => self.text_preview_plugin_enabled,
                        _ => false,
                    };
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Preview,
                        Some(if enabled { "Enabled" } else { "Enable" }),
                        enabled,
                        true,
                    )
                    .clicked()
                    {
                        match plugin.id {
                            MEDIA_PREVIEW_PLUGIN_ID => {
                                self.media_preview_plugin_enabled =
                                    !self.media_preview_plugin_enabled;
                                if !self.media_preview_plugin_enabled
                                    && self.media_preview.active_plugin
                                        == Some(PreviewPluginKind::Media)
                                {
                                    self.close_media_preview_plugin();
                                }
                            }
                            TEXT_PREVIEW_PLUGIN_ID => {
                                self.text_preview_plugin_enabled =
                                    !self.text_preview_plugin_enabled;
                                if !self.text_preview_plugin_enabled
                                    && self.media_preview.active_plugin
                                        == Some(PreviewPluginKind::Text)
                                {
                                    self.close_media_preview_plugin();
                                }
                            }
                            _ => {}
                        }
                        self.save_preferences();
                    }
                }
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Properties,
            "Background",
            |ui| {
                let response = ui.add(
                    egui::TextEdit::singleline(&mut self.background_image_path)
                        .hint_text("~/Pictures/wallpaper.png")
                        .desired_width(ui.available_width()),
                );
                if response.changed() {
                    self.background_loaded_path = None;
                    self.background_texture = None;
                    self.background_error = None;
                }
                ui.horizontal_wrapped(|ui| {
                    let apply_clicked = icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Properties,
                        Some("Apply"),
                        false,
                        true,
                    )
                    .clicked();
                    let enter_pressed = response.lost_focus()
                        && ui.input(|input| input.key_pressed(egui::Key::Enter));
                    if apply_clicked || enter_pressed {
                        self.background_loaded_path = None;
                        self.save_preferences();
                        self.status = "Background updated".to_string();
                    }
                    if icon_button(
                        ui,
                        self.palette,
                        ChromeIcon::Trash,
                        Some("Clear"),
                        false,
                        true,
                    )
                    .clicked()
                    {
                        self.background_image_path.clear();
                        self.background_texture = None;
                        self.background_loaded_path = None;
                        self.background_error = None;
                        self.save_preferences();
                        self.status = "Background cleared".to_string();
                    }
                });
                if ui
                    .add(
                        egui::Slider::new(&mut self.background_image_opacity, 0.0..=0.85)
                            .text("Background opacity"),
                    )
                    .changed()
                {
                    self.save_preferences();
                }
                if let Some(error) = &self.background_error {
                    ui.label(RichText::new(error).size(11.0).color(self.palette.muted()));
                }
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::List,
            "Resource Bounds",
            |ui| {
                let thumbnail_stats = self.thumbnails.stats();
                property_line(
                    ui,
                    "Pins",
                    &format!("{}/{}", self.bookmarks.len(), BOOKMARK_LIMIT),
                );
                property_line(
                    ui,
                    "History",
                    &format!("{}/{}", self.history.len(), HISTORY_LIMIT),
                );
                property_line(
                    ui,
                    "Undo",
                    &format!("{}/{}", self.undo_stack.len(), UNDO_LIMIT),
                );
                property_line(ui, "Logs", &format!("{}/{}", self.logs.len(), LOG_LIMIT));
                property_line(
                    ui,
                    "Directory",
                    &format!(
                        "{}/{} scan entries",
                        self.entries.len(),
                        DIRECTORY_ENTRY_LIMIT
                    ),
                );
                if let Some(results) = &self.deep_results {
                    property_line(
                        ui,
                        "Search",
                        &format!(
                            "{}/{} results / {} dirs",
                            results.entries.len(),
                            results.result_limit,
                            results.visited_dirs
                        ),
                    );
                } else {
                    property_line(ui, "Search", &format!("0/{} results", SEARCH_LIMIT));
                }
                property_line(
                    ui,
                    "Operations",
                    &format!(
                        "{} sources / {} items / {} depth",
                        OPERATION_SOURCE_LIMIT, OPERATION_ITEM_LIMIT, OPERATION_DEPTH_LIMIT
                    ),
                );
                property_line(
                    ui,
                    "Launchers",
                    &format!(
                        "{} active / {} reapers",
                        active_child_reapers(),
                        CHILD_REAPER_LIMIT
                    ),
                );
                property_line(
                    ui,
                    "Thumbnails",
                    &format!(
                        "{} cached / {} pending / {} queued / {} active",
                        thumbnail_stats.cache_entries,
                        thumbnail_stats.pending,
                        thumbnail_stats.queued,
                        thumbnail_stats.active_workers
                    ),
                );
                property_line(
                    ui,
                    "Thumb limits",
                    &format!(
                        "{} cache / {} queue / {} workers",
                        thumbnail_stats.cache_limit,
                        thumbnail_stats.queue_limit,
                        thumbnail_stats.worker_limit
                    ),
                );
                property_line(
                    ui,
                    "Preview",
                    &format!(
                        "{} cache / {} image / {} px / async worker",
                        MEDIA_PREVIEW_IMAGE_CACHE_LIMIT,
                        human_size(MEDIA_PREVIEW_IMAGE_BYTES_LIMIT),
                        MEDIA_PREVIEW_IMAGE_MAX_EDGE
                    ),
                );
                property_line(
                    ui,
                    "Text preview",
                    &format!(
                        "{} / {} lines / async worker",
                        human_size(TEXT_PREVIEW_BYTES_LIMIT),
                        TEXT_PREVIEW_LINE_LIMIT
                    ),
                );
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Terminal,
            "Renderer",
            |ui| {
                property_line(ui, "GPU", &pretty_value(&self.runtime.gpu_mode));
                property_line(ui, "Renderer", &pretty_value(&self.runtime.renderer));
                property_line(ui, "Shader", &pretty_value(&self.runtime.shader));
                property_line(ui, "WGPU", &pretty_value(&self.runtime.wgpu_backends));
                property_line(ui, "Power", &pretty_value(&self.runtime.wgpu_power));
                property_line(ui, "Latency", &pretty_value(&self.runtime.frame_latency));
                property_line(ui, "VSync", if self.runtime.vsync { "On" } else { "Off" });
            },
        );
    }

    fn file_panel(&mut self, ui: &mut egui::Ui, phase: f32, now: f64) {
        let response = egui::Frame::none()
            .inner_margin(egui::Margin::symmetric(14.0, 10.0))
            .show(ui, |ui| {
                self.folder_overview(ui);
                ui.add_space(10.0);
                if let Some(progress) = &self.operation_progress {
                    let title =
                        pretty_value(self.operation_title.as_deref().unwrap_or("Operation"));
                    progress_card(ui, &title, progress, phase, self.palette);
                    ui.add_space(8.0);
                }

                let entries = self.visible_entries();
                if entries.is_empty() {
                    empty_state(
                        ui,
                        &self.search_query,
                        self.recursive_search,
                        self.deep_search_active(),
                        self.palette,
                    );
                    self.empty_state_actions(ui);
                    return;
                }

                let scroll_height = (ui.available_height() - 12.0).max(280.0);
                egui::ScrollArea::vertical()
                    .auto_shrink([false, false])
                    .max_height(scroll_height)
                    .show(ui, |ui| match self.view_mode {
                        ViewMode::List => self.list_view(ui, entries, phase, now),
                        ViewMode::Grid => self.grid_view(ui, entries, phase, now),
                    });
            });
        let external_drag = ui.ctx().input(|input| !input.raw.hovered_files.is_empty());
        if external_drag && !self.dropped_on_pinned_folders(ui.ctx()) {
            draw_drop_outline(
                ui.painter(),
                response.response.rect.shrink(3.0),
                self.palette,
                "Drop to copy here",
            );
        }
    }

    fn folder_overview(&self, ui: &mut egui::Ui) {
        ui.horizontal(|ui| {
            let (icon_rect, _) = ui.allocate_exact_size(Vec2::splat(50.0), Sense::hover());
            draw_folder_summary_icon(ui.painter(), icon_rect);
            ui.add_space(5.0);
            ui.vertical(|ui| {
                ui.horizontal(|ui| {
                    ui.label(
                        RichText::new(folder_title(&self.current_dir))
                            .size(18.0)
                            .strong()
                            .color(self.palette.text()),
                    );
                    if self.is_bookmarked(&self.current_dir) {
                        inline_chrome_icon(
                            ui,
                            self.palette,
                            ChromeIcon::Pin,
                            self.palette.accent(0.0),
                        );
                    }
                });
                ui.label(
                    RichText::new(path_for_display(&self.current_dir))
                        .size(10.5)
                        .color(self.palette.muted()),
                );
                ui.label(RichText::new(self.folder_detail()).size(10.5).color(
                    if self.deep_search_active() {
                        self.palette.accent(0.0)
                    } else {
                        self.palette.secondary_text()
                    },
                ));
            });
        });
        ui.add_space(7.0);
        let (divider, _) =
            ui.allocate_exact_size(Vec2::new(ui.available_width(), 1.0), Sense::hover());
        ui.painter().line_segment(
            [divider.left_center(), divider.right_center()],
            Stroke::new(1.0, self.palette.hairline()),
        );
    }

    fn empty_state_actions(&mut self, ui: &mut egui::Ui) {
        ui.add_space(10.0);
        ui.horizontal_centered(|ui| {
            if self.search_query.trim().is_empty() {
                if icon_button(
                    ui,
                    self.palette,
                    ChromeIcon::FolderPlus,
                    Some("New Folder"),
                    false,
                    true,
                )
                .clicked()
                {
                    self.open_new_folder_dialog();
                }
                if icon_button(
                    ui,
                    self.palette,
                    ChromeIcon::FilePlus,
                    Some("New File"),
                    false,
                    true,
                )
                .clicked()
                {
                    self.open_new_file_dialog();
                }
            } else {
                if icon_button(
                    ui,
                    self.palette,
                    ChromeIcon::Search,
                    Some("Clear Search"),
                    false,
                    true,
                )
                .clicked()
                {
                    self.search_query.clear();
                    self.update_deep_search();
                }
                if icon_button(
                    ui,
                    self.palette,
                    ChromeIcon::Search,
                    Some("Subfolders"),
                    self.recursive_search,
                    true,
                )
                .clicked()
                {
                    self.recursive_search = !self.recursive_search;
                    self.save_preferences();
                    self.update_deep_search();
                }
            }
        });
    }

    fn folder_detail(&self) -> String {
        if self.deep_search_active() {
            return "Searching subfolders within configured limits".to_string();
        }
        if let Some(results) = &self.deep_results {
            return format!(
                "{} matches across {} folders{}",
                results.entries.len(),
                results.visited_dirs,
                if results.limited() { " · capped" } else { "" }
            );
        }
        if !self.search_query.trim().is_empty() {
            return "Filtered in the current folder".to_string();
        }
        self.snapshot
            .as_ref()
            .map(|snapshot| {
                format!(
                    "{} folders · {} files · {}{}",
                    snapshot.directory_count,
                    snapshot.file_count,
                    human_size(snapshot.total_size),
                    if snapshot.scan_limited {
                        " · scan capped"
                    } else {
                        ""
                    }
                )
            })
            .unwrap_or_else(|| "Loading folder details".to_string())
    }

    fn list_view(&mut self, ui: &mut egui::Ui, entries: Vec<FileEntry>, phase: f32, now: f64) {
        self.list_header(ui);
        ui.add_space(5.0);
        for (index, entry) in entries.into_iter().enumerate() {
            self.entry_row(ui, entry, index, phase, now);
        }
    }

    fn list_header(&mut self, ui: &mut egui::Ui) {
        let (rect, _) =
            ui.allocate_exact_size(Vec2::new(ui.available_width(), 30.0), Sense::hover());
        let painter = ui.painter_at(rect);
        let header_rect = rect.shrink2(egui::vec2(0.0, 1.0));
        painter.rect_filled(
            header_rect,
            Rounding::same(theme_rounding(self.palette) + 7.0),
            self.palette.recessed_fill(),
        );
        painter.rect_stroke(
            header_rect,
            Rounding::same(theme_rounding(self.palette) + 7.0),
            Stroke::new(1.0, self.palette.hairline()),
        );
        painter.line_segment(
            [header_rect.left_bottom(), header_rect.right_bottom()],
            Stroke::new(1.0, self.palette.hover_fill()),
        );
        let columns = list_columns(rect);
        draw_list_column_rules(&painter, rect, columns, self.palette);
        let mut clicked_sort = None;
        for (label, key, x, cell) in [
            (
                "Name",
                SortKey::Name,
                columns.name,
                Rect::from_min_max(
                    Pos2::new(rect.left() + 4.0, rect.top()),
                    Pos2::new(columns.kind - 8.0, rect.bottom()),
                ),
            ),
            (
                "Kind",
                SortKey::Kind,
                columns.kind,
                Rect::from_min_max(
                    Pos2::new(columns.kind - 8.0, rect.top()),
                    Pos2::new(columns.size - 8.0, rect.bottom()),
                ),
            ),
            (
                "Size",
                SortKey::Size,
                columns.size,
                Rect::from_min_max(
                    Pos2::new(columns.size - 8.0, rect.top()),
                    Pos2::new(columns.modified - 8.0, rect.bottom()),
                ),
            ),
            (
                "Modified",
                SortKey::Modified,
                columns.modified,
                Rect::from_min_max(
                    Pos2::new(columns.modified - 8.0, rect.top()),
                    Pos2::new(rect.right() - 4.0, rect.bottom()),
                ),
            ),
        ] {
            let response = ui.interact(cell, ui.id().with(("list-header", label)), Sense::click());
            let active = self.sort_key == key;
            if response.hovered() || active {
                painter.rect_filled(
                    cell.shrink2(egui::vec2(3.0, 4.0)),
                    Rounding::same(theme_rounding(self.palette) + 3.0),
                    if active {
                        self.palette.selected_fill()
                    } else {
                        self.palette.hover_fill()
                    },
                );
            }
            let color = if active {
                self.palette.accent(0.0)
            } else if response.hovered() {
                self.palette.text()
            } else {
                self.palette.muted()
            };
            painter.text(
                Pos2::new(x, rect.center().y),
                Align2::LEFT_CENTER,
                label,
                FontId::proportional(11.0),
                color,
            );
            if active {
                draw_sort_indicator(
                    &painter,
                    Pos2::new(x + label.len() as f32 * 6.3 + 8.0, rect.center().y),
                    self.sort_reverse,
                    color,
                );
            }
            if response.clicked() {
                clicked_sort = Some(key);
            }
        }
        if let Some(key) = clicked_sort {
            self.set_sort_key(key);
        }
    }

    fn entry_row(
        &mut self,
        ui: &mut egui::Ui,
        entry: FileEntry,
        index: usize,
        phase: f32,
        now: f64,
    ) {
        let selected = self.selected.contains(&entry.path);
        let focused = self.selection_focus.as_ref() == Some(&entry.path);
        let (rect, response) = ui.allocate_exact_size(
            Vec2::new(ui.available_width(), 46.0),
            Sense::click_and_drag(),
        );
        let painter = ui.painter_at(rect);
        let row_rect = rect.shrink2(egui::vec2(1.0, 3.0));
        let accent = self.palette.accent(phase);
        if selected {
            painter.rect_filled(
                row_rect,
                Rounding::same(theme_rounding(self.palette) + 5.0),
                self.palette.selected_fill(),
            );
            painter.rect_filled(
                Rect::from_min_max(
                    row_rect.left_top(),
                    row_rect.left_bottom() + egui::vec2(3.0, 0.0),
                ),
                Rounding::same(theme_rounding(self.palette) + 5.0),
                accent,
            );
        } else if response.hovered() {
            painter.rect_filled(
                row_rect,
                Rounding::same(theme_rounding(self.palette) + 5.0),
                self.palette.hover_fill(),
            );
        } else if index % 2 == 1 {
            painter.rect_filled(
                row_rect,
                Rounding::same(theme_rounding(self.palette) + 5.0),
                self.palette.row_alt_fill(),
            );
        }
        if focused {
            painter.rect_stroke(
                row_rect.shrink(1.0),
                Rounding::same(theme_rounding(self.palette) + 5.0),
                Stroke::new(1.4, accent),
            );
        }
        let columns = list_columns(rect);
        draw_list_row_rules(&painter, rect, columns, self.palette);
        let muted = if selected {
            self.palette.secondary_text()
        } else {
            self.palette.muted()
        };
        let media_class = media_class_for_path(
            &entry.path,
            entry.kind == EntryKind::Directory,
            entry.kind == EntryKind::Symlink,
        );
        if response.hovered() {
            self.hover.observe(&entry, media_class, now);
            self.hover_animation_active = self.hover_effects_enabled;
        }
        self.observe_entry_drag_source(&response, &entry);
        let hover_intent = response.hovered()
            && self.hover_effects_enabled
            && self
                .hover
                .intent_ready(&entry.path, now, VIDEO_HOVER_INTENT_SECONDS);
        let thumb_rect = Rect::from_center_size(
            Pos2::new(columns.name + 19.0, rect.center().y),
            Vec2::new(36.0, 34.0),
        );
        let tile_visible = ui.clip_rect().intersects(rect);
        let modified_ms = thumbnail_modified_ms(entry.modified);
        if tile_visible {
            self.thumbnails
                .request_poster(&entry.path, entry.size, modified_ms);
            if hover_intent {
                self.thumbnails
                    .request_hover_strip(&entry.path, entry.size, modified_ms);
            }
        }
        if let Some(view) = tile_visible
            .then(|| {
                self.thumbnails.thumbnail_for(
                    &entry.path,
                    entry.size,
                    modified_ms,
                    hover_intent,
                    phase,
                )
            })
            .flatten()
        {
            paint_thumbnail(
                &painter,
                thumb_rect,
                view,
                response.hovered() && self.hover_effects_enabled,
                accent,
            );
            if media_class == MediaClass::Video {
                draw_video_badge(&painter, thumb_rect, view.animated);
            }
        } else {
            draw_file_type_icon(
                &painter,
                thumb_rect,
                media_class,
                response.hovered() && self.hover_effects_enabled,
                self.thumbnails.is_loading(&entry.path),
                self.palette,
            );
        }
        let name_text = truncate_middle(&entry.name, 38);
        painter.text(
            Pos2::new(columns.name + 46.0, rect.center().y),
            Align2::LEFT_CENTER,
            &name_text,
            FontId::proportional(13.0),
            self.palette.text(),
        );
        let emblem_x =
            (columns.name + 48.0 + name_text.chars().count() as f32 * 6.9).min(columns.kind - 72.0);
        draw_entry_emblems(
            &painter,
            Pos2::new(emblem_x, rect.center().y),
            &entry,
            self.palette,
        );
        if self.preview_plugin_kind_enabled_for_entry(&entry) && (response.hovered() || selected) {
            draw_inline_preview_badge(
                &painter,
                Rect::from_center_size(
                    Pos2::new(columns.kind - 42.0, rect.center().y),
                    Vec2::new(58.0, 18.0),
                ),
                self.palette,
                false,
            );
        }
        painter.text(
            Pos2::new(columns.kind, rect.center().y),
            Align2::LEFT_CENTER,
            entry.type_label(),
            FontId::proportional(11.0),
            muted,
        );
        painter.text(
            Pos2::new(columns.size, rect.center().y),
            Align2::LEFT_CENTER,
            if entry.kind == EntryKind::Directory {
                "--".to_string()
            } else {
                human_size(entry.size)
            },
            FontId::proportional(11.0),
            muted,
        );
        painter.text(
            Pos2::new(columns.modified, rect.center().y),
            Align2::LEFT_CENTER,
            modified_label(entry.modified),
            FontId::proportional(11.0),
            muted,
        );
        if response.clicked() {
            let modifiers = ui.input(|input| input.modifiers);
            self.select_entry_with_modifiers(entry.path.clone(), modifiers);
        }
        if response.double_clicked() {
            self.open_entry(&entry);
        }
        if response.secondary_clicked() && !selected {
            self.set_single_selection(entry.path.clone());
        }
        response.context_menu(|ui| self.selection_context_menu(ui));
        ui.add_space(2.0);
    }

    fn grid_view(&mut self, ui: &mut egui::Ui, entries: Vec<FileEntry>, phase: f32, now: f64) {
        let available_width = ui.available_width().max(1.0);
        let spacing = 12.0;
        let target_width = 146.0;
        let columns = ((available_width + spacing) / (target_width + spacing))
            .floor()
            .max(1.0) as usize;
        let tile_width = ((available_width - spacing * columns.saturating_sub(1) as f32)
            / columns as f32)
            .clamp(120.0, 172.0);
        let tile_height = 174.0;
        egui::Grid::new("file-grid")
            .num_columns(columns)
            .spacing(Vec2::new(spacing, 12.0))
            .show(ui, |ui| {
                for (index, entry) in entries.into_iter().enumerate() {
                    self.entry_tile(ui, entry, Vec2::new(tile_width, tile_height), phase, now);
                    if (index + 1) % columns == 0 {
                        ui.end_row();
                    }
                }
            });
    }

    fn entry_tile(
        &mut self,
        ui: &mut egui::Ui,
        entry: FileEntry,
        size: Vec2,
        phase: f32,
        now: f64,
    ) {
        let selected = self.selected.contains(&entry.path);
        let focused = self.selection_focus.as_ref() == Some(&entry.path);
        let (rect, response) = ui.allocate_exact_size(size, Sense::click_and_drag());
        let painter = ui.painter_at(rect);
        let accent = self.palette.accent(phase);
        let tile_rect = rect.shrink(2.0);
        if selected || response.hovered() {
            painter.rect_filled(
                tile_rect.translate(egui::vec2(0.0, 2.0)),
                Rounding::same(theme_rounding(self.palette) + 10.0),
                Color32::from_black_alpha(if self.palette.is_light() { 12 } else { 30 }),
            );
        }
        painter.rect_filled(
            tile_rect,
            Rounding::same(theme_rounding(self.palette) + 10.0),
            if selected {
                self.palette.selected_fill()
            } else if response.hovered() {
                self.palette.hover_fill()
            } else {
                Color32::TRANSPARENT
            },
        );
        painter.rect_stroke(
            tile_rect,
            Rounding::same(theme_rounding(self.palette) + 10.0),
            Stroke::new(
                if focused { 1.6 } else { 1.0 },
                if selected {
                    self.palette.accent(phase)
                } else if response.hovered() && self.hover_effects_enabled {
                    accent
                } else if response.hovered() {
                    self.palette.border()
                } else {
                    Color32::TRANSPARENT
                },
            ),
        );

        let media_class = media_class_for_path(
            &entry.path,
            entry.kind == EntryKind::Directory,
            entry.kind == EntryKind::Symlink,
        );
        if response.hovered() {
            self.hover.observe(&entry, media_class, now);
            self.hover_animation_active = self.hover_effects_enabled;
        }
        self.observe_entry_drag_source(&response, &entry);
        let hover_intent = response.hovered()
            && self.hover_effects_enabled
            && self
                .hover
                .intent_ready(&entry.path, now, VIDEO_HOVER_INTENT_SECONDS);
        let icon_rect = Rect::from_center_size(
            Pos2::new(rect.center().x, rect.top() + 54.0),
            Vec2::new((rect.width() - 28.0).min(128.0), 86.0),
        );
        let tile_visible = ui.clip_rect().intersects(rect);
        let modified_ms = thumbnail_modified_ms(entry.modified);
        if tile_visible {
            self.thumbnails
                .request_poster(&entry.path, entry.size, modified_ms);
            if hover_intent {
                self.thumbnails
                    .request_hover_strip(&entry.path, entry.size, modified_ms);
            }
        }
        if let Some(view) = tile_visible
            .then(|| {
                self.thumbnails.thumbnail_for(
                    &entry.path,
                    entry.size,
                    modified_ms,
                    hover_intent,
                    phase,
                )
            })
            .flatten()
        {
            paint_thumbnail(
                &painter,
                icon_rect,
                view,
                response.hovered() && self.hover_effects_enabled,
                accent,
            );
            if media_class == MediaClass::Video {
                draw_video_badge(&painter, icon_rect, view.animated);
            }
        } else {
            draw_file_type_icon(
                &painter,
                icon_rect,
                media_class,
                response.hovered() && self.hover_effects_enabled,
                self.thumbnails.is_loading(&entry.path),
                self.palette,
            );
        }
        draw_entry_emblems(
            &painter,
            tile_rect.left_top() + egui::vec2(8.0, 14.0),
            &entry,
            self.palette,
        );
        if self.preview_plugin_kind_enabled_for_entry(&entry) && (response.hovered() || selected) {
            draw_inline_preview_badge(
                &painter,
                Rect::from_center_size(
                    icon_rect.right_top() + egui::vec2(-10.0, 10.0),
                    Vec2::splat(22.0),
                ),
                self.palette,
                true,
            );
        }
        let name_limit = (((rect.width() - 20.0) / 6.8).floor() as usize).clamp(12, 24);
        painter.text(
            Pos2::new(rect.center().x, rect.top() + 106.0),
            Align2::CENTER_TOP,
            truncate_middle(&entry.name, name_limit),
            FontId::proportional(13.2),
            self.palette.text(),
        );
        painter.text(
            Pos2::new(rect.center().x, rect.top() + 139.0),
            Align2::CENTER_TOP,
            entry.type_label(),
            FontId::proportional(10.5),
            self.palette.muted(),
        );

        if response.clicked() {
            let modifiers = ui.input(|input| input.modifiers);
            self.select_entry_with_modifiers(entry.path.clone(), modifiers);
        }
        if response.double_clicked() {
            self.open_entry(&entry);
        }
        if response.secondary_clicked() && !selected {
            self.set_single_selection(entry.path.clone());
        }
        response.context_menu(|ui| self.selection_context_menu(ui));
    }

    fn selection_context_menu(&mut self, ui: &mut egui::Ui) {
        let selected_count = self.selected.len().max(1);
        let primary = self.primary_selection();
        let title = if selected_count == 1 {
            primary
                .as_ref()
                .map(|entry| entry.name.as_str())
                .unwrap_or("Selection")
        } else {
            "Selection"
        };
        let detail = if selected_count == 1 {
            primary
                .as_ref()
                .map(|entry| entry.type_label())
                .unwrap_or("Item")
                .to_string()
        } else {
            format!("{selected_count} items")
        };
        context_menu_chrome(ui, self.palette, title, &detail);

        context_section(ui, self.palette, "Open");
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Open,
            "Open",
            "Enter",
            true,
            false,
        ) {
            self.open_selection();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Open,
            "Open Externally",
            "Shift+Enter",
            true,
            false,
        ) {
            self.open_selection_externally();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Preview,
            "Preview Inline",
            "Space",
            self.previewable_selection().is_some(),
            false,
        ) {
            self.open_media_preview_plugin();
            ui.close_menu();
        }
        if let Some(entry) = primary.clone() {
            if context_action(
                ui,
                self.palette,
                ChromeIcon::Up,
                if entry.kind == EntryKind::Directory {
                    "Show in Parent"
                } else {
                    "Show in Folder"
                },
                "Parent",
                selected_count == 1 && entry.path.parent().is_some(),
                false,
            ) {
                self.show_entry_in_folder(&entry);
                ui.close_menu();
            }
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Window,
            "Open in New Window",
            "Window",
            true,
            false,
        ) {
            let target = location_target_for_entry(primary.as_ref(), &self.current_dir);
            match launch_new_window(&target) {
                Ok(()) => self.push_log(format!("NEW WINDOW // {}", target.display())),
                Err(error) => self.report_error("Could not open new window", "NEW WINDOW", error),
            }
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Terminal,
            "Open Terminal Here",
            "Shell",
            true,
            false,
        ) {
            let target = location_target_for_entry(primary.as_ref(), &self.current_dir);
            match launch_terminal_at(&target) {
                Ok(()) => self.push_log(format!("TERMINAL // {}", target.display())),
                Err(error) => self.report_error("Could not open terminal", "TERMINAL", error),
            }
            ui.close_menu();
        }
        context_section(ui, self.palette, "Selection");
        if context_action(
            ui,
            self.palette,
            ChromeIcon::List,
            "Invert Selection",
            "Visible",
            true,
            false,
        ) {
            self.invert_visible_selection();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::FolderPlus,
            "Select Folders",
            "Visible",
            true,
            false,
        ) {
            self.select_visible_kind(true);
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::FilePlus,
            "Select Files",
            "Visible",
            true,
            false,
        ) {
            self.select_visible_kind(false);
            ui.close_menu();
        }
        let selected_folder_count = self
            .selected_entries()
            .iter()
            .filter(|entry| entry.kind == EntryKind::Directory)
            .count();
        if selected_folder_count > 0 {
            context_section(ui, self.palette, "Pinning");
            if selected_folder_count == 1 {
                let target = self
                    .selected_entries()
                    .into_iter()
                    .find(|entry| entry.kind == EntryKind::Directory)
                    .map(|entry| entry.path);
                if let Some(target) = target {
                    if self.is_bookmarked(&target) {
                        if context_action(
                            ui,
                            self.palette,
                            ChromeIcon::Pin,
                            "Unpin Folder",
                            "Pinned",
                            true,
                            false,
                        ) {
                            self.remove_bookmark(&target);
                            ui.close_menu();
                        }
                    } else if context_action(
                        ui,
                        self.palette,
                        ChromeIcon::Pin,
                        "Pin Folder",
                        "Quick Access",
                        true,
                        false,
                    ) {
                        self.add_bookmark(target);
                        ui.close_menu();
                    }
                }
            } else if context_action(
                ui,
                self.palette,
                ChromeIcon::Pin,
                "Pin Selected Folders",
                "Quick Access",
                true,
                false,
            ) {
                self.pin_selected_folders();
                ui.close_menu();
            }
        }
        context_divider(ui, self.palette);
        context_section(ui, self.palette, "Edit");
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Copy,
            "Copy",
            "Ctrl+C",
            true,
            false,
        ) {
            self.copy_selection(ClipboardMode::Copy);
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Copy,
            if selected_count == 1 {
                "Copy Path"
            } else {
                "Copy Paths"
            },
            "Path",
            true,
            false,
        ) {
            self.copy_selection_text(ui.ctx(), false);
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Copy,
            if selected_count == 1 {
                "Copy Name"
            } else {
                "Copy Names"
            },
            "Name",
            true,
            false,
        ) {
            self.copy_selection_text(ui.ctx(), true);
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Cut,
            "Cut",
            "Ctrl+X",
            true,
            false,
        ) {
            self.copy_selection(ClipboardMode::Cut);
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Paste,
            "Paste Into This Folder",
            "Ctrl+V",
            self.clipboard.is_some(),
            false,
        ) {
            self.paste_clipboard();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::FilePlus,
            "New File",
            "Create",
            true,
            false,
        ) {
            self.open_new_file_dialog();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::FolderPlus,
            "New Folder",
            "Ctrl+N",
            true,
            false,
        ) {
            self.open_new_folder_dialog();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Duplicate,
            "Duplicate",
            "Ctrl+D",
            true,
            false,
        ) {
            self.duplicate_selection();
            ui.close_menu();
        }
        context_divider(ui, self.palette);
        context_section(ui, self.palette, "Danger Zone");
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Rename,
            "Rename",
            "F2",
            selected_count == 1,
            false,
        ) {
            self.open_rename_dialog();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Trash,
            "Move to Trash",
            "Delete",
            true,
            false,
        ) {
            self.trash_selection();
            ui.close_menu();
        }
        if context_action(
            ui,
            self.palette,
            ChromeIcon::Trash,
            "Delete Forever...",
            "Shift+Del",
            true,
            true,
        ) {
            self.request_delete_selection(true);
            ui.close_menu();
        }
    }

    fn properties_panel(&mut self, ui: &mut egui::Ui, phase: f32) {
        let selected = self.selected_entries();
        let (title, detail) = if selected.is_empty() {
            (
                folder_title(&self.current_dir),
                path_for_display(&self.current_dir),
            )
        } else if selected.len() == 1 {
            (
                selected[0].name.clone(),
                selected[0].type_label().to_string(),
            )
        } else {
            ("Selection".to_string(), format!("{} items", selected.len()))
        };
        overlay_header(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Properties,
            &title,
            &detail,
        );
        ui.add_space(10.0);

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Properties,
            "Summary",
            |ui| {
                self.properties_summary(ui, &selected);
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Properties,
            "Metadata",
            |ui| {
                if selected.is_empty() {
                    self.directory_properties(ui);
                } else if selected.len() == 1 {
                    self.single_properties(ui, &selected[0]);
                } else {
                    self.multi_properties(ui, &selected);
                }
            },
        );

        overlay_card(
            ui,
            self.palette,
            self.theme_opacity,
            ChromeIcon::Open,
            "Actions",
            |ui| {
                self.action_buttons(ui);
            },
        );

        if selected.is_empty() {
            ui.add_space(2.0);
        }
        if let Some(progress) = &self.operation_progress {
            let title = pretty_value(self.operation_title.as_deref().unwrap_or("Operation"));
            overlay_card(
                ui,
                self.palette,
                self.theme_opacity,
                ChromeIcon::Refresh,
                "Activity",
                |ui| {
                    progress_card(ui, &title, progress, phase, self.palette);
                },
            );
        }
    }

    fn properties_summary(&self, ui: &mut egui::Ui, selected: &[FileEntry]) {
        if selected.is_empty() {
            let detail = self
                .snapshot
                .as_ref()
                .map(|snapshot| {
                    format!(
                        "{} folders · {} files · {}",
                        snapshot.directory_count,
                        snapshot.file_count,
                        human_size(snapshot.total_size)
                    )
                })
                .unwrap_or_else(|| "Current folder".to_string());
            summary_row(
                ui,
                self.palette,
                ChromeIcon::FolderPlus,
                &folder_title(&self.current_dir),
                &detail,
                &path_for_display(&self.current_dir),
            );
        } else if selected.len() == 1 {
            let entry = &selected[0];
            let detail = if entry.kind == EntryKind::Directory {
                format!("{} · folder size not scanned", entry.type_label())
            } else {
                format!("{} · {}", entry.type_label(), human_size(entry.size))
            };
            summary_row(
                ui,
                self.palette,
                ChromeIcon::Properties,
                &entry.name,
                &detail,
                &path_for_display(&entry.path),
            );
        } else {
            let summary = selection_summary(selected);
            summary_row(
                ui,
                self.palette,
                ChromeIcon::List,
                &format!("{} selected", summary.count),
                &format!(
                    "{} files · {} folders · {} known",
                    summary.files,
                    summary.folders,
                    human_size(summary.file_bytes)
                ),
                "Folder sizes are not scanned recursively",
            );
        }
    }

    fn directory_properties(&self, ui: &mut egui::Ui) {
        property_section_title(ui, "Folder Details");
        property_line(ui, "Location", &path_for_display(&self.current_dir));
        if let Some(snapshot) = &self.snapshot {
            property_section_title(ui, "Contents");
            property_line(ui, "Folders", &snapshot.directory_count.to_string());
            property_line(ui, "Files", &snapshot.file_count.to_string());
            property_line(ui, "Visible", &snapshot.entries.len().to_string());
            property_line(ui, "Hidden", &snapshot.hidden_entries.to_string());
            property_line(ui, "Size", &human_size(snapshot.total_size));
            property_line(
                ui,
                "Scan limit",
                if snapshot.scan_limited {
                    "Reached"
                } else {
                    "Open"
                },
            );
            if snapshot.scan_limited {
                property_line(ui, "Scanned", &snapshot.total_entries.to_string());
                property_line(ui, "Entry cap", &snapshot.entry_limit.to_string());
            }
            if snapshot.error_limited {
                property_line(ui, "Warning cap", &snapshot.error_limit.to_string());
            }
        }
    }

    fn single_properties(&self, ui: &mut egui::Ui, entry: &FileEntry) {
        property_section_title(ui, "Item Details");
        property_line(ui, "Name", &entry.name);
        property_line(ui, "Path", &path_for_display(&entry.path));
        property_line(ui, "Type", entry.type_label());
        property_line(ui, "Size", &human_size(entry.size));
        property_section_title(ui, "Metadata");
        property_line(ui, "Modified", &absolute_time_label(entry.modified));
        property_line(ui, "Age", &modified_label(entry.modified));
        property_line(ui, "Permissions", &entry.permissions);
        property_line(ui, "Read only", if entry.readonly { "Yes" } else { "No" });
        if let Some(target) = &entry.symlink_target {
            property_line(ui, "Link target", &target.display().to_string());
        }
    }

    fn multi_properties(&self, ui: &mut egui::Ui, entries: &[FileEntry]) {
        let summary = selection_summary(entries);
        property_section_title(ui, "Selection Details");
        property_line(ui, "Selected", &summary.count.to_string());
        property_line(ui, "Folders", &summary.folders.to_string());
        property_line(ui, "Files", &summary.files.to_string());
        property_line(ui, "Known file size", &human_size(summary.file_bytes));
        if summary.folders > 0 {
            property_line(ui, "Folder size", "Not scanned recursively");
        }
    }

    fn action_buttons(&mut self, ui: &mut egui::Ui) {
        ui.horizontal_wrapped(|ui| {
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Open,
                Some("Open"),
                false,
                true,
            )
            .clicked()
            {
                self.open_selection();
            }
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Copy,
                Some("Copy"),
                false,
                true,
            )
            .clicked()
            {
                self.copy_selection(ClipboardMode::Copy);
            }
            if icon_button(ui, self.palette, ChromeIcon::Cut, Some("Cut"), false, true).clicked() {
                self.copy_selection(ClipboardMode::Cut);
            }
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Paste,
                Some("Paste"),
                false,
                self.clipboard.is_some(),
            )
            .clicked()
            {
                self.paste_clipboard();
            }
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::FilePlus,
                Some("New File"),
                false,
                true,
            )
            .clicked()
            {
                self.open_new_file_dialog();
            }
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Rename,
                Some("Rename"),
                false,
                self.selected.len() == 1,
            )
            .clicked()
            {
                self.open_rename_dialog();
            }
            if icon_button(
                ui,
                self.palette,
                ChromeIcon::Trash,
                Some("Trash"),
                false,
                !self.selected.is_empty(),
            )
            .clicked()
            {
                self.trash_selection();
            }
        });
    }

    fn dialogs(&mut self, ctx: &egui::Context, phase: f32) {
        if !self.dialogs.is_open() {
            return;
        }

        modal_backdrop(ctx, self.palette);
        if self.dialogs.error.is_some() {
            self.error_dialog(ctx);
        } else if self.dialogs.delete_confirm.is_some() {
            self.delete_dialog(ctx, phase);
        } else if self.dialogs.rename_open {
            self.rename_dialog(ctx, phase);
        } else if self.dialogs.new_file_open {
            self.new_file_dialog(ctx, phase);
        } else {
            self.new_folder_dialog(ctx, phase);
        }
    }

    fn new_folder_dialog(&mut self, ctx: &egui::Context, _phase: f32) {
        if !self.dialogs.new_folder_open {
            return;
        }
        let mut open = self.dialogs.new_folder_open;
        let mut create_clicked = false;
        let mut cancel_clicked = false;
        egui::Window::new("New Folder")
            .id(egui::Id::new("new-folder-dialog"))
            .order(egui::Order::Foreground)
            .open(&mut open)
            .collapsible(false)
            .resizable(false)
            .default_width(390.0)
            .min_width(340.0)
            .frame(window_frame(self.palette, self.theme_opacity))
            .show(ctx, |ui| {
                overlay_header(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::FolderPlus,
                    "New Folder",
                    &path_for_display(&self.current_dir),
                );
                ui.add_space(10.0);
                overlay_card(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::FolderPlus,
                    "Folder Name",
                    |ui| {
                        field_label(ui, "Name");
                        let response = ui.add(
                            egui::TextEdit::singleline(&mut self.dialogs.new_folder_name)
                                .id_salt("new-folder-name")
                                .desired_width(ui.available_width()),
                        );
                        if self.dialogs.new_folder_focus_requested {
                            let selection_end = self.dialogs.new_folder_name.chars().count();
                            focus_text_edit(ui, &response, selection_end);
                            self.dialogs.new_folder_focus_requested = false;
                        }
                        let validation = validate_child_name(
                            &self.current_dir,
                            &self.dialogs.new_folder_name,
                            true,
                            None,
                        );
                        validation_status(ui, self.palette, &validation, "Ready to create folder");
                        ui.add_space(10.0);
                        ui.horizontal_wrapped(|ui| {
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::FolderPlus,
                                Some("Create"),
                                validation.accepted,
                                validation.accepted,
                            )
                            .clicked()
                                || (response.lost_focus()
                                    && ui.input(|input| input.key_pressed(egui::Key::Enter))
                                    && validation.accepted)
                            {
                                create_clicked = true;
                            }
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Back,
                                Some("Cancel"),
                                false,
                                true,
                            )
                            .clicked()
                            {
                                cancel_clicked = true;
                            }
                        });
                    },
                );
            });
        if ctx.input(|input| input.key_pressed(egui::Key::Escape)) {
            cancel_clicked = true;
            create_clicked = false;
        }
        self.dialogs.new_folder_open = open && !cancel_clicked;
        if create_clicked {
            match create_folder(&self.current_dir, &self.dialogs.new_folder_name) {
                Ok(path) => {
                    self.push_undo(undo_for_created_folder(path.clone()));
                    self.set_single_selection(path.clone());
                    self.push_log(format!("NEW FOLDER // {}", path.display()));
                    self.dialogs.new_folder_open = false;
                    self.refresh_current_dir();
                }
                Err(error) => self.dialogs.error = Some(error),
            }
        }
    }

    fn new_file_dialog(&mut self, ctx: &egui::Context, _phase: f32) {
        if !self.dialogs.new_file_open {
            return;
        }
        let mut open = self.dialogs.new_file_open;
        let mut create_clicked = false;
        let mut cancel_clicked = false;
        egui::Window::new("New File")
            .id(egui::Id::new("new-file-dialog"))
            .order(egui::Order::Foreground)
            .open(&mut open)
            .collapsible(false)
            .resizable(false)
            .default_width(390.0)
            .min_width(340.0)
            .frame(window_frame(self.palette, self.theme_opacity))
            .show(ctx, |ui| {
                overlay_header(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::FilePlus,
                    "New File",
                    &path_for_display(&self.current_dir),
                );
                ui.add_space(10.0);
                overlay_card(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::FilePlus,
                    "File Name",
                    |ui| {
                        field_label(ui, "Name");
                        let response = ui.add(
                            egui::TextEdit::singleline(&mut self.dialogs.new_file_name)
                                .id_salt("new-file-name")
                                .desired_width(ui.available_width()),
                        );
                        if self.dialogs.new_file_focus_requested {
                            let selection_end =
                                rename_selection_end(&self.dialogs.new_file_name, false);
                            focus_text_edit(ui, &response, selection_end);
                            self.dialogs.new_file_focus_requested = false;
                        }
                        let validation = validate_child_name(
                            &self.current_dir,
                            &self.dialogs.new_file_name,
                            false,
                            None,
                        );
                        validation_status(ui, self.palette, &validation, "Ready to create file");
                        ui.add_space(10.0);
                        ui.horizontal_wrapped(|ui| {
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::FilePlus,
                                Some("Create"),
                                validation.accepted,
                                validation.accepted,
                            )
                            .clicked()
                                || (response.lost_focus()
                                    && ui.input(|input| input.key_pressed(egui::Key::Enter))
                                    && validation.accepted)
                            {
                                create_clicked = true;
                            }
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Back,
                                Some("Cancel"),
                                false,
                                true,
                            )
                            .clicked()
                            {
                                cancel_clicked = true;
                            }
                        });
                    },
                );
            });
        if ctx.input(|input| input.key_pressed(egui::Key::Escape)) {
            cancel_clicked = true;
            create_clicked = false;
        }
        self.dialogs.new_file_open = open && !cancel_clicked;
        if create_clicked {
            match create_file(&self.current_dir, &self.dialogs.new_file_name) {
                Ok(path) => {
                    self.push_undo(undo_for_created_file(path.clone()));
                    self.set_single_selection(path.clone());
                    self.push_log(format!("NEW FILE // {}", path.display()));
                    self.dialogs.new_file_open = false;
                    self.refresh_current_dir();
                }
                Err(error) => self.dialogs.error = Some(error),
            }
        }
    }

    fn rename_dialog(&mut self, ctx: &egui::Context, _phase: f32) {
        if !self.dialogs.rename_open {
            return;
        }
        let Some(path) = self.dialogs.rename_path.clone() else {
            self.dialogs.rename_open = false;
            return;
        };
        let parent = path.parent().unwrap_or(self.current_dir.as_path());
        let original_name = path.file_name().and_then(|name| name.to_str());
        let target_is_folder = path.is_dir();
        let mut open = self.dialogs.rename_open;
        let mut rename_clicked = false;
        let mut cancel_clicked = false;
        egui::Window::new("Rename")
            .id(egui::Id::new("rename-dialog"))
            .order(egui::Order::Foreground)
            .open(&mut open)
            .collapsible(false)
            .resizable(false)
            .default_width(420.0)
            .min_width(360.0)
            .frame(window_frame(self.palette, self.theme_opacity))
            .show(ctx, |ui| {
                overlay_header(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::Rename,
                    "Rename",
                    original_name.unwrap_or("Selected item"),
                );
                ui.add_space(10.0);
                overlay_card(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::Rename,
                    "Name",
                    |ui| {
                        property_line(ui, "Current", &path_for_display(&path));
                        field_label(ui, "New name");
                        let response = ui.add(
                            egui::TextEdit::singleline(&mut self.dialogs.rename_name)
                                .id_salt("rename-name")
                                .desired_width(ui.available_width()),
                        );
                        if self.dialogs.rename_focus_requested {
                            let selection_end =
                                rename_selection_end(&self.dialogs.rename_name, target_is_folder);
                            focus_text_edit(ui, &response, selection_end);
                            self.dialogs.rename_focus_requested = false;
                        }
                        let validation = validate_child_name(
                            parent,
                            &self.dialogs.rename_name,
                            target_is_folder,
                            original_name,
                        );
                        validation_status(ui, self.palette, &validation, "Ready to rename item");
                        ui.add_space(10.0);
                        ui.horizontal_wrapped(|ui| {
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Rename,
                                Some("Rename"),
                                validation.accepted,
                                validation.accepted,
                            )
                            .clicked()
                                || (response.lost_focus()
                                    && ui.input(|input| input.key_pressed(egui::Key::Enter))
                                    && validation.accepted)
                            {
                                rename_clicked = true;
                            }
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Back,
                                Some("Cancel"),
                                false,
                                true,
                            )
                            .clicked()
                            {
                                cancel_clicked = true;
                            }
                        });
                    },
                );
            });
        if ctx.input(|input| input.key_pressed(egui::Key::Escape)) {
            cancel_clicked = true;
            rename_clicked = false;
        }
        self.dialogs.rename_open = open && !cancel_clicked;
        if rename_clicked {
            match rename_path(&path, &self.dialogs.rename_name) {
                Ok(target) => {
                    if target != path {
                        self.push_undo(undo_for_rename(path.clone(), target.clone()));
                    }
                    self.set_single_selection(target.clone());
                    self.push_log(format!(
                        "RENAME // {} -> {}",
                        path.display(),
                        target.display()
                    ));
                    self.dialogs.rename_open = false;
                    self.dialogs.rename_path = None;
                    self.refresh_current_dir();
                }
                Err(error) => self.dialogs.error = Some(error),
            }
        }
    }

    fn delete_dialog(&mut self, ctx: &egui::Context, _phase: f32) {
        let Some(intent) = self.dialogs.delete_confirm.clone() else {
            return;
        };
        let title = if intent.permanent {
            "Delete Forever"
        } else {
            "Move to Trash"
        };
        let mut open = true;
        let mut confirm_clicked = false;
        let mut cancel_clicked = false;
        egui::Window::new(title)
            .id(egui::Id::new("delete-dialog"))
            .order(egui::Order::Foreground)
            .open(&mut open)
            .collapsible(false)
            .resizable(false)
            .default_width(440.0)
            .min_width(360.0)
            .frame(window_frame(self.palette, self.theme_opacity))
            .show(ctx, |ui| {
                overlay_header(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::Trash,
                    title,
                    &format!("{} selected items", intent.paths.len()),
                );
                ui.add_space(10.0);
                overlay_card(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::Trash,
                    "Confirmation",
                    |ui| {
                        safety_notice(
                            ui,
                            self.palette,
                            ChromeIcon::Trash,
                            if intent.permanent {
                                "Permanent Delete"
                            } else {
                                "Move to Trash"
                            },
                            if intent.permanent {
                                "This cannot be undone. Review the selected items before continuing."
                            } else {
                                "Items are moved to the local trash and can be restored with undo."
                            },
                            intent.permanent,
                        );
                        ui.add_space(8.0);
                        property_line(ui, "Items", &intent.paths.len().to_string());
                        property_line(
                            ui,
                            "Mode",
                            if intent.permanent {
                                "Permanent delete"
                            } else {
                                "Move to trash"
                            },
                        );
                        ui.add_space(8.0);
                        ui.horizontal_wrapped(|ui| {
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Trash,
                                Some(if intent.permanent {
                                    "Delete Forever"
                                } else {
                                    "Move to Trash"
                                }),
                                true,
                                true,
                            )
                            .clicked()
                            {
                                confirm_clicked = true;
                            }
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Back,
                                Some("Cancel"),
                                false,
                                true,
                            )
                            .clicked()
                            {
                                cancel_clicked = true;
                            }
                        });
                    },
                );
                overlay_card(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::List,
                    "Selected Items",
                    |ui| {
                        egui::ScrollArea::vertical()
                            .max_height(130.0)
                            .show(ui, |ui| {
                                for path in intent.paths.iter().take(40) {
                                    muted_label(ui, path_for_display(path));
                                }
                                if intent.paths.len() > 40 {
                                    muted_label(
                                        ui,
                                        format!("... {} more", intent.paths.len() - 40),
                                    );
                                }
                            });
                    },
                );
            });
        let escape_pressed = ctx.input(|input| input.key_pressed(egui::Key::Escape));
        let enter_pressed = ctx.input(|input| input.key_pressed(egui::Key::Enter));
        if !open || cancel_clicked || escape_pressed {
            self.dialogs.delete_confirm = None;
        } else if confirm_clicked || (enter_pressed && !intent.permanent) {
            let request = if intent.permanent {
                OperationRequest::Delete {
                    sources: intent.paths,
                }
            } else {
                OperationRequest::Trash {
                    sources: intent.paths,
                }
            };
            self.dialogs.delete_confirm = None;
            self.start_operation(request, false);
        }
    }

    fn error_dialog(&mut self, ctx: &egui::Context) {
        let Some(message) = self.dialogs.error.clone() else {
            return;
        };
        let mut open = true;
        let mut close_clicked = false;
        egui::Window::new("Action Failed")
            .id(egui::Id::new("error-dialog"))
            .order(egui::Order::Foreground)
            .open(&mut open)
            .collapsible(false)
            .resizable(false)
            .default_width(410.0)
            .min_width(340.0)
            .frame(window_frame(self.palette, self.theme_opacity))
            .show(ctx, |ui| {
                overlay_header(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::Properties,
                    "Action Failed",
                    "The requested action could not be completed",
                );
                ui.add_space(10.0);
                overlay_card(
                    ui,
                    self.palette,
                    self.theme_opacity,
                    ChromeIcon::Properties,
                    "Message",
                    |ui| {
                        safety_notice(
                            ui,
                            self.palette,
                            ChromeIcon::Properties,
                            "Action Failed",
                            &message,
                            true,
                        );
                        ui.add_space(10.0);
                        ui.horizontal_wrapped(|ui| {
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Copy,
                                Some("Copy Details"),
                                false,
                                true,
                            )
                            .clicked()
                            {
                                ui.ctx().copy_text(message.clone());
                                self.status = "Error details copied".to_string();
                            }
                            if icon_button(
                                ui,
                                self.palette,
                                ChromeIcon::Back,
                                Some("Close"),
                                true,
                                true,
                            )
                            .clicked()
                            {
                                close_clicked = true;
                            }
                        });
                    },
                );
            });
        let close_pressed = ctx.input(|input| {
            input.key_pressed(egui::Key::Escape) || input.key_pressed(egui::Key::Enter)
        });
        if !open || close_clicked || close_pressed {
            self.dialogs.error = None;
        }
    }
}

#[derive(Clone, Copy)]
struct ListColumns {
    name: f32,
    kind: f32,
    size: f32,
    modified: f32,
}

#[derive(Clone, Copy)]
enum ChromeIcon {
    Sidebar,
    Back,
    Forward,
    Up,
    Home,
    Search,
    Open,
    Preview,
    Window,
    Terminal,
    Refresh,
    FolderPlus,
    FilePlus,
    Cut,
    Copy,
    Paste,
    Trash,
    Pin,
    Rename,
    Duplicate,
    Sort,
    Hidden,
    Link,
    Lock,
    List,
    Grid,
    Properties,
    Settings,
}

fn list_columns(rect: Rect) -> ListColumns {
    let width = rect.width();
    ListColumns {
        name: rect.left() + 12.0,
        kind: rect.left() + width * 0.58,
        size: rect.left() + width * 0.72,
        modified: rect.left() + width * 0.84,
    }
}

fn draw_list_column_rules(
    painter: &egui::Painter,
    rect: Rect,
    columns: ListColumns,
    theme: AppPalette,
) {
    for x in [
        columns.kind - 14.0,
        columns.size - 14.0,
        columns.modified - 14.0,
    ] {
        painter.line_segment(
            [
                Pos2::new(x, rect.top() + 5.0),
                Pos2::new(x, rect.bottom() - 5.0),
            ],
            Stroke::new(1.0, theme.hairline()),
        );
    }
}

fn draw_list_row_rules(
    painter: &egui::Painter,
    rect: Rect,
    _columns: ListColumns,
    theme: AppPalette,
) {
    painter.line_segment(
        [
            Pos2::new(rect.left() + 10.0, rect.bottom() - 1.0),
            Pos2::new(rect.right() - 10.0, rect.bottom() - 1.0),
        ],
        Stroke::new(
            1.0,
            Color32::from_rgba_unmultiplied(
                theme.hairline().r(),
                theme.hairline().g(),
                theme.hairline().b(),
                52,
            ),
        ),
    );
}

fn draw_sort_indicator(painter: &egui::Painter, center: Pos2, descending: bool, color: Color32) {
    let half_width = 3.5;
    let half_height = 3.0;
    let points = if descending {
        vec![
            center + egui::vec2(-half_width, -half_height),
            center + egui::vec2(half_width, -half_height),
            center + egui::vec2(0.0, half_height),
        ]
    } else {
        vec![
            center + egui::vec2(-half_width, half_height),
            center + egui::vec2(half_width, half_height),
            center + egui::vec2(0.0, -half_height),
        ]
    };
    painter.add(egui::Shape::convex_polygon(points, color, Stroke::NONE));
}

fn draw_drop_outline(painter: &egui::Painter, rect: Rect, theme: AppPalette, label: &str) {
    let rounding = Rounding::same(theme_rounding(theme) + 11.0);
    painter.rect_stroke(rect, rounding, Stroke::new(1.6, theme.accent(0.0)));
    painter.rect_filled(
        rect.shrink(3.0),
        Rounding::same(theme_rounding(theme) + 9.0),
        Color32::from_rgba_unmultiplied(
            theme.accent(0.0).r(),
            theme.accent(0.0).g(),
            theme.accent(0.0).b(),
            if theme.is_light() { 16 } else { 30 },
        ),
    );
    let label_rect = Rect::from_center_size(
        rect.center_top() + egui::vec2(0.0, 23.0),
        Vec2::new(148.0, 24.0),
    );
    painter.rect_filled(label_rect, Rounding::same(999.0), theme.elevated_fill());
    painter.rect_stroke(
        label_rect,
        Rounding::same(999.0),
        Stroke::new(1.0, theme.accent(0.0)),
    );
    painter.text(
        label_rect.center(),
        Align2::CENTER_CENTER,
        label,
        FontId::proportional(11.0),
        theme.accent(0.0),
    );
}

fn draw_preview_canvas(painter: &egui::Painter, rect: Rect, theme: AppPalette) -> Rect {
    let canvas = rect.shrink(2.0);
    let rounding = Rounding::same(theme_rounding(theme) + 8.0);
    painter.rect_filled(canvas, rounding, theme.recessed_fill());
    painter.rect_stroke(canvas, rounding, Stroke::new(1.0, theme.hairline()));
    painter.rect_filled(
        Rect::from_min_max(
            canvas.left_top() + egui::vec2(1.0, 1.0),
            canvas.right_top() + egui::vec2(-1.0, 3.0),
        ),
        Rounding::same(theme_rounding(theme) + 6.0),
        Color32::from_rgba_unmultiplied(255, 255, 255, if theme.is_light() { 96 } else { 22 }),
    );
    canvas
}

fn draw_entry_emblems(painter: &egui::Painter, start: Pos2, entry: &FileEntry, theme: AppPalette) {
    let mut x = start.x;
    for icon in [
        (entry.kind == EntryKind::Symlink).then_some(ChromeIcon::Link),
        entry.readonly.then_some(ChromeIcon::Lock),
        entry.hidden.then_some(ChromeIcon::Hidden),
    ]
    .into_iter()
    .flatten()
    {
        let icon_rect = Rect::from_center_size(Pos2::new(x + 7.0, start.y), Vec2::splat(13.0));
        draw_chrome_icon(painter, icon_rect.shrink(1.0), icon, theme.muted());
        x += 15.0;
    }
}

fn draw_folder_summary_icon(painter: &egui::Painter, rect: Rect) {
    draw_folder_icon(painter, rect.shrink(3.0), class_accent(MediaClass::Folder));
}

fn draw_place_icon(
    painter: &egui::Painter,
    rect: Rect,
    place: &Place,
    pinned: bool,
    active: bool,
    theme: AppPalette,
) {
    let base = if active || pinned {
        theme.accent(0.0)
    } else if place.path.is_dir() {
        theme.secondary_text()
    } else {
        theme.muted()
    };
    if active || pinned {
        let glow = Rect::from_center_size(
            rect.center_bottom() + egui::vec2(0.0, -2.0),
            Vec2::new(rect.width() * 0.86, 4.0),
        );
        painter.rect_filled(
            glow,
            Rounding::same(999.0),
            Color32::from_rgba_unmultiplied(base.r(), base.g(), base.b(), 48),
        );
    }

    match place.label.as_str() {
        "Home" => draw_home_place_icon(painter, rect.shrink(3.0), base),
        "Trash" => draw_trash_place_icon(painter, rect.shrink(3.0), base),
        "Current" => draw_current_place_icon(painter, rect.shrink(3.0), base),
        "Filesystem" => {
            painter.text(
                rect.center(),
                Align2::CENTER_CENTER,
                "/",
                FontId::monospace(12.0),
                base,
            );
        }
        _ => draw_folder_place_icon(painter, rect.shrink(2.5), base),
    }

    if pinned {
        let pin_rect = Rect::from_center_size(
            rect.right_bottom() + egui::vec2(-1.0, -1.0),
            Vec2::splat(8.5),
        );
        draw_chrome_icon(painter, pin_rect.shrink(1.4), ChromeIcon::Pin, base);
    }
}

fn draw_folder_place_icon(painter: &egui::Painter, rect: Rect, color: Color32) {
    let body = Rect::from_min_max(
        rect.left_top() + egui::vec2(0.5, rect.height() * 0.32),
        rect.right_bottom() - egui::vec2(0.5, 0.5),
    );
    let tab = Rect::from_min_size(
        body.left_top() + egui::vec2(1.0, -3.8),
        Vec2::new(body.width() * 0.48, 5.2),
    );
    painter.rect_filled(tab, Rounding::same(1.5), lift(color, 0.20));
    painter.rect_filled(body, Rounding::same(2.0), color);
    painter.line_segment(
        [
            body.left_top() + egui::vec2(2.0, 1.5),
            body.right_top() + egui::vec2(-2.0, 1.5),
        ],
        Stroke::new(0.8, Color32::from_white_alpha(92)),
    );
    painter.rect_stroke(
        body,
        Rounding::same(2.0),
        Stroke::new(0.8, shade(color, 0.18)),
    );
}

fn draw_home_place_icon(painter: &egui::Painter, rect: Rect, color: Color32) {
    let roof = vec![
        Pos2::new(rect.left(), rect.center().y),
        Pos2::new(rect.center().x, rect.top()),
        Pos2::new(rect.right(), rect.center().y),
    ];
    painter.add(egui::Shape::convex_polygon(roof, color, Stroke::NONE));
    painter.rect_filled(
        Rect::from_min_max(
            Pos2::new(rect.left() + 1.5, rect.center().y - 0.5),
            Pos2::new(rect.right() - 1.5, rect.bottom()),
        ),
        Rounding::same(1.5),
        color,
    );
}

fn draw_trash_place_icon(painter: &egui::Painter, rect: Rect, color: Color32) {
    painter.line_segment(
        [
            Pos2::new(rect.left() + 1.0, rect.top() + 2.0),
            Pos2::new(rect.right() - 1.0, rect.top() + 2.0),
        ],
        Stroke::new(1.4, color),
    );
    let bin = Rect::from_min_max(
        Pos2::new(rect.left() + 2.0, rect.top() + 4.0),
        Pos2::new(rect.right() - 2.0, rect.bottom()),
    );
    painter.rect_stroke(bin, Rounding::same(1.2), Stroke::new(1.2, color));
    painter.line_segment(
        [
            Pos2::new(bin.center().x, bin.top() + 1.5),
            Pos2::new(bin.center().x, bin.bottom() - 1.5),
        ],
        Stroke::new(1.0, color),
    );
}

fn draw_current_place_icon(painter: &egui::Painter, rect: Rect, color: Color32) {
    painter.circle_stroke(
        rect.center(),
        rect.width().min(rect.height()) * 0.42,
        Stroke::new(1.3, color),
    );
    painter.circle_filled(rect.center(), 2.2, color);
}

fn draw_inline_preview_badge(
    painter: &egui::Painter,
    rect: Rect,
    theme: AppPalette,
    compact: bool,
) {
    let rounding = Rounding::same(999.0);
    icon_shadow(painter, rect, 4.0, if compact { 22 } else { 18 });
    painter.rect_filled(rect, rounding, theme.selected_fill());
    painter.rect_stroke(rect, rounding, Stroke::new(1.0, theme.accent(0.0)));
    let icon_rect = Rect::from_center_size(
        if compact {
            rect.center()
        } else {
            rect.left_center() + egui::vec2(10.0, 0.0)
        },
        Vec2::splat(if compact { 12.0 } else { 10.5 }),
    );
    draw_chrome_icon(painter, icon_rect, ChromeIcon::Preview, theme.accent(0.0));
    if !compact {
        painter.text(
            rect.left_center() + egui::vec2(20.0, 0.0),
            Align2::LEFT_CENTER,
            "Inline",
            FontId::proportional(9.5),
            theme.secondary_text(),
        );
    }
}

fn media_class_for_entry(entry: &FileEntry) -> MediaClass {
    media_class_for_path(
        &entry.path,
        entry.kind == EntryKind::Directory,
        entry.kind == EntryKind::Symlink,
    )
}

fn preview_plugin_kind_for_entry(entry: &FileEntry) -> Option<PreviewPluginKind> {
    match media_class_for_entry(entry) {
        MediaClass::Image | MediaClass::Video => Some(PreviewPluginKind::Media),
        MediaClass::Code | MediaClass::Text => Some(PreviewPluginKind::Text),
        _ => None,
    }
}

fn selection_summary(entries: &[FileEntry]) -> SelectionSummary {
    let mut summary = SelectionSummary {
        count: entries.len(),
        ..SelectionSummary::default()
    };
    for entry in entries {
        if entry.kind == EntryKind::Directory {
            summary.folders += 1;
        } else {
            summary.files += 1;
            summary.file_bytes = summary.file_bytes.saturating_add(entry.size);
        }
    }
    summary
}

fn operation_results_in_directory(paths: &[PathBuf], directory: &Path) -> Vec<PathBuf> {
    paths
        .iter()
        .filter(|path| path.parent() == Some(directory))
        .cloned()
        .collect()
}

fn stepped_selection_index(
    entry_count: usize,
    current_index: Option<usize>,
    direction: isize,
) -> Option<usize> {
    if entry_count == 0 {
        return None;
    }
    Some(match current_index {
        Some(current) => (current as isize + direction).clamp(0, entry_count as isize - 1) as usize,
        None if direction < 0 => entry_count - 1,
        None => 0,
    })
}

fn location_target_for_entry(entry: Option<&FileEntry>, fallback: &Path) -> PathBuf {
    let Some(entry) = entry else {
        return fallback.to_path_buf();
    };
    if entry.kind == EntryKind::Directory {
        entry.path.clone()
    } else {
        entry
            .path
            .parent()
            .map(Path::to_path_buf)
            .unwrap_or_else(|| fallback.to_path_buf())
    }
}

fn fit_rect_to_aspect(rect: Rect, aspect: f32) -> Rect {
    let aspect = aspect.max(0.05);
    let rect_aspect = rect.width() / rect.height().max(1.0);
    if rect_aspect > aspect {
        Rect::from_center_size(
            rect.center(),
            Vec2::new(rect.height() * aspect, rect.height()),
        )
    } else {
        Rect::from_center_size(
            rect.center(),
            Vec2::new(rect.width(), rect.width() / aspect),
        )
    }
}

fn zoom_rect(rect: Rect, zoom: f32) -> Rect {
    Rect::from_center_size(rect.center(), rect.size() * zoom.clamp(0.5, 2.5))
}

fn preview_zoom_percent(zoom: f32) -> u16 {
    (zoom.clamp(0.5, 2.5) * 100.0).round() as u16
}

fn modal_backdrop(ctx: &egui::Context, theme: AppPalette) {
    let screen = ctx.screen_rect();
    egui::Area::new(egui::Id::new("modal-backdrop"))
        .order(egui::Order::Middle)
        .fixed_pos(screen.min)
        .interactable(true)
        .show(ctx, |ui| {
            let (rect, _) = ui.allocate_exact_size(screen.size(), Sense::click_and_drag());
            ui.painter().rect_filled(
                rect,
                Rounding::ZERO,
                Color32::from_black_alpha(if theme.is_light() { 54 } else { 96 }),
            );
        });
}

fn focus_text_edit(ui: &egui::Ui, response: &egui::Response, selection_end: usize) {
    response.request_focus();
    if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), response.id) {
        state
            .cursor
            .set_char_range(Some(egui::text::CCursorRange::two(
                egui::text::CCursor::new(0),
                egui::text::CCursor::new(selection_end),
            )));
        state.store(ui.ctx(), response.id);
    }
}

fn rename_selection_end(name: &str, is_folder: bool) -> usize {
    if is_folder {
        return name.chars().count();
    }
    Path::new(name)
        .file_stem()
        .and_then(|stem| stem.to_str())
        .map(|stem| stem.chars().count())
        .filter(|count| *count > 0)
        .unwrap_or_else(|| name.chars().count())
}

fn property_line(ui: &mut egui::Ui, key: &str, value: &str) {
    let muted = ui.visuals().widgets.inactive.fg_stroke.color;
    let text = ui
        .visuals()
        .override_text_color
        .unwrap_or_else(|| Color32::from_gray(198));
    ui.horizontal_wrapped(|ui| {
        ui.add_sized(
            Vec2::new(104.0, 18.0),
            egui::Label::new(RichText::new(key).size(11.0).color(muted)),
        );
        ui.label(RichText::new(value).size(12.0).color(text));
    });
    ui.add_space(3.0);
}

fn property_section_title(ui: &mut egui::Ui, title: &str) {
    ui.add_space(4.0);
    ui.horizontal(|ui| {
        ui.label(
            RichText::new(title.to_uppercase())
                .size(9.5)
                .strong()
                .color(ui.visuals().widgets.inactive.fg_stroke.color),
        );
        ui.add(egui::Separator::default().horizontal());
    });
    ui.add_space(2.0);
}

fn summary_row(
    ui: &mut egui::Ui,
    theme: AppPalette,
    icon: ChromeIcon,
    title: &str,
    detail: &str,
    path: &str,
) {
    egui::Frame::none()
        .fill(theme.recessed_fill())
        .stroke(Stroke::new(1.0, theme.hairline()))
        .rounding(Rounding::same(theme_rounding(theme) + 8.0))
        .inner_margin(egui::Margin::symmetric(10.0, 8.0))
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                let (icon_rect, _) = ui.allocate_exact_size(Vec2::splat(34.0), Sense::hover());
                let painter = ui.painter_at(icon_rect);
                painter.circle_filled(icon_rect.center(), 16.0, theme.accent_soft());
                painter.circle_stroke(icon_rect.center(), 16.0, Stroke::new(1.0, theme.hairline()));
                draw_chrome_icon(
                    &painter,
                    Rect::from_center_size(icon_rect.center(), Vec2::splat(16.0)),
                    icon,
                    theme.accent(0.0),
                );
                ui.add_space(4.0);
                ui.vertical(|ui| {
                    ui.label(
                        RichText::new(truncate_middle(title, 38))
                            .size(13.0)
                            .strong()
                            .color(theme.text()),
                    );
                    ui.label(
                        RichText::new(detail)
                            .size(11.0)
                            .color(theme.secondary_text()),
                    );
                    ui.label(
                        RichText::new(truncate_middle(path, 58))
                            .size(10.0)
                            .color(theme.muted()),
                    );
                });
            });
        });
}

fn validation_status(
    ui: &mut egui::Ui,
    theme: AppPalette,
    validation: &NameValidation,
    ready: &str,
) {
    let (icon, title, detail, color, fill) = if !validation.accepted {
        (
            ChromeIcon::Properties,
            "Needs Attention",
            validation
                .message
                .as_deref()
                .unwrap_or("The name cannot be used"),
            Color32::from_rgb(255, 122, 128),
            Color32::from_rgba_unmultiplied(255, 122, 128, if theme.is_light() { 24 } else { 34 }),
        )
    } else if validation.warning {
        (
            ChromeIcon::Hidden,
            "Hidden Item",
            validation.message.as_deref().unwrap_or(ready),
            Color32::from_rgb(235, 174, 76),
            Color32::from_rgba_unmultiplied(235, 174, 76, if theme.is_light() { 24 } else { 34 }),
        )
    } else {
        (
            ChromeIcon::Open,
            "Ready",
            ready,
            theme.accent(0.0),
            theme.accent_soft(),
        )
    };

    egui::Frame::none()
        .fill(fill)
        .stroke(Stroke::new(1.0, color))
        .rounding(Rounding::same(theme_rounding(theme) + 7.0))
        .inner_margin(egui::Margin::symmetric(8.0, 6.0))
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                inline_chrome_icon(ui, theme, icon, color);
                ui.vertical(|ui| {
                    ui.label(RichText::new(title).size(11.5).strong().color(theme.text()));
                    ui.label(
                        RichText::new(detail)
                            .size(10.5)
                            .color(theme.secondary_text()),
                    );
                });
            });
        });
}

fn safety_notice(
    ui: &mut egui::Ui,
    theme: AppPalette,
    icon: ChromeIcon,
    title: &str,
    detail: &str,
    destructive: bool,
) {
    let accent = if destructive {
        Color32::from_rgb(255, 122, 128)
    } else {
        theme.accent(0.0)
    };
    egui::Frame::none()
        .fill(Color32::from_rgba_unmultiplied(
            accent.r(),
            accent.g(),
            accent.b(),
            if theme.is_light() { 24 } else { 34 },
        ))
        .stroke(Stroke::new(1.0, accent))
        .rounding(Rounding::same(theme_rounding(theme) + 8.0))
        .inner_margin(egui::Margin::symmetric(10.0, 8.0))
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                inline_chrome_icon(ui, theme, icon, accent);
                ui.vertical(|ui| {
                    ui.label(RichText::new(title).size(12.0).strong().color(theme.text()));
                    ui.label(
                        RichText::new(detail)
                            .size(10.8)
                            .color(theme.secondary_text()),
                    );
                });
            });
        });
}

fn progress_card(
    ui: &mut egui::Ui,
    title: &str,
    progress: &OperationProgress,
    phase: f32,
    theme: AppPalette,
) {
    let value = operation_percent(progress.done_items, progress.total_items).clamp(0.0, 1.0);
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 46.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let card = rect.shrink2(egui::vec2(1.0, 1.0));
    painter.rect_filled(
        card,
        Rounding::same(theme_rounding(theme) + 7.0),
        theme.surface(),
    );
    painter.rect_stroke(
        card,
        Rounding::same(theme_rounding(theme) + 7.0),
        Stroke::new(1.0, theme.hairline()),
    );
    painter.text(
        card.left_top() + egui::vec2(8.0, 6.0),
        Align2::LEFT_TOP,
        title,
        FontId::proportional(12.0),
        theme.text(),
    );
    painter.text(
        card.right_top() + egui::vec2(-8.0, 6.0),
        Align2::RIGHT_TOP,
        format!("{}%", (value * 100.0).round() as u8),
        FontId::proportional(12.0),
        theme.secondary_text(),
    );
    let bar = Rect::from_min_max(
        card.left_bottom() + egui::vec2(8.0, -14.0),
        card.right_bottom() + egui::vec2(-8.0, -8.0),
    );
    painter.rect_filled(bar, Rounding::same(999.0), theme.recessed_fill());
    let filled = Rect::from_min_max(
        bar.left_top(),
        Pos2::new(bar.left() + bar.width() * value, bar.bottom()),
    );
    painter.rect_filled(filled, Rounding::same(999.0), theme.accent(phase));
}

fn paint_thumbnail(
    painter: &egui::Painter,
    rect: Rect,
    view: ThumbnailView,
    hovered: bool,
    accent: Color32,
) {
    icon_shadow(painter, rect, 4.0, 34);
    painter.rect_filled(rect, Rounding::same(4.0), Color32::from_rgb(246, 248, 252));
    painter.rect_stroke(
        rect,
        Rounding::same(4.0),
        Stroke::new(
            1.0,
            if hovered {
                accent
            } else {
                Color32::from_rgb(178, 184, 196)
            },
        ),
    );

    let frame = rect.shrink(2.0);
    let frame_aspect = frame.width() / frame.height().max(1.0);
    let target = if frame_aspect > view.aspect {
        let width = frame.height() * view.aspect;
        Rect::from_center_size(frame.center(), Vec2::new(width, frame.height()))
    } else {
        let height = frame.width() / view.aspect.max(0.01);
        Rect::from_center_size(frame.center(), Vec2::new(frame.width(), height))
    };
    painter.image(
        view.texture_id,
        target,
        Rect::from_min_max(Pos2::ZERO, Pos2::new(1.0, 1.0)),
        Color32::WHITE,
    );

    if hovered {
        painter.rect_stroke(
            rect.shrink(1.0),
            Rounding::same(3.0),
            Stroke::new(1.0, accent),
        );
    }
}

fn draw_video_badge(painter: &egui::Painter, rect: Rect, animated: bool) {
    let tri_center = rect.right_bottom() + egui::vec2(-15.0, -14.0);
    let shadow = vec![
        tri_center + egui::vec2(-3.0, -5.0) + egui::vec2(1.0, 1.0),
        tri_center + egui::vec2(-3.0, 5.0) + egui::vec2(1.0, 1.0),
        tri_center + egui::vec2(6.0, 0.0) + egui::vec2(1.0, 1.0),
    ];
    painter.add(egui::Shape::convex_polygon(
        shadow,
        Color32::from_black_alpha(112),
        Stroke::NONE,
    ));
    painter.add(egui::Shape::convex_polygon(
        vec![
            tri_center + egui::vec2(-3.0, -5.0),
            tri_center + egui::vec2(-3.0, 5.0),
            tri_center + egui::vec2(6.0, 0.0),
        ],
        Color32::WHITE,
        Stroke::NONE,
    ));
    if animated {
        let dot = tri_center + egui::vec2(8.0, -7.0);
        painter.circle_filled(
            dot + egui::vec2(1.0, 1.0),
            2.4,
            Color32::from_black_alpha(96),
        );
        painter.circle_filled(dot, 2.3, Color32::from_rgb(142, 118, 255));
    }
}

fn draw_file_type_icon(
    painter: &egui::Painter,
    rect: Rect,
    class: MediaClass,
    _hovered: bool,
    loading: bool,
    theme: AppPalette,
) {
    let accent = class_accent(class);
    let glyph_rect = if rect.height() < 42.0 {
        rect.shrink2(Vec2::new(2.0, 2.0))
    } else {
        rect.shrink2(Vec2::new(8.0, 6.0))
    };
    match class {
        MediaClass::Folder => draw_folder_icon(painter, glyph_rect, accent),
        MediaClass::Image => draw_image_icon(painter, glyph_rect, accent),
        MediaClass::Video => draw_video_icon(painter, glyph_rect, accent),
        MediaClass::Audio => draw_audio_icon(painter, glyph_rect, accent),
        MediaClass::Archive => draw_archive_icon(painter, glyph_rect, accent),
        MediaClass::Code => draw_code_icon(painter, glyph_rect, accent),
        MediaClass::Pdf
        | MediaClass::Text
        | MediaClass::Symlink
        | MediaClass::Special
        | MediaClass::File => draw_document_icon(painter, glyph_rect, accent),
    }

    if loading {
        let center = rect.right_bottom() + egui::vec2(-9.0, -8.0);
        painter.circle_filled(center, 6.0, theme.elevated_fill());
        painter.circle_stroke(center, 6.0, Stroke::new(1.0, theme.hairline()));
        for index in 0..3 {
            painter.circle_filled(
                center + egui::vec2(-2.8 + index as f32 * 2.8, 0.0),
                0.9,
                theme.accent(0.0),
            );
        }
    }
}

fn class_accent(class: MediaClass) -> Color32 {
    match class {
        MediaClass::Folder => Color32::from_rgb(74, 153, 255),
        MediaClass::Image => Color32::from_rgb(65, 181, 103),
        MediaClass::Video => Color32::from_rgb(188, 118, 255),
        MediaClass::Audio => Color32::from_rgb(66, 149, 255),
        MediaClass::Archive => Color32::from_rgb(220, 154, 46),
        MediaClass::Code => Color32::from_rgb(70, 166, 82),
        MediaClass::Pdf => Color32::from_rgb(215, 55, 55),
        MediaClass::Text => Color32::from_rgb(128, 128, 128),
        MediaClass::Symlink => Color32::from_rgb(48, 184, 184),
        MediaClass::Special => Color32::from_rgb(128, 128, 128),
        MediaClass::File => Color32::from_rgb(128, 128, 128),
    }
}

fn icon_shadow(painter: &egui::Painter, rect: Rect, _rounding: f32, alpha: u8) {
    let soft_alpha = (alpha as f32 * 0.50).round() as u8;
    let mid_alpha = (alpha as f32 * 0.34).round() as u8;
    let center = rect.center_bottom() + egui::vec2(0.0, -1.5);
    painter.rect_filled(
        Rect::from_center_size(
            center + egui::vec2(0.0, 2.0),
            Vec2::new(rect.width() * 0.86, (rect.height() * 0.16).max(4.0)),
        ),
        Rounding::same(999.0),
        Color32::from_black_alpha(soft_alpha),
    );
    painter.rect_filled(
        Rect::from_center_size(
            center,
            Vec2::new(rect.width() * 0.58, (rect.height() * 0.10).max(3.0)),
        ),
        Rounding::same(999.0),
        Color32::from_black_alpha(mid_alpha),
    );
}

fn mix_color(a: Color32, b: Color32, amount: f32) -> Color32 {
    let amount = amount.clamp(0.0, 1.0);
    let mix = |left: u8, right: u8| {
        ((left as f32) + ((right as f32) - (left as f32)) * amount).round() as u8
    };
    Color32::from_rgb(mix(a.r(), b.r()), mix(a.g(), b.g()), mix(a.b(), b.b()))
}

fn lift(color: Color32, amount: f32) -> Color32 {
    mix_color(color, Color32::WHITE, amount)
}

fn shade(color: Color32, amount: f32) -> Color32 {
    mix_color(color, Color32::BLACK, amount)
}

fn icon_scale(rect: Rect) -> f32 {
    (rect.height() / 58.0).clamp(0.42, 1.18)
}

fn draw_folder_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let rect = fit_rect_to_aspect(rect, FOLDER_ICON_ASPECT);
    let s = icon_scale(rect);
    let body = Rect::from_min_max(
        rect.left_top() + egui::vec2(2.5 * s, rect.height() * 0.20),
        rect.right_bottom() - egui::vec2(2.5 * s, 1.5 * s),
    );
    let tab = Rect::from_min_size(
        body.left_top() + egui::vec2(3.0 * s, -10.0 * s),
        Vec2::new(body.width() * 0.52, 15.0 * s),
    );
    let front = Rect::from_min_max(
        Pos2::new(body.left(), body.top() + body.height() * 0.16),
        body.right_bottom(),
    );
    icon_shadow(painter, body, 7.0 * s, 34);
    painter.rect_filled(tab, Rounding::same(4.5 * s), lift(accent, 0.38));
    painter.rect_stroke(
        tab,
        Rounding::same(4.5 * s),
        Stroke::new(0.8, lift(accent, 0.52)),
    );
    painter.rect_filled(body, Rounding::same(7.0 * s), lift(accent, 0.18));
    painter.rect_stroke(
        body,
        Rounding::same(7.0 * s),
        Stroke::new(1.0, shade(accent, 0.16)),
    );
    painter.rect_filled(front, Rounding::same(7.0 * s), accent);
    painter.rect_filled(
        Rect::from_min_max(
            front.left_top(),
            front.right_top() + egui::vec2(0.0, front.height() * 0.38),
        ),
        Rounding::same(7.0 * s),
        lift(accent, 0.24),
    );
    painter.rect_filled(
        Rect::from_min_max(
            front.left_bottom() + egui::vec2(0.0, -8.0 * s),
            front.right_bottom(),
        ),
        Rounding::same(6.0 * s),
        shade(accent, 0.10),
    );
    painter.line_segment(
        [
            front.left_top() + egui::vec2(6.0 * s, 3.0 * s),
            front.right_top() + egui::vec2(-7.0 * s, 3.0 * s),
        ],
        Stroke::new(1.0, Color32::from_white_alpha(126)),
    );
    painter.rect_stroke(
        front,
        Rounding::same(7.0 * s),
        Stroke::new(1.0, shade(accent, 0.20)),
    );
}

fn draw_image_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let s = icon_scale(rect);
    let frame = rect.shrink(5.0 * s);
    icon_shadow(painter, frame, 6.0 * s, 34);
    painter.rect_filled(
        frame,
        Rounding::same(6.0 * s),
        Color32::from_rgb(244, 248, 255),
    );
    let photo = frame.shrink(4.0 * s);
    painter.rect_filled(
        photo,
        Rounding::same(4.0 * s),
        Color32::from_rgb(98, 176, 238),
    );
    painter.rect_filled(
        Rect::from_min_max(
            photo.left_bottom() + egui::vec2(0.0, -20.0 * s),
            photo.right_bottom(),
        ),
        Rounding::same(4.0 * s),
        Color32::from_rgb(56, 155, 89),
    );
    painter.circle_filled(
        photo.left_top() + egui::vec2(photo.width() * 0.72, 12.0 * s),
        4.0 * s,
        Color32::from_rgb(255, 213, 92),
    );
    painter.add(egui::Shape::convex_polygon(
        vec![
            photo.left_bottom() + egui::vec2(6.0 * s, -4.0 * s),
            photo.center() + egui::vec2(-8.0 * s, -4.0 * s),
            photo.center() + egui::vec2(3.0 * s, 12.0 * s),
        ],
        Color32::from_rgb(37, 120, 72),
        Stroke::NONE,
    ));
    painter.rect_stroke(
        frame,
        Rounding::same(6.0 * s),
        Stroke::new(1.0, shade(accent, 0.18)),
    );
}

fn draw_video_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let s = icon_scale(rect);
    let frame = rect.shrink(6.0 * s);
    icon_shadow(painter, frame, 6.0 * s, 38);
    painter.rect_filled(frame, Rounding::same(7.0 * s), shade(accent, 0.36));
    painter.rect_filled(
        Rect::from_min_max(
            frame.left_top(),
            frame.right_top() + egui::vec2(0.0, frame.height() * 0.42),
        ),
        Rounding::same(7.0 * s),
        lift(accent, 0.18),
    );
    for index in 0..4 {
        let x = frame.left() + 7.0 * s + index as f32 * 8.0 * s;
        painter.line_segment(
            [
                Pos2::new(x, frame.top() + 5.0 * s),
                Pos2::new(x + 3.0 * s, frame.top() + 5.0 * s),
            ],
            Stroke::new(1.4 * s, Color32::from_white_alpha(116)),
        );
        painter.line_segment(
            [
                Pos2::new(x, frame.bottom() - 5.0 * s),
                Pos2::new(x + 3.0 * s, frame.bottom() - 5.0 * s),
            ],
            Stroke::new(1.4 * s, Color32::from_white_alpha(84)),
        );
    }
    painter.rect_filled(
        Rect::from_center_size(frame.center(), Vec2::new(25.0 * s, 25.0 * s)),
        Rounding::same(999.0),
        Color32::from_rgba_unmultiplied(255, 255, 255, 42),
    );
    painter.add(egui::Shape::convex_polygon(
        vec![
            frame.center() + egui::vec2(-6.0 * s, -9.0 * s),
            frame.center() + egui::vec2(-6.0 * s, 9.0 * s),
            frame.center() + egui::vec2(10.0 * s, 0.0),
        ],
        Color32::WHITE,
        Stroke::NONE,
    ));
    painter.rect_stroke(
        frame,
        Rounding::same(7.0 * s),
        Stroke::new(1.0, shade(accent, 0.22)),
    );
}

fn draw_audio_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let s = icon_scale(rect);
    let frame = rect.shrink(7.0 * s);
    icon_shadow(painter, frame, 7.0 * s, 34);
    painter.rect_filled(frame, Rounding::same(999.0), accent);
    painter.rect_filled(
        Rect::from_min_max(
            frame.left_top(),
            frame.right_top() + egui::vec2(0.0, frame.height() * 0.46),
        ),
        Rounding::same(999.0),
        lift(accent, 0.24),
    );
    let center = frame.center();
    for (index, height) in [13.0, 22.0, 31.0, 22.0, 14.0].into_iter().enumerate() {
        let x = center.x - 18.0 * s + index as f32 * 9.0 * s;
        painter.line_segment(
            [
                Pos2::new(x, center.y - height * 0.5 * s),
                Pos2::new(x, center.y + height * 0.5 * s),
            ],
            Stroke::new(2.6 * s, Color32::WHITE),
        );
    }
    painter.rect_stroke(
        frame,
        Rounding::same(999.0),
        Stroke::new(1.0, shade(accent, 0.22)),
    );
}

fn draw_archive_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let s = icon_scale(rect);
    let frame = rect.shrink(6.0 * s);
    icon_shadow(painter, frame, 6.0 * s, 34);
    painter.rect_filled(frame, Rounding::same(6.0 * s), lift(accent, 0.16));
    painter.rect_filled(
        Rect::from_min_max(
            frame.left_top(),
            frame.right_top() + egui::vec2(0.0, frame.height() * 0.32),
        ),
        Rounding::same(6.0 * s),
        lift(accent, 0.34),
    );
    let strap = Rect::from_min_max(
        Pos2::new(frame.center().x - 6.0 * s, frame.top() + 2.0 * s),
        Pos2::new(frame.center().x + 6.0 * s, frame.bottom() - 2.0 * s),
    );
    painter.rect_filled(strap, Rounding::same(2.0 * s), shade(accent, 0.12));
    for index in 0..4 {
        let y = strap.top() + 5.0 * s + index as f32 * 8.0 * s;
        if y < strap.bottom() - 3.0 * s {
            painter.line_segment(
                [
                    Pos2::new(strap.left() + 2.0 * s, y),
                    Pos2::new(strap.right() - 2.0 * s, y),
                ],
                Stroke::new(1.0, lift(accent, 0.30)),
            );
        }
    }
    painter.rect_stroke(
        frame,
        Rounding::same(6.0 * s),
        Stroke::new(1.0, shade(accent, 0.22)),
    );
}

fn draw_code_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let s = icon_scale(rect);
    draw_document_icon(painter, rect, accent);
    let code_rect = rect.shrink2(egui::vec2(14.0 * s, 16.0 * s));
    painter.text(
        code_rect.left_center() + egui::vec2(4.0 * s, 0.0),
        Align2::LEFT_CENTER,
        "<",
        FontId::monospace(20.0 * s),
        accent,
    );
    painter.text(
        code_rect.right_center() + egui::vec2(-4.0 * s, 0.0),
        Align2::RIGHT_CENTER,
        "/>",
        FontId::monospace(17.0 * s),
        accent,
    );
}

fn draw_document_icon(painter: &egui::Painter, rect: Rect, accent: Color32) {
    let s = icon_scale(rect);
    let frame = rect.shrink(7.0 * s);
    icon_shadow(painter, frame, 5.0 * s, 28);
    painter.rect_filled(
        frame,
        Rounding::same(5.0 * s),
        Color32::from_rgb(252, 253, 255),
    );
    let fold = 14.0 * s;
    painter.add(egui::Shape::convex_polygon(
        vec![
            frame.right_top(),
            frame.right_top() + egui::vec2(0.0, fold),
            frame.right_top() + egui::vec2(-fold, 0.0),
        ],
        lift(accent, 0.38),
        Stroke::NONE,
    ));
    painter.line_segment(
        [
            frame.right_top() + egui::vec2(-fold, 0.0),
            frame.right_top() + egui::vec2(0.0, fold),
        ],
        Stroke::new(1.0, shade(accent, 0.12)),
    );
    painter.rect_filled(
        Rect::from_min_max(
            frame.left_top(),
            frame.left_top() + egui::vec2(5.0 * s, frame.height()),
        ),
        Rounding::same(4.0 * s),
        accent,
    );
    painter.line_segment(
        [
            frame.left_top() + egui::vec2(7.0 * s, 3.0 * s),
            frame.right_top() + egui::vec2(-fold - 3.0 * s, 3.0 * s),
        ],
        Stroke::new(1.0, Color32::from_white_alpha(180)),
    );
    let line_count = if s < 0.7 { 2 } else { 4 };
    for index in 0..line_count {
        let y = frame.top() + 14.0 * s + index as f32 * 8.0 * s;
        painter.line_segment(
            [
                Pos2::new(frame.left() + 13.0 * s, y),
                Pos2::new(frame.right() - 12.0 * s, y),
            ],
            Stroke::new(1.0, Color32::from_gray(184)),
        );
    }
    painter.rect_stroke(
        frame,
        Rounding::same(5.0 * s),
        Stroke::new(1.0, Color32::from_rgb(190, 195, 205)),
    );
}

fn empty_state(
    ui: &mut egui::Ui,
    query: &str,
    recursive: bool,
    searching: bool,
    theme: AppPalette,
) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 230.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let card = rect.shrink2(egui::vec2(14.0, 8.0));
    painter.rect_filled(
        card,
        Rounding::same(theme_rounding(theme) + 12.0),
        Color32::from_rgba_unmultiplied(
            theme.recessed_fill().r(),
            theme.recessed_fill().g(),
            theme.recessed_fill().b(),
            if theme.is_light() { 210 } else { 178 },
        ),
    );
    painter.rect_stroke(
        card,
        Rounding::same(theme_rounding(theme) + 12.0),
        Stroke::new(1.0, theme.hairline()),
    );
    let icon_rect =
        Rect::from_center_size(card.center() + egui::vec2(0.0, -62.0), Vec2::splat(62.0));
    if query.trim().is_empty() {
        draw_file_type_icon(&painter, icon_rect, MediaClass::Folder, false, false, theme);
    } else {
        draw_file_type_icon(&painter, icon_rect, MediaClass::Text, false, false, theme);
        let badge = Rect::from_center_size(
            icon_rect.right_bottom() + egui::vec2(-8.0, -8.0),
            Vec2::splat(20.0),
        );
        painter.circle_filled(badge.center(), 11.0, theme.surface());
        painter.circle_stroke(badge.center(), 11.0, Stroke::new(1.0, theme.hairline()));
        draw_chrome_icon(
            &painter,
            badge.shrink(4.0),
            ChromeIcon::Search,
            theme.accent(0.0),
        );
    }
    painter.text(
        card.center() + egui::vec2(0.0, 12.0),
        Align2::CENTER_CENTER,
        if searching {
            "Searching Subfolders"
        } else if query.trim().is_empty() {
            "Empty Folder"
        } else {
            "No Matches"
        },
        FontId::proportional(18.0),
        theme.text(),
    );
    let detail = if searching {
        "Results will appear as soon as the bounded scan completes"
    } else if query.trim().is_empty() {
        "Drop files here or create something new"
    } else if recursive {
        "No results in this folder or its subfolders"
    } else {
        "Clear the search or include subfolders"
    };
    painter.text(
        card.center() + egui::vec2(0.0, 44.0),
        Align2::CENTER_CENTER,
        detail,
        FontId::proportional(12.0),
        theme.muted(),
    );
}

fn panel_frame(
    theme: AppPalette,
    fill: Color32,
    margin: egui::Margin,
    opacity: f32,
) -> egui::Frame {
    egui::Frame::none()
        .fill(themed_alpha(fill, opacity))
        .stroke(Stroke::new(1.0, theme.hairline()))
        .inner_margin(margin)
}

fn themed_alpha(color: Color32, opacity: f32) -> Color32 {
    let alpha = ((color.a() as f32) * opacity.clamp(0.45, 1.0)).round() as u8;
    Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), alpha)
}

fn pathbar_button(theme: AppPalette, label: &str, current: bool) -> egui::Button<'_> {
    let fill = if current {
        theme.selected_fill()
    } else {
        Color32::TRANSPARENT
    };
    let stroke = if current {
        theme.accent(0.0)
    } else {
        theme.hairline()
    };
    egui::Button::new(
        RichText::new(truncate_middle(label, 24))
            .size(11.5)
            .color(if current {
                theme.text()
            } else {
                theme.secondary_text()
            }),
    )
    .fill(fill)
    .stroke(Stroke::new(1.0, stroke))
    .rounding(Rounding::same(theme_rounding(theme) + 7.0))
    .min_size(Vec2::new(0.0, 22.0))
}

fn info_chip(ui: &mut egui::Ui, theme: AppPalette, text: &str, active: bool) {
    let fill = if active {
        Color32::from_rgba_unmultiplied(
            theme.accent(0.0).r(),
            theme.accent(0.0).g(),
            theme.accent(0.0).b(),
            if theme.is_light() { 24 } else { 42 },
        )
    } else {
        Color32::from_rgba_unmultiplied(
            theme.surface().r(),
            theme.surface().g(),
            theme.surface().b(),
            if theme.is_light() { 210 } else { 192 },
        )
    };
    let stroke = if active {
        theme.accent(0.0)
    } else {
        theme.hairline()
    };
    egui::Frame::none()
        .fill(fill)
        .stroke(Stroke::new(1.0, stroke))
        .rounding(Rounding::same(theme_rounding(theme)))
        .inner_margin(egui::Margin::symmetric(6.0, 2.0))
        .show(ui, |ui| {
            ui.label(RichText::new(text).size(10.0).color(if active {
                theme.accent(0.0)
            } else {
                theme.muted()
            }));
        });
}

fn status_item(ui: &mut egui::Ui, theme: AppPalette, text: impl AsRef<str>, active: bool) {
    let color = if active {
        theme.accent(0.0)
    } else {
        theme.muted()
    };
    ui.horizontal(|ui| {
        let (dot_rect, _) = ui.allocate_exact_size(Vec2::splat(7.0), Sense::hover());
        ui.painter().circle_filled(
            dot_rect.center(),
            if active { 2.6 } else { 1.8 },
            if active {
                theme.accent(0.0)
            } else {
                theme.hairline()
            },
        );
        ui.label(
            RichText::new(truncate_middle(text.as_ref(), 42))
                .size(11.0)
                .color(color),
        );
    });
    ui.add_space(8.0);
}

fn status_button(theme: AppPalette, label: &str) -> egui::Button<'_> {
    egui::Button::new(
        RichText::new(label)
            .size(11.0)
            .color(theme.secondary_text()),
    )
    .fill(theme.elevated_fill())
    .stroke(Stroke::new(1.0, theme.hairline()))
    .rounding(Rounding::same(theme_rounding(theme) + 6.0))
    .min_size(Vec2::new(0.0, 22.0))
}

fn overlay_header(
    ui: &mut egui::Ui,
    theme: AppPalette,
    opacity: f32,
    icon: ChromeIcon,
    title: &str,
    detail: &str,
) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 66.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let card = rect.shrink2(egui::vec2(1.0, 1.0));
    painter.rect_filled(
        card,
        Rounding::same(theme_rounding(theme) + 10.0),
        themed_alpha(theme.surface(), opacity),
    );
    painter.rect_stroke(
        card,
        Rounding::same(theme_rounding(theme) + 10.0),
        Stroke::new(1.0, theme.hairline()),
    );
    painter.rect_filled(
        Rect::from_min_max(card.left_top(), card.left_bottom() + egui::vec2(4.0, 0.0)),
        Rounding::same(theme_rounding(theme) + 6.0),
        theme.accent(0.0),
    );
    let icon_rect = Rect::from_center_size(
        card.left_center() + egui::vec2(28.0, 0.0),
        Vec2::splat(28.0),
    );
    let icon_glow = Rect::from_center_size(
        icon_rect.center_bottom() + egui::vec2(0.0, -2.0),
        Vec2::new(25.0, 5.0),
    );
    painter.rect_filled(
        icon_glow,
        Rounding::same(999.0),
        Color32::from_rgba_unmultiplied(
            theme.accent(0.0).r(),
            theme.accent(0.0).g(),
            theme.accent(0.0).b(),
            if theme.is_light() { 32 } else { 46 },
        ),
    );
    draw_chrome_icon(&painter, icon_rect, icon, theme.accent(0.0));
    painter.text(
        card.left_center() + egui::vec2(58.0, -10.0),
        Align2::LEFT_CENTER,
        truncate_middle(title, 34),
        FontId::proportional(17.0),
        theme.text(),
    );
    painter.text(
        card.left_center() + egui::vec2(58.0, 12.0),
        Align2::LEFT_CENTER,
        truncate_middle(detail, 48),
        FontId::proportional(11.0),
        theme.muted(),
    );
}

fn overlay_card(
    ui: &mut egui::Ui,
    theme: AppPalette,
    opacity: f32,
    icon: ChromeIcon,
    title: &str,
    add_contents: impl FnOnce(&mut egui::Ui),
) {
    egui::Frame::none()
        .fill(themed_alpha(theme.surface(), opacity))
        .stroke(Stroke::new(1.0, theme.hairline()))
        .rounding(Rounding::same(theme_rounding(theme) + 8.0))
        .inner_margin(egui::Margin::same(10.0))
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                inline_chrome_icon(ui, theme, icon, theme.accent(0.0));
                ui.label(
                    RichText::new(title)
                        .size(12.0)
                        .strong()
                        .color(theme.secondary_text()),
                );
            });
            ui.add_space(8.0);
            add_contents(ui);
        });
    ui.add_space(8.0);
}

fn inline_chrome_icon(ui: &mut egui::Ui, theme: AppPalette, icon: ChromeIcon, color: Color32) {
    let (rect, _) = ui.allocate_exact_size(Vec2::splat(20.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let glow = Rect::from_center_size(
        rect.center_bottom() + egui::vec2(0.0, -3.0),
        Vec2::new(14.0, 4.0),
    );
    painter.rect_filled(
        glow,
        Rounding::same(999.0),
        Color32::from_rgba_unmultiplied(
            color.r(),
            color.g(),
            color.b(),
            if theme.is_light() { 20 } else { 34 },
        ),
    );
    draw_chrome_icon(&painter, rect.shrink(3.0), icon, color);
}

fn icon_button(
    ui: &mut egui::Ui,
    theme: AppPalette,
    icon: ChromeIcon,
    label: Option<&str>,
    active: bool,
    enabled: bool,
) -> egui::Response {
    let quiet = label.is_none();
    let label_width = label
        .map(|text| text.chars().count() as f32 * 6.4 + 32.0)
        .unwrap_or(28.0);
    let width = label_width.clamp(28.0, 116.0);
    let sense = if enabled {
        Sense::click()
    } else {
        Sense::hover()
    };
    let (rect, response) = ui.allocate_exact_size(Vec2::new(width, 28.0), sense);
    let painter = ui.painter_at(rect);
    let row = rect.shrink2(egui::vec2(1.0, 1.0));
    let fill = if active {
        theme.selected_fill()
    } else if enabled && response.hovered() {
        theme.hover_fill()
    } else if !enabled {
        if quiet {
            Color32::TRANSPARENT
        } else {
            Color32::from_rgba_unmultiplied(
                theme.button_fill().r(),
                theme.button_fill().g(),
                theme.button_fill().b(),
                if theme.is_light() { 92 } else { 76 },
            )
        }
    } else if quiet {
        Color32::TRANSPARENT
    } else {
        theme.button_fill()
    };
    if enabled && (active || response.hovered()) {
        painter.rect_filled(
            row.translate(egui::vec2(0.0, 1.5)),
            Rounding::same(theme_rounding(theme) + 7.0),
            Color32::from_black_alpha(if theme.is_light() { 8 } else { 22 }),
        );
    }
    painter.rect_filled(row, Rounding::same(theme_rounding(theme) + 7.0), fill);
    painter.rect_stroke(
        row,
        Rounding::same(theme_rounding(theme) + 7.0),
        Stroke::new(
            1.0,
            if active {
                theme.accent(0.0)
            } else if quiet && !response.hovered() {
                Color32::TRANSPARENT
            } else {
                theme.hairline()
            },
        ),
    );
    if active {
        painter.rect_filled(
            Rect::from_center_size(
                row.center_bottom() + egui::vec2(0.0, -2.0),
                Vec2::new(18.0, 2.0),
            ),
            Rounding::same(999.0),
            theme.accent(0.0),
        );
    } else if enabled && response.hovered() {
        painter.line_segment(
            [
                row.left_top() + egui::vec2(8.0, 1.0),
                row.right_top() + egui::vec2(-8.0, 1.0),
            ],
            Stroke::new(
                1.0,
                Color32::from_rgba_unmultiplied(
                    255,
                    255,
                    255,
                    if theme.is_light() { 104 } else { 38 },
                ),
            ),
        );
    }
    let color = if !enabled {
        Color32::from_rgba_unmultiplied(
            theme.muted().r(),
            theme.muted().g(),
            theme.muted().b(),
            120,
        )
    } else if active {
        theme.accent(0.0)
    } else {
        theme.secondary_text()
    };
    let icon_rect = Rect::from_center_size(
        Pos2::new(row.left() + 14.0, row.center().y),
        Vec2::splat(15.0),
    );
    draw_chrome_icon(&painter, icon_rect, icon, color);
    if let Some(label) = label {
        painter.text(
            Pos2::new(row.left() + 28.0, row.center().y),
            Align2::LEFT_CENTER,
            label,
            FontId::proportional(11.0),
            if enabled { theme.text() } else { color },
        );
    }
    response
}

fn draw_chrome_icon(painter: &egui::Painter, rect: Rect, icon: ChromeIcon, color: Color32) {
    let stroke = Stroke::new(1.5, color);
    let thin = Stroke::new(1.1, color);
    match icon {
        ChromeIcon::Sidebar => {
            painter.rect_stroke(rect, Rounding::same(2.0), thin);
            painter.line_segment(
                [
                    Pos2::new(rect.left() + rect.width() * 0.38, rect.top()),
                    Pos2::new(rect.left() + rect.width() * 0.38, rect.bottom()),
                ],
                thin,
            );
            for index in 0..3 {
                let y = rect.top() + 4.0 + index as f32 * 3.7;
                painter.line_segment(
                    [
                        Pos2::new(rect.left() + 2.0, y),
                        Pos2::new(rect.left() + 4.0, y),
                    ],
                    thin,
                );
            }
        }
        ChromeIcon::Back | ChromeIcon::Forward => {
            let dir = if matches!(icon, ChromeIcon::Back) {
                -1.0
            } else {
                1.0
            };
            let center = rect.center();
            painter.line_segment(
                [
                    center + egui::vec2(3.5 * dir, -5.0),
                    center + egui::vec2(-2.5 * dir, 0.0),
                ],
                stroke,
            );
            painter.line_segment(
                [
                    center + egui::vec2(-2.5 * dir, 0.0),
                    center + egui::vec2(3.5 * dir, 5.0),
                ],
                stroke,
            );
        }
        ChromeIcon::Up => {
            let c = rect.center();
            painter.line_segment(
                [c + egui::vec2(0.0, -6.0), c + egui::vec2(0.0, 6.0)],
                stroke,
            );
            painter.line_segment(
                [c + egui::vec2(0.0, -6.0), c + egui::vec2(-4.0, -1.5)],
                stroke,
            );
            painter.line_segment(
                [c + egui::vec2(0.0, -6.0), c + egui::vec2(4.0, -1.5)],
                stroke,
            );
        }
        ChromeIcon::Home => draw_home_place_icon(painter, rect.shrink(1.0), color),
        ChromeIcon::Search => {
            painter.circle_stroke(rect.center() + egui::vec2(-1.5, -1.5), 4.4, thin);
            painter.line_segment(
                [
                    rect.center() + egui::vec2(2.0, 2.0),
                    rect.right_bottom() + egui::vec2(-2.0, -2.0),
                ],
                stroke,
            );
        }
        ChromeIcon::Open => {
            draw_folder_place_icon(painter, rect.shrink(1.0), color);
            painter.line_segment(
                [
                    rect.center() + egui::vec2(-1.5, -2.5),
                    rect.center() + egui::vec2(4.5, 0.0),
                ],
                stroke,
            );
            painter.line_segment(
                [
                    rect.center() + egui::vec2(4.5, 0.0),
                    rect.center() + egui::vec2(-1.5, 2.5),
                ],
                stroke,
            );
        }
        ChromeIcon::Preview => {
            let screen = rect.shrink2(egui::vec2(1.5, 3.0));
            painter.rect_stroke(screen, Rounding::same(2.2), thin);
            painter.add(egui::Shape::convex_polygon(
                vec![
                    screen.center() + egui::vec2(-2.5, -4.0),
                    screen.center() + egui::vec2(-2.5, 4.0),
                    screen.center() + egui::vec2(4.5, 0.0),
                ],
                color,
                Stroke::NONE,
            ));
            painter.line_segment(
                [
                    screen.left_bottom() + egui::vec2(3.0, -1.5),
                    screen.right_bottom() + egui::vec2(-3.0, -1.5),
                ],
                thin,
            );
        }
        ChromeIcon::Window => {
            painter.rect_stroke(rect.shrink(2.0), Rounding::same(2.0), thin);
            painter.line_segment(
                [
                    Pos2::new(rect.left() + 3.0, rect.top() + 5.0),
                    Pos2::new(rect.right() - 3.0, rect.top() + 5.0),
                ],
                thin,
            );
            painter.circle_filled(rect.left_top() + egui::vec2(4.0, 3.0), 0.9, color);
            painter.circle_filled(rect.left_top() + egui::vec2(7.0, 3.0), 0.9, color);
        }
        ChromeIcon::Terminal => {
            painter.rect_stroke(rect.shrink(2.0), Rounding::same(2.0), thin);
            let left = rect.left_center() + egui::vec2(3.0, -1.0);
            painter.line_segment([left, left + egui::vec2(3.0, 2.0)], stroke);
            painter.line_segment(
                [left + egui::vec2(3.0, 2.0), left + egui::vec2(0.0, 4.0)],
                stroke,
            );
            painter.line_segment(
                [
                    rect.center() + egui::vec2(2.0, 4.0),
                    rect.center() + egui::vec2(6.0, 4.0),
                ],
                thin,
            );
        }
        ChromeIcon::Refresh => {
            painter.circle_stroke(rect.center(), 5.4, thin);
            painter.line_segment(
                [
                    rect.center() + egui::vec2(4.8, -3.2),
                    rect.center() + egui::vec2(7.0, -6.0),
                ],
                stroke,
            );
            painter.line_segment(
                [
                    rect.center() + egui::vec2(4.8, -3.2),
                    rect.center() + egui::vec2(2.0, -5.2),
                ],
                stroke,
            );
        }
        ChromeIcon::FolderPlus => {
            draw_folder_place_icon(painter, rect.shrink(1.0), color);
            draw_plus(
                painter,
                rect.right_bottom() + egui::vec2(-4.0, -4.0),
                2.8,
                color,
            );
        }
        ChromeIcon::FilePlus => {
            draw_document_outline(painter, rect, color);
            draw_plus(painter, rect.center() + egui::vec2(2.0, 2.0), 3.0, color);
        }
        ChromeIcon::Cut => {
            painter.circle_stroke(rect.left_center() + egui::vec2(3.0, -3.0), 2.0, thin);
            painter.circle_stroke(rect.left_center() + egui::vec2(3.0, 4.0), 2.0, thin);
            painter.line_segment(
                [
                    rect.left_center() + egui::vec2(5.0, -2.0),
                    rect.right_top() + egui::vec2(-2.0, 3.0),
                ],
                thin,
            );
            painter.line_segment(
                [
                    rect.left_center() + egui::vec2(5.0, 3.0),
                    rect.right_bottom() + egui::vec2(-2.0, -3.0),
                ],
                thin,
            );
        }
        ChromeIcon::Copy => {
            painter.rect_stroke(
                rect.translate(egui::vec2(-2.0, 2.0)).shrink(3.0),
                Rounding::same(1.5),
                thin,
            );
            painter.rect_stroke(
                rect.translate(egui::vec2(2.0, -2.0)).shrink(3.0),
                Rounding::same(1.5),
                thin,
            );
        }
        ChromeIcon::Paste => {
            painter.rect_stroke(
                rect.shrink2(egui::vec2(3.0, 2.0)),
                Rounding::same(1.5),
                thin,
            );
            painter.rect_filled(
                Rect::from_center_size(
                    rect.center_top() + egui::vec2(0.0, 2.0),
                    Vec2::new(6.0, 3.0),
                ),
                Rounding::same(1.0),
                color,
            );
        }
        ChromeIcon::Trash => draw_trash_place_icon(painter, rect.shrink(1.0), color),
        ChromeIcon::Pin => {
            painter.circle_stroke(rect.center_top() + egui::vec2(0.0, 4.0), 3.0, thin);
            painter.line_segment(
                [
                    rect.center() + egui::vec2(0.0, -1.0),
                    rect.center() + egui::vec2(0.0, 6.0),
                ],
                stroke,
            );
            painter.line_segment(
                [
                    rect.center() + egui::vec2(-4.0, 2.0),
                    rect.center() + egui::vec2(4.0, 2.0),
                ],
                thin,
            );
        }
        ChromeIcon::Rename => {
            painter.line_segment(
                [
                    rect.left_bottom() + egui::vec2(3.0, -3.0),
                    rect.right_top() + egui::vec2(-3.0, 3.0),
                ],
                stroke,
            );
            painter.line_segment(
                [
                    rect.left_bottom() + egui::vec2(2.0, -2.0),
                    rect.left_bottom() + egui::vec2(5.0, -1.0),
                ],
                thin,
            );
            painter.rect_stroke(
                Rect::from_min_size(
                    rect.right_top() + egui::vec2(-5.0, 2.0),
                    Vec2::new(3.0, 3.0),
                ),
                Rounding::same(0.8),
                thin,
            );
        }
        ChromeIcon::Duplicate => {
            painter.rect_stroke(
                rect.translate(egui::vec2(-2.0, 2.0)).shrink(3.0),
                Rounding::same(1.5),
                thin,
            );
            painter.rect_stroke(
                rect.translate(egui::vec2(2.0, -2.0)).shrink(3.0),
                Rounding::same(1.5),
                thin,
            );
            draw_plus(
                painter,
                rect.right_bottom() + egui::vec2(-4.0, -4.0),
                2.5,
                color,
            );
        }
        ChromeIcon::Sort => {
            for index in 0..3 {
                let y = rect.top() + 3.0 + index as f32 * 4.5;
                painter.line_segment(
                    [
                        Pos2::new(rect.left() + 2.0, y),
                        Pos2::new(rect.right() - 2.0 - index as f32 * 3.0, y),
                    ],
                    thin,
                );
            }
        }
        ChromeIcon::Hidden => {
            painter.circle_stroke(rect.center(), 5.0, thin);
            painter.circle_filled(rect.center(), 2.0, color);
        }
        ChromeIcon::Link => {
            let left =
                Rect::from_center_size(rect.center() + egui::vec2(-3.2, 0.0), Vec2::new(6.0, 4.6));
            let right =
                Rect::from_center_size(rect.center() + egui::vec2(3.2, 0.0), Vec2::new(6.0, 4.6));
            painter.rect_stroke(left, Rounding::same(2.6), thin);
            painter.rect_stroke(right, Rounding::same(2.6), thin);
            painter.line_segment(
                [
                    rect.center() + egui::vec2(-2.0, 0.0),
                    rect.center() + egui::vec2(2.0, 0.0),
                ],
                stroke,
            );
        }
        ChromeIcon::Lock => {
            let body = Rect::from_min_max(
                rect.left_bottom() + egui::vec2(2.5, -7.0),
                rect.right_bottom() + egui::vec2(-2.5, -1.0),
            );
            painter.rect_stroke(body, Rounding::same(1.8), thin);
            painter.circle_stroke(rect.center_top() + egui::vec2(0.0, 5.5), 3.1, thin);
            painter.circle_filled(body.center(), 0.9, color);
        }
        ChromeIcon::List => {
            for index in 0..3 {
                let y = rect.top() + 3.0 + index as f32 * 4.5;
                painter.circle_filled(Pos2::new(rect.left() + 2.0, y), 1.0, color);
                painter.line_segment(
                    [
                        Pos2::new(rect.left() + 5.0, y),
                        Pos2::new(rect.right() - 1.0, y),
                    ],
                    thin,
                );
            }
        }
        ChromeIcon::Grid => {
            for x in 0..2 {
                for y in 0..2 {
                    let min =
                        rect.left_top() + egui::vec2(2.0 + x as f32 * 6.5, 2.0 + y as f32 * 6.5);
                    painter.rect_stroke(
                        Rect::from_min_size(min, Vec2::splat(4.5)),
                        Rounding::same(1.0),
                        thin,
                    );
                }
            }
        }
        ChromeIcon::Properties => draw_document_outline(painter, rect, color),
        ChromeIcon::Settings => {
            painter.circle_stroke(rect.center(), 4.0, thin);
            painter.circle_filled(rect.center(), 1.6, color);
            for angle in [
                0.0_f32,
                std::f32::consts::FRAC_PI_2,
                std::f32::consts::PI,
                std::f32::consts::PI + std::f32::consts::FRAC_PI_2,
            ] {
                let dir = egui::vec2(angle.cos(), angle.sin());
                painter.line_segment([rect.center() + dir * 5.0, rect.center() + dir * 7.0], thin);
            }
        }
    }
}

fn draw_plus(painter: &egui::Painter, center: Pos2, radius: f32, color: Color32) {
    let stroke = Stroke::new(1.4, color);
    painter.line_segment(
        [
            center + egui::vec2(-radius, 0.0),
            center + egui::vec2(radius, 0.0),
        ],
        stroke,
    );
    painter.line_segment(
        [
            center + egui::vec2(0.0, -radius),
            center + egui::vec2(0.0, radius),
        ],
        stroke,
    );
}

fn draw_document_outline(painter: &egui::Painter, rect: Rect, color: Color32) {
    let frame = rect.shrink2(egui::vec2(3.0, 1.5));
    painter.rect_stroke(frame, Rounding::same(1.8), Stroke::new(1.1, color));
    painter.line_segment(
        [
            Pos2::new(frame.left() + 3.0, frame.center().y),
            Pos2::new(frame.right() - 3.0, frame.center().y),
        ],
        Stroke::new(1.0, color),
    );
}

fn command_separator(ui: &mut egui::Ui, theme: AppPalette) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(8.0, 26.0), Sense::hover());
    ui.painter_at(rect).line_segment(
        [
            rect.center_top() + egui::vec2(0.0, 4.0),
            rect.center_bottom() + egui::vec2(0.0, -4.0),
        ],
        Stroke::new(1.0, theme.border()),
    );
}

fn context_menu_chrome(ui: &mut egui::Ui, theme: AppPalette, title: &str, detail: &str) {
    ui.set_min_width(246.0);
    ui.spacing_mut().item_spacing = Vec2::new(0.0, 3.0);
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 42.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let card = rect.shrink2(egui::vec2(1.0, 1.0));
    painter.rect_filled(
        card,
        Rounding::same(theme_rounding(theme) + 4.0),
        theme.elevated_fill(),
    );
    painter.rect_stroke(
        card,
        Rounding::same(theme_rounding(theme) + 4.0),
        Stroke::new(1.0, theme.hairline()),
    );
    painter.rect_filled(
        Rect::from_min_max(card.left_top(), card.left_bottom() + egui::vec2(3.0, 0.0)),
        Rounding::same(theme_rounding(theme) + 2.0),
        theme.accent(0.0),
    );
    painter.text(
        card.left_top() + egui::vec2(12.0, 8.0),
        Align2::LEFT_TOP,
        truncate_middle(title, 28),
        FontId::proportional(12.5),
        theme.text(),
    );
    painter.text(
        card.left_top() + egui::vec2(12.0, 25.0),
        Align2::LEFT_TOP,
        truncate_middle(detail, 30),
        FontId::proportional(10.0),
        theme.muted(),
    );
}

fn context_section(ui: &mut egui::Ui, theme: AppPalette, label: &str) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 18.0), Sense::hover());
    ui.painter_at(rect).text(
        rect.left_center() + egui::vec2(8.0, 0.0),
        Align2::LEFT_CENTER,
        label.to_ascii_uppercase(),
        FontId::monospace(9.0),
        theme.muted(),
    );
}

fn context_divider(ui: &mut egui::Ui, theme: AppPalette) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 8.0), Sense::hover());
    ui.painter_at(rect).line_segment(
        [
            rect.left_center() + egui::vec2(6.0, 0.0),
            rect.right_center() + egui::vec2(-6.0, 0.0),
        ],
        Stroke::new(1.0, theme.border()),
    );
}

fn context_action(
    ui: &mut egui::Ui,
    theme: AppPalette,
    icon: ChromeIcon,
    label: &str,
    hint: &str,
    enabled: bool,
    destructive: bool,
) -> bool {
    let sense = if enabled {
        Sense::click()
    } else {
        Sense::hover()
    };
    let (rect, response) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 30.0), sense);
    let painter = ui.painter_at(rect);
    let row = rect.shrink2(egui::vec2(2.0, 1.0));
    if enabled && response.hovered() {
        painter.rect_filled(
            row,
            Rounding::same(theme_rounding(theme) + 4.0),
            theme.hover_fill(),
        );
        painter.rect_stroke(
            row,
            Rounding::same(theme_rounding(theme) + 4.0),
            Stroke::new(1.0, theme.hairline()),
        );
    }
    let base_color = if destructive {
        Color32::from_rgb(255, 114, 108)
    } else if enabled {
        theme.text()
    } else {
        theme.muted()
    };
    let hint_color = if enabled {
        theme.muted()
    } else {
        Color32::from_rgba_unmultiplied(
            theme.muted().r(),
            theme.muted().g(),
            theme.muted().b(),
            120,
        )
    };
    draw_chrome_icon(
        &painter,
        Rect::from_center_size(row.left_center() + egui::vec2(14.0, 0.0), Vec2::splat(14.0)),
        icon,
        base_color,
    );
    painter.text(
        row.left_center() + egui::vec2(32.0, 0.0),
        Align2::LEFT_CENTER,
        label,
        FontId::proportional(12.0),
        base_color,
    );
    painter.text(
        row.right_center() + egui::vec2(-10.0, 0.0),
        Align2::RIGHT_CENTER,
        hint,
        FontId::monospace(9.5),
        hint_color,
    );
    enabled && response.clicked()
}

fn sidebar_section_header(ui: &mut egui::Ui, theme: AppPalette, title: &str, detail: &str) {
    ui.vertical(|ui| {
        ui.horizontal(|ui| {
            ui.label(
                RichText::new(title)
                    .size(11.5)
                    .strong()
                    .color(theme.secondary_text()),
            );
            ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                ui.label(RichText::new(detail).size(9.0).color(theme.muted()));
            });
        });
        let (rect, _) =
            ui.allocate_exact_size(Vec2::new(ui.available_width(), 1.0), Sense::hover());
        ui.painter_at(rect).line_segment(
            [rect.left_center(), rect.right_center()],
            Stroke::new(1.0, theme.hairline()),
        );
    });
}

fn sidebar_pin_empty_state(ui: &mut egui::Ui, theme: AppPalette) {
    egui::Frame::none()
        .fill(theme.elevated_fill())
        .stroke(Stroke::new(1.0, theme.hairline()))
        .rounding(Rounding::same(theme_rounding(theme) + 6.0))
        .inner_margin(egui::Margin::same(8.0))
        .show(ui, |ui| {
            ui.horizontal(|ui| {
                inline_chrome_icon(ui, theme, ChromeIcon::Pin, theme.accent(0.0));
                ui.vertical(|ui| {
                    ui.label(
                        RichText::new("No pinned folders")
                            .size(12.0)
                            .strong()
                            .color(theme.text()),
                    );
                    ui.label(
                        RichText::new("Pin current or drag folders here")
                            .size(10.5)
                            .color(theme.muted()),
                    );
                });
            });
        });
}

fn compact_section_icon(ui: &mut egui::Ui, theme: AppPalette, icon: ChromeIcon) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(34.0, 20.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let icon_rect = Rect::from_center_size(rect.center(), Vec2::splat(15.0));
    painter.rect_filled(
        Rect::from_center_size(
            rect.center_bottom() + egui::vec2(0.0, -3.0),
            Vec2::new(18.0, 4.0),
        ),
        Rounding::same(999.0),
        Color32::from_rgba_unmultiplied(
            theme.accent(0.0).r(),
            theme.accent(0.0).g(),
            theme.accent(0.0).b(),
            if theme.is_light() { 22 } else { 36 },
        ),
    );
    draw_chrome_icon(&painter, icon_rect, icon, theme.accent(0.0));
}

fn sidebar_meter(ui: &mut egui::Ui, theme: AppPalette, title: &str, detail: &str, value: f32) {
    let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 46.0), Sense::hover());
    let painter = ui.painter_at(rect);
    let label_pos = rect.left_top() + egui::vec2(4.0, 4.0);
    painter.text(
        label_pos,
        Align2::LEFT_TOP,
        title,
        FontId::proportional(12.0),
        theme.text(),
    );
    painter.text(
        rect.right_top() + egui::vec2(-4.0, 5.0),
        Align2::RIGHT_TOP,
        detail,
        FontId::proportional(10.0),
        theme.muted(),
    );
    let track = Rect::from_min_size(
        rect.left_bottom() + egui::vec2(4.0, -12.0),
        Vec2::new(rect.width() - 8.0, 4.0),
    );
    painter.rect_filled(track, Rounding::same(3.0), theme.hover_fill());
    let fill_width = track.width() * value.clamp(0.0, 1.0);
    if fill_width > 1.0 {
        painter.rect_filled(
            Rect::from_min_size(track.left_top(), Vec2::new(fill_width, track.height())),
            Rounding::same(3.0),
            theme.accent(0.0),
        );
    }
    ui.add_space(2.0);
}

fn compact_meter(painter: &egui::Painter, rect: Rect, theme: AppPalette, value: f32) {
    let body = Rect::from_center_size(rect.center(), Vec2::new(22.0, 28.0));
    painter.rect_filled(
        body,
        Rounding::same(theme_rounding(theme) + 4.0),
        theme.button_fill(),
    );
    painter.rect_stroke(
        body,
        Rounding::same(theme_rounding(theme) + 4.0),
        Stroke::new(1.0, theme.border()),
    );
    let track = body.shrink2(egui::vec2(6.0, 5.0));
    painter.rect_filled(track, Rounding::same(3.0), theme.hover_fill());
    let fill_height = track.height() * value.clamp(0.0, 1.0);
    if fill_height > 1.0 {
        painter.rect_filled(
            Rect::from_min_max(
                Pos2::new(track.left(), track.bottom() - fill_height),
                track.right_bottom(),
            ),
            Rounding::same(3.0),
            theme.accent(0.0),
        );
    }
}

fn ratio(value: usize, limit: usize) -> f32 {
    if limit == 0 {
        0.0
    } else {
        value.min(limit) as f32 / limit as f32
    }
}

fn theme_rounding(theme: AppPalette) -> f32 {
    match theme {
        AppPalette::NautilusLight | AppPalette::NautilusDark => 4.0,
        AppPalette::MacosLight | AppPalette::MacosDark => 6.0,
    }
}

fn folder_title(path: &Path) -> String {
    if path == home_dir() {
        return "Home".to_string();
    }
    path.file_name()
        .and_then(|name| name.to_str())
        .map(str::to_string)
        .unwrap_or_else(|| path.display().to_string())
}

fn title_case(value: &str) -> String {
    let mut words = Vec::new();
    for (index, word) in value.split_whitespace().enumerate() {
        words.push(title_word(word, index));
    }
    words.join(" ")
}

fn title_word(word: &str, index: usize) -> String {
    match word {
        "/" | "·" => return word.to_string(),
        "GPU" | "WGPU" | "CPU" | "USB" | "SSH" | "HTTP" | "HTTPS" | "DX12" | "N/A" => {
            return word.to_string()
        }
        "GLOW" => return "Glow".to_string(),
        "OPENGL" => return "OpenGL".to_string(),
        "GLES" => return "GLES".to_string(),
        "VSYNC" => return "VSync".to_string(),
        "GUTH" => return "Guth".to_string(),
        _ => {}
    }

    let lower = word.to_ascii_lowercase();
    if index > 0
        && matches!(
            lower.as_str(),
            "and" | "or" | "to" | "of" | "in" | "on" | "at" | "for"
        )
    {
        return lower;
    }

    let mut chars = lower.chars();
    if let Some(first) = chars.next() {
        format!("{}{}", first.to_uppercase(), chars.as_str())
    } else {
        String::new()
    }
}

fn pretty_value(value: &str) -> String {
    let normalized = value.replace(" // ", " · ").replace(" -> ", " to ");
    if normalized.chars().any(|ch| ch.is_lowercase()) {
        normalized
    } else {
        title_case(&normalized)
    }
}

fn pretty_status(value: &str) -> String {
    let normalized = value
        .replace(" // ", " · ")
        .replace(" ITEMS", " items")
        .replace(" ITEM", " item")
        .replace(" BYTES", " bytes");
    if normalized.chars().any(|ch| ch.is_lowercase()) {
        normalized
    } else {
        title_case(&normalized)
    }
}

fn window_frame(theme: AppPalette, opacity: f32) -> egui::Frame {
    egui::Frame::none()
        .fill(themed_alpha(theme.elevated_fill(), opacity))
        .stroke(Stroke::new(1.0, theme.hairline()))
        .rounding(Rounding::same(theme_rounding(theme) + 8.0))
        .inner_margin(egui::Margin::same(16.0))
}

fn load_background_image(path: &Path) -> Result<egui::ColorImage, String> {
    if !path.is_file() {
        return Err("Background image path is not a file".to_string());
    }
    let metadata = std::fs::metadata(path)
        .map_err(|error| format!("Background image metadata failed: {error}"))?;
    if metadata.len() > BACKGROUND_IMAGE_BYTES_LIMIT {
        return Err(format!(
            "Background image is too large: {} limit",
            human_size(BACKGROUND_IMAGE_BYTES_LIMIT)
        ));
    }
    let dimensions_reader = image::ImageReader::open(path)
        .map_err(|error| format!("Background image open failed: {error}"))?
        .with_guessed_format()
        .map_err(|error| format!("Background image format failed: {error}"))?;
    let (width, height) = dimensions_reader
        .into_dimensions()
        .map_err(|error| format!("Background image dimensions failed: {error}"))?;
    let pixels = u64::from(width).saturating_mul(u64::from(height));
    if pixels > BACKGROUND_IMAGE_PIXELS_LIMIT {
        return Err(format!(
            "Background image dimensions are too large: {} pixels limit",
            BACKGROUND_IMAGE_PIXELS_LIMIT
        ));
    }
    let image = image::ImageReader::open(path)
        .map_err(|error| format!("Background image open failed: {error}"))?
        .with_guessed_format()
        .map_err(|error| format!("Background image format failed: {error}"))?
        .decode()
        .map_err(|error| format!("Background image decode failed: {error}"))?
        .to_rgba8();
    let size = [image.width() as usize, image.height() as usize];
    Ok(egui::ColorImage::from_rgba_unmultiplied(
        size,
        image.as_raw(),
    ))
}

fn load_media_preview_image(path: &Path) -> Result<egui::ColorImage, String> {
    if !path.is_file() {
        return Err("Preview path is not a file".to_string());
    }
    let metadata = std::fs::metadata(path)
        .map_err(|error| format!("Preview image metadata failed: {error}"))?;
    if metadata.len() > MEDIA_PREVIEW_IMAGE_BYTES_LIMIT {
        return Err(format!(
            "Preview image is too large: {} limit",
            human_size(MEDIA_PREVIEW_IMAGE_BYTES_LIMIT)
        ));
    }
    let dimensions_reader = image::ImageReader::open(path)
        .map_err(|error| format!("Preview image open failed: {error}"))?
        .with_guessed_format()
        .map_err(|error| format!("Preview image format failed: {error}"))?;
    let (width, height) = dimensions_reader
        .into_dimensions()
        .map_err(|error| format!("Preview image dimensions failed: {error}"))?;
    let pixels = u64::from(width).saturating_mul(u64::from(height));
    if pixels > MEDIA_PREVIEW_IMAGE_PIXELS_LIMIT {
        return Err(format!(
            "Preview image dimensions are too large: {} pixels limit",
            MEDIA_PREVIEW_IMAGE_PIXELS_LIMIT
        ));
    }
    let mut image = image::ImageReader::open(path)
        .map_err(|error| format!("Preview image open failed: {error}"))?
        .with_guessed_format()
        .map_err(|error| format!("Preview image format failed: {error}"))?
        .decode()
        .map_err(|error| format!("Preview image decode failed: {error}"))?;
    if width.max(height) > MEDIA_PREVIEW_IMAGE_MAX_EDGE {
        image = image.thumbnail(MEDIA_PREVIEW_IMAGE_MAX_EDGE, MEDIA_PREVIEW_IMAGE_MAX_EDGE);
    }
    let image = image.to_rgba8();
    let size = [image.width() as usize, image.height() as usize];
    Ok(egui::ColorImage::from_rgba_unmultiplied(
        size,
        image.as_raw(),
    ))
}

fn load_text_preview(path: &Path) -> Result<TextPreviewDocument, String> {
    if !path.is_file() {
        return Err("Text preview path is not a file".to_string());
    }
    let metadata = std::fs::metadata(path)
        .map_err(|error| format!("Text preview metadata failed: {error}"))?;
    let mut file =
        std::fs::File::open(path).map_err(|error| format!("Text preview open failed: {error}"))?;
    let read_limit = TEXT_PREVIEW_BYTES_LIMIT.saturating_add(1);
    let capacity = metadata.len().min(read_limit).min(usize::MAX as u64) as usize;
    let mut bytes = Vec::with_capacity(capacity);
    file.by_ref()
        .take(read_limit)
        .read_to_end(&mut bytes)
        .map_err(|error| format!("Text preview read failed: {error}"))?;

    let mut truncated = metadata.len() > TEXT_PREVIEW_BYTES_LIMIT;
    if bytes.len() as u64 > TEXT_PREVIEW_BYTES_LIMIT {
        bytes.truncate(TEXT_PREVIEW_BYTES_LIMIT as usize);
        truncated = true;
    }
    if looks_like_binary(&bytes) {
        return Err("Text preview skipped: binary data detected".to_string());
    }

    let lossy = std::str::from_utf8(&bytes).is_err();
    let mut content = String::from_utf8_lossy(&bytes).into_owned();
    let total_lines = content.lines().count();
    if total_lines > TEXT_PREVIEW_LINE_LIMIT {
        content = content
            .lines()
            .take(TEXT_PREVIEW_LINE_LIMIT)
            .collect::<Vec<_>>()
            .join("\n");
        truncated = true;
    }
    let line_count = content.lines().count();
    let numbered_content = numbered_text(&content);

    Ok(TextPreviewDocument {
        content,
        numbered_content,
        bytes_read: bytes.len(),
        line_count,
        truncated,
        lossy,
    })
}

fn looks_like_binary(bytes: &[u8]) -> bool {
    let sample_len = bytes.len().min(4096);
    if sample_len == 0 {
        return false;
    }
    let sample = &bytes[..sample_len];
    if sample.contains(&0) {
        return true;
    }
    let control_count = sample
        .iter()
        .filter(|byte| matches!(**byte, 0x00..=0x08 | 0x0E..=0x1F | 0x7F))
        .count();
    control_count > sample_len / 12
}

fn numbered_text(content: &str) -> String {
    if content.is_empty() {
        return String::new();
    }

    let line_count = content.lines().count().max(1);
    let width = decimal_digits(line_count);
    let mut numbered = String::with_capacity(content.len() + line_count * (width + 3));
    for (index, line) in content.lines().enumerate() {
        use std::fmt::Write as _;
        let _ = writeln!(numbered, "{:>width$}  {line}", index + 1, width = width);
    }
    if !content.ends_with('\n') && numbered.ends_with('\n') {
        numbered.pop();
    }
    numbered
}

fn decimal_digits(value: usize) -> usize {
    let mut digits = 1;
    let mut value = value;
    while value >= 10 {
        value /= 10;
        digits += 1;
    }
    digits
}

fn count_text_matches(content: &str, query: &str) -> usize {
    let query = query.trim();
    if query.is_empty() {
        return 0;
    }
    let content = content.to_lowercase();
    let query = query.to_lowercase();
    content.matches(&query).count()
}

fn truncate_middle(value: &str, max_chars: usize) -> String {
    let count = value.chars().count();
    if count <= max_chars || max_chars < 5 {
        return value.to_string();
    }
    let head = (max_chars - 3) / 2;
    let tail = max_chars - 3 - head;
    let prefix: String = value.chars().take(head).collect();
    let suffix: String = value.chars().skip(count - tail).collect();
    format!("{prefix}...{suffix}")
}

fn usable_start_dir(path: PathBuf) -> PathBuf {
    if path.is_dir() {
        canonical_display_path(&path)
    } else if home_dir().is_dir() {
        home_dir()
    } else {
        PathBuf::from("/")
    }
}

fn load_preferences() -> Preferences {
    let mut preferences = Preferences::default();
    let Ok(file) = std::fs::File::open(preferences_path()) else {
        return preferences;
    };
    let reader = std::io::BufReader::new(file);

    for line in reader
        .lines()
        .map_while(Result::ok)
        .take(PREFERENCE_LINE_LIMIT)
    {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let Some((key, value)) = line.split_once('=') else {
            continue;
        };
        let key = key.trim();
        let value = value.trim();
        match key {
            "view_mode" => preferences.view_mode = ViewMode::from_config(value),
            "show_hidden" => preferences.show_hidden = parse_bool(value),
            "recursive_search" => preferences.recursive_search = parse_bool(value),
            "plugin.knotlook_media" => preferences.media_preview_plugin_enabled = parse_bool(value),
            "plugin.knotread_text" => preferences.text_preview_plugin_enabled = parse_bool(value),
            "sort_key" => preferences.sort_key = sort_key_from_config(value),
            "sort_reverse" => preferences.sort_reverse = parse_bool(value),
            "folders_first" => preferences.folders_first = parse_bool(value),
            "palette" => preferences.palette = AppPalette::from_config(value),
            "motion_enabled" => preferences.motion_enabled = parse_bool(value),
            "hover_effects_enabled" => preferences.hover_effects_enabled = parse_bool(value),
            "theme_opacity" => {
                preferences.theme_opacity = value
                    .parse::<f32>()
                    .map(|opacity| opacity.clamp(0.55, 1.0))
                    .unwrap_or(preferences.theme_opacity)
            }
            "background_image" => preferences.background_image_path = value.to_string(),
            "sidebar_collapsed" => preferences.sidebar_collapsed = parse_bool(value),
            "background_opacity" => {
                preferences.background_image_opacity = value
                    .parse::<f32>()
                    .map(|opacity| opacity.clamp(0.0, 0.85))
                    .unwrap_or(preferences.background_image_opacity)
            }
            _ => {}
        }
    }

    preferences
}

fn write_preferences(preferences: Preferences) -> std::io::Result<()> {
    let path = preferences_path();
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(
        path,
        format!(
            "view_mode={}\nshow_hidden={}\nrecursive_search={}\nplugin.knotlook_media={}\nplugin.knotread_text={}\nsort_key={}\nsort_reverse={}\nfolders_first={}\npalette={}\nmotion_enabled={}\nhover_effects_enabled={}\ntheme_opacity={:.2}\nbackground_image={}\nbackground_opacity={:.2}\nsidebar_collapsed={}\n",
            preferences.view_mode.as_config(),
            preferences.show_hidden,
            preferences.recursive_search,
            preferences.media_preview_plugin_enabled,
            preferences.text_preview_plugin_enabled,
            sort_key_as_config(preferences.sort_key),
            preferences.sort_reverse,
            preferences.folders_first,
            preferences.palette.as_config(),
            preferences.motion_enabled,
            preferences.hover_effects_enabled,
            preferences.theme_opacity,
            preferences.background_image_path,
            preferences.background_image_opacity,
            preferences.sidebar_collapsed,
        ),
    )
}

fn preferences_path() -> PathBuf {
    config_dir().join("preferences.conf")
}

fn bookmarks_path() -> PathBuf {
    config_dir().join("bookmarks.conf")
}

fn config_dir() -> PathBuf {
    config_home().join("guth")
}

fn config_home() -> PathBuf {
    std::env::var_os("XDG_CONFIG_HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|| home_dir().join(".config"))
}

fn migrate_legacy_config() {
    let current = config_dir();
    let legacy = config_home().join("knottfiles");
    if let Err(error) = migrate_config_files(&legacy, &current) {
        eprintln!("guth: could not migrate legacy configuration: {error}");
    }
}

fn migrate_config_files(legacy: &Path, current: &Path) -> std::io::Result<()> {
    if !legacy.is_dir() {
        return Ok(());
    }
    std::fs::create_dir_all(current)?;
    for name in ["preferences.conf", "bookmarks.conf"] {
        let source = legacy.join(name);
        let destination = current.join(name);
        if source.is_file() && !destination.exists() {
            std::fs::copy(source, destination)?;
        }
    }
    Ok(())
}

fn load_bookmarks() -> Vec<PathBuf> {
    let Ok(file) = std::fs::File::open(bookmarks_path()) else {
        return Vec::new();
    };
    let reader = std::io::BufReader::new(file);
    let mut bookmarks = Vec::new();
    let mut seen = BTreeSet::new();
    for line in reader.lines().map_while(Result::ok) {
        if bookmarks.len() >= BOOKMARK_LIMIT {
            break;
        }
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let path = canonical_display_path(&expand_tilde(line));
        if seen.insert(path.clone()) {
            bookmarks.push(path);
        }
    }
    bookmarks
}

fn write_bookmarks(bookmarks: &[PathBuf]) -> std::io::Result<()> {
    let path = bookmarks_path();
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    let mut content = String::new();
    for bookmark in bookmarks.iter().take(BOOKMARK_LIMIT) {
        content.push_str(&bookmark.display().to_string());
        content.push('\n');
    }
    std::fs::write(path, content)
}

fn bookmark_label(path: &Path) -> String {
    path.file_name()
        .and_then(|name| name.to_str())
        .map(str::to_string)
        .unwrap_or_else(|| path_for_display(path))
}

fn bookmark_position(bookmarks: &[PathBuf], path: &Path) -> Option<usize> {
    let normalized = canonical_display_path(path);
    bookmarks
        .iter()
        .position(|bookmark| canonical_display_path(bookmark) == normalized)
}

fn move_bookmark_path(
    bookmarks: &mut Vec<PathBuf>,
    path: &Path,
    direction: isize,
) -> Option<(usize, usize)> {
    if bookmarks.len() < 2 || direction == 0 {
        return None;
    }
    let from = bookmark_position(bookmarks, path)?;
    let to = (from as isize + direction).clamp(0, bookmarks.len() as isize - 1) as usize;
    if from == to {
        return None;
    }
    let bookmark = bookmarks.remove(from);
    bookmarks.insert(to, bookmark);
    Some((from, to))
}

fn parse_bool(value: &str) -> bool {
    matches!(value, "1" | "true" | "yes" | "on")
}

fn sort_key_as_config(key: SortKey) -> &'static str {
    match key {
        SortKey::Name => "name",
        SortKey::Modified => "modified",
        SortKey::Size => "size",
        SortKey::Kind => "kind",
    }
}

fn sort_key_from_config(value: &str) -> SortKey {
    match value {
        "modified" => SortKey::Modified,
        "size" => SortKey::Size,
        "kind" => SortKey::Kind,
        _ => SortKey::Name,
    }
}

fn expand_navigation_path(path: PathBuf, base: &Path) -> PathBuf {
    if path.is_absolute() {
        path
    } else {
        base.join(path)
    }
}

fn target_inside_any_source(sources: &[PathBuf], target_dir: &Path) -> bool {
    sources.iter().any(|source| target_dir.starts_with(source))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn bookmark_labels_use_leaf_or_display_path() {
        assert_eq!(
            bookmark_label(Path::new("/tmp/guth-project")),
            "guth-project"
        );
        assert_eq!(bookmark_label(Path::new("/")), "/");
    }

    #[test]
    fn dialogs_report_modal_state() {
        assert!(!Dialogs::default().is_open());
        assert!(Dialogs {
            new_file_open: true,
            ..Dialogs::default()
        }
        .is_open());
        assert!(Dialogs {
            error: Some("failed".to_string()),
            ..Dialogs::default()
        }
        .is_open());
    }

    #[test]
    fn rename_selection_preserves_file_extension() {
        assert_eq!(rename_selection_end("report.pdf", false), 6);
        assert_eq!(rename_selection_end("archive.tar.gz", false), 11);
        assert_eq!(rename_selection_end(".gitignore", false), 10);
        assert_eq!(
            rename_selection_end("folder.with.dots", true),
            "folder.with.dots".chars().count()
        );
    }

    #[test]
    fn folder_icon_fit_preserves_tall_proportions() {
        let source = Rect::from_min_size(Pos2::ZERO, Vec2::new(108.0, 74.0));
        let fitted = fit_rect_to_aspect(source, FOLDER_ICON_ASPECT);

        assert!((fitted.width() / fitted.height() - FOLDER_ICON_ASPECT).abs() < 0.001);
        assert_eq!(fitted.height(), source.height());
        assert!(fitted.width() < source.width());
    }

    #[test]
    fn text_preview_binary_detection_allows_plain_text() {
        assert!(!looks_like_binary(
            b"fn main() {\n    println!(\"hi\");\n}\n"
        ));
        assert!(looks_like_binary(b"PNG\0\0\0binary"));
    }

    #[test]
    fn text_preview_numbering_preserves_content_shape() {
        assert_eq!(numbered_text("alpha\nbeta"), "1  alpha\n2  beta");
        assert_eq!(numbered_text("alpha\nbeta\n"), "1  alpha\n2  beta\n");
    }

    #[test]
    fn text_preview_find_counts_case_insensitive_matches() {
        assert_eq!(count_text_matches("Alpha alpha ALPHA", "alpha"), 3);
        assert_eq!(count_text_matches("Alpha alpha ALPHA", ""), 0);
    }

    #[test]
    fn selection_summary_counts_known_file_bytes_only() {
        let entries = vec![
            test_entry("folder", EntryKind::Directory, 4096),
            test_entry("file-a", EntryKind::File, 120),
            test_entry("file-b", EntryKind::File, 30),
        ];

        assert_eq!(
            selection_summary(&entries),
            SelectionSummary {
                count: 3,
                folders: 1,
                files: 2,
                file_bytes: 150,
            }
        );
    }

    #[test]
    fn operation_results_only_focus_current_folder() {
        let paths = vec![
            PathBuf::from("/tmp/current/copied.txt"),
            PathBuf::from("/tmp/current/folder"),
            PathBuf::from("/tmp/elsewhere/hidden.txt"),
        ];
        assert_eq!(
            operation_results_in_directory(&paths, Path::new("/tmp/current")),
            paths[..2]
        );
    }

    #[test]
    fn initial_arrow_selection_does_not_skip_entries() {
        assert_eq!(stepped_selection_index(4, None, 1), Some(0));
        assert_eq!(stepped_selection_index(4, None, -1), Some(3));
        assert_eq!(stepped_selection_index(4, Some(1), 1), Some(2));
        assert_eq!(stepped_selection_index(0, None, 1), None);
    }

    #[test]
    fn legacy_configuration_is_copied_once() {
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_nanos();
        let root = std::env::temp_dir().join(format!("guth-config-migration-{nonce}"));
        let legacy = root.join("knottfiles");
        let current = root.join("guth");
        std::fs::create_dir_all(&legacy).expect("create legacy config");
        std::fs::write(legacy.join("preferences.conf"), "palette=nautilus_dark\n")
            .expect("write legacy preferences");
        std::fs::write(legacy.join("bookmarks.conf"), "/tmp\n").expect("write legacy bookmarks");

        migrate_config_files(&legacy, &current).expect("migrate config");
        assert_eq!(
            std::fs::read_to_string(current.join("preferences.conf")).unwrap_or_default(),
            "palette=nautilus_dark\n"
        );
        assert_eq!(
            std::fs::read_to_string(current.join("bookmarks.conf")).unwrap_or_default(),
            "/tmp\n"
        );
        std::fs::write(current.join("preferences.conf"), "kept=true\n")
            .expect("replace current preferences");
        std::fs::remove_file(current.join("bookmarks.conf")).expect("remove current bookmarks");
        migrate_config_files(&legacy, &current).expect("skip existing config");
        assert_eq!(
            std::fs::read_to_string(current.join("preferences.conf")).unwrap_or_default(),
            "kept=true\n"
        );
        assert_eq!(
            std::fs::read_to_string(current.join("bookmarks.conf")).unwrap_or_default(),
            "/tmp\n"
        );

        let _ = std::fs::remove_dir_all(root);
    }

    #[test]
    fn location_target_uses_folder_or_parent() {
        let fallback = Path::new("/tmp/base");
        let folder = test_entry("/tmp/base/folder", EntryKind::Directory, 0);
        let file = test_entry("/tmp/base/file.txt", EntryKind::File, 10);

        assert_eq!(
            location_target_for_entry(Some(&folder), fallback),
            folder.path
        );
        assert_eq!(
            location_target_for_entry(Some(&file), fallback),
            PathBuf::from("/tmp/base")
        );
        assert_eq!(location_target_for_entry(None, fallback), fallback);
    }

    #[test]
    fn bookmark_order_moves_within_bounds() {
        let mut bookmarks = vec![
            PathBuf::from("/tmp/a"),
            PathBuf::from("/tmp/b"),
            PathBuf::from("/tmp/c"),
        ];

        assert_eq!(
            move_bookmark_path(&mut bookmarks, Path::new("/tmp/b"), -1),
            Some((1, 0))
        );
        assert_eq!(
            bookmarks,
            vec![
                PathBuf::from("/tmp/b"),
                PathBuf::from("/tmp/a"),
                PathBuf::from("/tmp/c")
            ]
        );
        assert_eq!(
            move_bookmark_path(&mut bookmarks, Path::new("/tmp/b"), -1),
            None
        );
        assert_eq!(
            move_bookmark_path(&mut bookmarks, Path::new("/tmp/a"), 1),
            Some((1, 2))
        );
        assert_eq!(
            bookmarks,
            vec![
                PathBuf::from("/tmp/b"),
                PathBuf::from("/tmp/c"),
                PathBuf::from("/tmp/a")
            ]
        );
    }

    fn test_entry(name: &str, kind: EntryKind, size: u64) -> FileEntry {
        FileEntry {
            name: Path::new(name)
                .file_name()
                .and_then(|name| name.to_str())
                .unwrap_or(name)
                .to_string(),
            path: PathBuf::from(name),
            kind,
            size,
            modified: None,
            permissions: "rw".to_string(),
            readonly: false,
            hidden: false,
            symlink_target: None,
        }
    }
}
