//! Scene viewer display settings with localStorage persistence.

use serde::{Deserialize, Serialize};

use crate::utils::LocalStoragePersist;

/// Reference resolution for enlarged props calculation.
pub const REFERENCE_WIDTH: f64 = 1920.0;
pub const REFERENCE_HEIGHT: f64 = 1080.0;

/// Base size for props and avatars in scene space.
pub const BASE_PROP_SIZE: f64 = 60.0;

/// Minimum zoom level (25%).
pub const ZOOM_MIN: f64 = 0.25;

/// Maximum zoom level (400%).
pub const ZOOM_MAX: f64 = 4.0;

/// Zoom step increment.
pub const ZOOM_STEP: f64 = 0.25;

/// Pan step in pixels for keyboard navigation.
pub const PAN_STEP: f64 = 50.0;

/// Minimum text em size (50%).
pub const TEXT_EM_MIN: f64 = 0.5;

/// Maximum text em size (200%).
pub const TEXT_EM_MAX: f64 = 2.0;

/// Text em size step increment.
pub const TEXT_EM_STEP: f64 = 0.1;

/// Calculate the minimum zoom level for pan mode.
///
/// - Large scenes: min zoom fills the viewport
/// - Small scenes: min zoom is 1.0 (native resolution, centered)
pub fn calculate_min_zoom(
    scene_width: f64,
    scene_height: f64,
    viewport_width: f64,
    viewport_height: f64,
) -> f64 {
    if scene_width <= 0.0 || scene_height <= 0.0 || viewport_width <= 0.0 || viewport_height <= 0.0
    {
        return 1.0;
    }

    let min_x = viewport_width / scene_width;
    let min_y = viewport_height / scene_height;
    // For large scenes: min zoom fills viewport
    // For small scenes: clamp to 1.0 (native resolution)
    min_x.max(min_y).min(1.0)
}

/// Scene viewer display settings.
///
/// These settings control how the scene is rendered and are persisted
/// to localStorage for user preference retention.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ViewerSettings {
    /// When true, canvas shows at native resolution with scrolling.
    /// When false, canvas scales to fit viewport (default).
    pub panning_enabled: bool,

    /// Zoom level (0.25 to 4.0). Only applicable when `panning_enabled` is true.
    pub zoom_level: f64,

    /// When true, props use reference scaling based on 1920x1080.
    /// Applies in both fit mode and pan mode.
    pub enlarge_props: bool,

    /// Saved horizontal scroll position for pan mode.
    pub scroll_x: f64,

    /// Saved vertical scroll position for pan mode.
    pub scroll_y: f64,

    /// Text size multiplier for display names, chat bubbles, and badges.
    /// Range: 0.5 to 2.0 (50% to 200%).
    pub text_em_size: f64,
}

impl Default for ViewerSettings {
    fn default() -> Self {
        Self {
            panning_enabled: false,
            zoom_level: 1.0,
            enlarge_props: true,
            scroll_x: 0.0,
            scroll_y: 0.0,
            text_em_size: 1.0,
        }
    }
}

// Implement LocalStoragePersist trait for automatic load/save
impl LocalStoragePersist for ViewerSettings {
    const STORAGE_KEY: &'static str = "chattyness_viewer_settings";
}

impl ViewerSettings {
    /// Calculate the effective prop size based on current settings.
    ///
    /// In pan mode without enlarge, returns base size * zoom level.
    /// In pan mode with enlarge, returns base size * reference_scale * zoom level.
    /// In fit mode with enlarge_props, returns size adjusted for reference resolution.
    /// Otherwise returns base size (caller should multiply by canvas scale).
    pub fn calculate_prop_size(&self, scene_width: f64, scene_height: f64) -> f64 {
        // Reference scale factor for "enlarge props" mode
        let ref_scale = (scene_width / REFERENCE_WIDTH).max(scene_height / REFERENCE_HEIGHT);

        if self.panning_enabled {
            if self.enlarge_props {
                BASE_PROP_SIZE * ref_scale * self.zoom_level
            } else {
                BASE_PROP_SIZE * self.zoom_level
            }
        } else if self.enlarge_props {
            // Reference scaling: ensure minimum size based on 1920x1080
            BASE_PROP_SIZE * ref_scale
        } else {
            // Default: base size (will be scaled by canvas scale factor)
            BASE_PROP_SIZE
        }
    }

    /// Adjust zoom level by a delta, clamping to valid range.
    ///
    /// If `min_zoom` is provided, uses that as the floor instead of `ZOOM_MIN`.
    pub fn adjust_zoom(&mut self, delta: f64) {
        self.zoom_level = (self.zoom_level + delta).clamp(ZOOM_MIN, ZOOM_MAX);
    }

    /// Adjust zoom level with a custom minimum.
    pub fn adjust_zoom_with_min(&mut self, delta: f64, min_zoom: f64) {
        let effective_min = min_zoom.max(ZOOM_MIN);
        self.zoom_level = (self.zoom_level + delta).clamp(effective_min, ZOOM_MAX);
    }

    /// Clamp zoom level to an effective minimum.
    pub fn clamp_zoom_min(&mut self, min_zoom: f64) {
        let effective_min = min_zoom.max(ZOOM_MIN);
        if self.zoom_level < effective_min {
            self.zoom_level = effective_min;
        }
    }

    /// Reset scroll position to origin.
    pub fn reset_scroll(&mut self) {
        self.scroll_x = 0.0;
        self.scroll_y = 0.0;
    }

    /// Adjust text em size by a delta, clamping to valid range.
    pub fn adjust_text_em(&mut self, delta: f64) {
        self.text_em_size = (self.text_em_size + delta).clamp(TEXT_EM_MIN, TEXT_EM_MAX);
    }
}
