//! egui-viewer spike
//!
//! Goal: prove egui can deliver the ristretto core experience.
//! - Open a folder of images
//! - Display the current image scaled to fit
//! - Scroll wheel = prev/next photo
//! - Arrow keys also navigate (hjkl + arrows)
//! - Dark theme (refined, not egui's default bluish dark)
//!
//! Non-goals for the spike: thumbnail bar, zoom/pan, right-click menu, fullscreen.

use std::path::{Path, PathBuf};
use std::sync::Arc;

use eframe::egui;
use egui::Color32;

fn main() -> eframe::Result<()> {
    let opts = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_inner_size([1200.0, 800.0])
            .with_title("image-viewer (egui spike)"),
        ..Default::default()
    };
    eframe::run_native(
        "image-viewer (egui spike)",
        opts,
        Box::new(|_cc| Ok(Box::new(Viewer::default()))),
    )
}

#[derive(Default)]
struct Viewer {
    images: Vec<PathBuf>,
    current: usize,
    texture: Option<Arc<egui::TextureHandle>>,
    status: String,
    loading: bool,
    pending_pick: Option<std::thread::JoinHandle<Option<PathBuf>>>,
}

impl eframe::App for Viewer {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        self.update_ui(ctx);
    }
}

impl Viewer {
    fn update_ui(&mut self, ctx: &egui::Context) {
        // Poll for any pending folder pick
        if let Some(handle) = self.pending_pick.take() {
            if !handle.is_finished() {
                self.pending_pick = Some(handle);
                ctx.request_repaint_after(std::time::Duration::from_millis(50));
            } else if let Ok(Some(path)) = handle.join() {
                self.load_folder(path);
                if !self.images.is_empty() {
                    self.current = 0;
                    self.load_current(ctx);
                } else {
                    self.status = "No images in that folder".into();
                }
            }
        }

        // Keyboard shortcuts (always active)
        ctx.input(|i| {
            let mut delta: i32 = 0;
            if i.key_pressed(egui::Key::ArrowRight) || i.key_pressed(egui::Key::L) {
                delta = 1;
            }
            if i.key_pressed(egui::Key::ArrowLeft) || i.key_pressed(egui::Key::H) {
                delta = -1;
            }
            if delta != 0 && !self.images.is_empty() {
                let len = self.images.len() as i32;
                let new = ((self.current as i32 + delta).rem_euclid(len)) as usize;
                if new != self.current {
                    self.current = new;
                    self.load_current(ctx);
                }
            }
            if i.key_pressed(egui::Key::O) {
                self.open_folder_dialog();
            }
            if i.key_pressed(egui::Key::Q) {
                std::process::exit(0);
            }

            // Scroll wheel = prev/next (ristretto-style, natural scrolling)
            let scroll = i.smooth_scroll_delta.y;
            if scroll.abs() > 4.0 && !self.images.is_empty() {
                let delta = if scroll > 0.0 { -1 } else { 1 };
                let len = self.images.len() as i32;
                let new = ((self.current as i32 + delta).rem_euclid(len)) as usize;
                if new != self.current {
                    self.current = new;
                    self.load_current(ctx);
                }
            }
        });

        // Top toolbar (thin, ristretto-style)
        egui::TopBottomPanel::top("toolbar")
            .exact_height(36.0)
            .show(ctx, |ui| {
                ui.horizontal_centered(|ui| {
                    ui.add_space(8.0);
                    if ui.button("Open folder").clicked() {
                        self.open_folder_dialog();
                    }
                    ui.separator();
                    if ui
                        .add_enabled(!self.images.is_empty(), egui::Button::new("◀"))
                        .clicked()
                    {
                        self.navigate(-1, ctx);
                    }
                    if ui
                        .add_enabled(!self.images.is_empty(), egui::Button::new("▶"))
                        .clicked()
                    {
                        self.navigate(1, ctx);
                    }
                    ui.separator();
                    ui.label(
                        egui::RichText::new(&self.status)
                            .color(Color32::from_rgb(180, 180, 185))
                            .size(13.0),
                    );
                });
            });

        // Bottom status bar
        egui::TopBottomPanel::bottom("status")
            .exact_height(24.0)
            .show(ctx, |ui| {
                ui.horizontal_centered(|ui| {
                    ui.add_space(8.0);
                    let count = if self.images.is_empty() {
                        "0/0".to_string()
                    } else {
                        format!("{}/{}", self.current + 1, self.images.len())
                    };
                    ui.label(
                        egui::RichText::new(count)
                            .color(Color32::from_rgb(140, 140, 145))
                            .size(11.0),
                    );
                });
            });

        // Central image area
        egui::CentralPanel::default()
            .frame(egui::Frame::none().fill(Color32::from_rgb(14, 14, 16)))
            .show(ctx, |ui| {
                if let Some(tex) = &self.texture {
                    let avail = ui.available_size();
                    let img_size = tex.size_vec2();
                    let scale = (avail.x / img_size.x)
                        .min(avail.y / img_size.y)
                        .min(1.0);
                    let display = img_size * scale;
                    ui.vertical_centered(|ui| {
                        ui.add_space((avail.y - display.y).max(0.0) / 2.0);
                        ui.image(egui::load::SizedTexture::new(
                            tex.as_ref().id(),
                            display,
                        ));
                    });
                } else {
                    ui.vertical_centered(|ui| {
                        ui.label(
                            egui::RichText::new(if self.loading {
                                "Loading..."
                            } else {
                                "No image (press O)"
                            })
                            .size(20.0)
                            .color(Color32::from_rgb(110, 110, 115)),
                        );
                    });
                }
            });
    }

    fn navigate(&mut self, delta: i32, ctx: &egui::Context) {
        if self.images.is_empty() {
            return;
        }
        let len = self.images.len() as i32;
        let new = ((self.current as i32 + delta).rem_euclid(len)) as usize;
        if new != self.current {
            self.current = new;
            self.load_current(ctx);
        }
    }

    fn open_folder_dialog(&mut self) {
        // Spawn a thread to avoid blocking the UI thread
        let handle = std::thread::spawn(|| {
            rfd::FileDialog::new()
                .set_title("Pick a folder of images")
                .pick_folder()
        });
        self.pending_pick = Some(handle);
        self.status = "Opening dialog...".into();
    }

    fn load_folder(&mut self, path: PathBuf) {
        let mut images: Vec<PathBuf> = Vec::new();
        if let Ok(entries) = std::fs::read_dir(&path) {
            for entry in entries.flatten() {
                let p = entry.path();
                if p.is_file() && is_supported_image(&p) {
                    images.push(p);
                }
            }
        }
        images.sort();
        self.images = images;
    }

    fn load_current(&mut self, ctx: &egui::Context) {
        let path = match self.images.get(self.current).cloned() {
            Some(p) => p,
            None => return,
        };
        self.loading = true;
        self.status = format!(
            "Loading {}",
            path.file_name()
                .map(|s| s.to_string_lossy())
                .unwrap_or_default()
        );

        // Synchronous decode for the spike — fine for local files.
        // Real impl will use a thread pool + channel back to UI thread.
        let bytes = match std::fs::read(&path) {
            Ok(b) => b,
            Err(e) => {
                self.loading = false;
                self.status = format!("Failed to read: {e}");
                return;
            }
        };
        let img = match image::load_from_memory(&bytes) {
            Ok(i) => i.to_rgba8(),
            Err(e) => {
                self.loading = false;
                self.status = format!("Decode failed: {e}");
                return;
            }
        };
        let size = [img.width() as usize, img.height() as usize];
        let color_image = egui::ColorImage {
            size,
            pixels: img
                .chunks(4)
                .map(|c| Color32::from_rgba_unmultiplied(c[0], c[1], c[2], c[3]))
                .collect(),
        };
        let tex = ctx.load_texture(
            "current_image",
            color_image,
            egui::TextureOptions::LINEAR,
        );
        self.texture = Some(Arc::new(tex));
        self.loading = false;
        self.status = format!(
            "{}  ({}/{})",
            path.file_name()
                .map(|s| s.to_string_lossy())
                .unwrap_or_default(),
            self.current + 1,
            self.images.len()
        );
    }
}

fn is_supported_image(p: &Path) -> bool {
    let ext = match p.extension().and_then(|e| e.to_str()) {
        Some(e) => e.to_ascii_lowercase(),
        None => return false,
    };
    matches!(
        ext.as_str(),
        "png" | "jpg" | "jpeg" | "gif" | "webp"
            | "bmp" | "ico" | "cur" | "avif"
            | "apng" | "jfif"
    )
}
