// SPDX-License-Identifier: GPL-3.0-or-later

use anyhow::Result;

use super::provider::BacklightProvider;
use super::{DisplayInfo, DisplayType};
use crate::caps::clamp_brightness;
use crate::display::ddc_ffi::DdcService;
use crate::settings::Settings;

/// Manages all detected displays.
pub struct DisplayManager<'a> {
    displays: Vec<InternalDisplay>,
    settings: Settings,
    backlight: &'a dyn BacklightProvider,
}

/// Internal display representation with enough info to route set operations.
struct InternalDisplay {
    pub info: DisplayInfo,
    /// For DDC: display number for DdcService. For backlight: name for set_brightness.
    pub backend_key: BackendKey,
}

pub enum BackendKey {
    Ddc(usize),
    Backlight(String),
}

impl<'a> DisplayManager<'a> {
    /// Detect displays using FFI display info (from DdcService) + backlight provider.
    /// Used by CLI for reliable detection matching the GUI path.
    pub fn detect_with_ffi(
        ffi_displays: &[crate::display::ddc_ffi::DdcDisplayInfo],
        backlight: &'a dyn BacklightProvider,
        settings: &Settings,
    ) -> Result<Self> {
        let mut displays = Vec::new();
        let mut index = 0;

        // Detect internal backlight displays first
        let bl_list = backlight.detect()?;
        let bl_count = bl_list.len();
        for bl in &bl_list {
            index += 1;
            let pct = if bl.max > 0 {
                bl.current as f64 / bl.max as f64 * 100.0
            } else {
                0.0
            };
            let custom_name = settings.get_custom_name(&format!("backlight:{}", bl.name));
            let display_name = custom_name.unwrap_or_else(|| {
                if bl_count == 1 {
                    "Built-in Display".to_string()
                } else {
                    format!("Backlight ({})", bl.name)
                }
            });
            displays.push(InternalDisplay {
                info: DisplayInfo {
                    index,
                    name: display_name,
                    display_type: DisplayType::Backlight,
                    brightness_pct: pct,
                    raw_current: bl.current as u64,
                    raw_max: bl.max as u64,
                },
                backend_key: BackendKey::Backlight(bl.name.clone()),
            });
        }

        // Use FFI display info (already detected by DdcService)
        for ffi_d in ffi_displays.iter() {
            index += 1;
            let pct = if ffi_d.max_brightness > 0 {
                ffi_d.current_brightness as f64 / ffi_d.max_brightness as f64 * 100.0
            } else {
                0.0
            };
            let custom_name = settings.get_custom_name(&format!("ddc:{}", ffi_d.display_num));
            displays.push(InternalDisplay {
                info: DisplayInfo {
                    index,
                    name: custom_name.unwrap_or_else(|| ffi_d.model_name.clone()),
                    display_type: DisplayType::Ddc,
                    brightness_pct: pct,
                    raw_current: ffi_d.current_brightness as u64,
                    raw_max: ffi_d.max_brightness as u64,
                },
                backend_key: BackendKey::Ddc(ffi_d.display_num as usize),
            });
        }

        Ok(Self {
            displays,
            settings: settings.clone(),
            backlight,
        })
    }

    pub fn displays(&self) -> Vec<&DisplayInfo> {
        self.displays.iter().map(|d| &d.info).collect()
    }

    pub fn count(&self) -> usize {
        self.displays.len()
    }

    /// Find the sequential index for a display by its backend key (e.g., "ddc:2", "backlight:intel").
    /// Returns None if no display matches.
    pub fn index_by_key(&self, key: &str) -> Option<usize> {
        for (i, d) in self.displays.iter().enumerate() {
            let display_key = match &d.backend_key {
                BackendKey::Ddc(num) => format!("ddc:{}", num),
                BackendKey::Backlight(name) => format!("backlight:{}", name),
            };
            if display_key == key {
                return Some(i + 1); // 1-based index
            }
        }
        None
    }

    /// Get brightness percentage of a display at 1-based index.
    pub fn brightness_at(&self, display_num: usize) -> Option<f64> {
        self.displays.get(display_num - 1).map(|d| d.info.brightness_pct)
    }

    /// Get the backend key string for a display at 1-based index.
    pub fn key_at(&self, display_num: usize) -> Option<String> {
        self.displays.get(display_num - 1).map(|d| match &d.backend_key {
            BackendKey::Ddc(n) => format!("ddc:{}", n),
            BackendKey::Backlight(n) => format!("backlight:{}", n),
        })
    }

    /// List all backend key strings in display order.
    pub fn all_keys(&self) -> Vec<String> {
        self.displays.iter().map(|d| match &d.backend_key {
            BackendKey::Ddc(n) => format!("ddc:{}", n),
            BackendKey::Backlight(n) => format!("backlight:{}", n),
        }).collect()
    }
    pub fn list(&self, json: bool) -> Result<()> {
        if self.displays.is_empty() {
            if json {
                println!("[]");
            } else {
                println!("No displays found.");
            }
            return Ok(());
        }

        if json {
            // JSON output for scripting
            print!("[");
            for (i, d) in self.displays.iter().enumerate() {
                if i > 0 { print!(","); }
                let type_str = match d.info.display_type {
                    super::DisplayType::Ddc => "ddc",
                    super::DisplayType::Backlight => "backlight",
                };
                print!(
                    r#"{{"index":{},"name":"{}","type":"{}","brightness":{:.1},"raw_current":{},"raw_max":{}}}"#,
                    d.info.index, d.info.name, type_str,
                    d.info.brightness_pct, d.info.raw_current, d.info.raw_max
                );
            }
            println!("]");
            return Ok(());
        }

        // Human-readable table
        let max_name = self.displays.iter()
            .map(|d| d.info.name.len())
            .max()
            .unwrap_or(0);
        for d in &self.displays {
            let pct = d.info.brightness_pct;
            let bar_len = 20;
            let filled = (pct / 100.0 * bar_len as f64).round() as usize;
            let bar: String = "\u{2588}".repeat(filled) + &"\u{2591}".repeat(bar_len - filled);
            let type_tag = match d.info.display_type {
                super::DisplayType::Ddc => "DDC/CI",
                super::DisplayType::Backlight => "BL   ",
            };
            println!(
                "  {:>2}  {:width$}  [{}]  {}  {:5.1}%",
                d.info.index, d.info.name, type_tag, bar, pct,
                width = max_name,
            );
        }
        Ok(())
    }

    /// Get brightness of a specific display.
    pub fn get(&self, display: usize) -> Result<()> {
        let d = self.display_by_index(display)?;
        println!("{:.1}%", d.info.brightness_pct);
        Ok(())
    }

    /// Set brightness of a display (or all if display is None).
    /// When setting all, individual hardware failures are logged but don't abort.
    pub fn set_with_service(&mut self, display: Option<usize>, percent: f64, service: Option<&DdcService>) -> Result<()> {
        let targets = self.resolve_targets(display)?;
        let is_all = display.is_none();

        for &idx in &targets {
            let clamped = self.apply_caps(idx, percent);
            if let Err(e) = self.set_via_service(idx, clamped, service) {
                if is_all {
                    log::warn!("Failed to set brightness for display {}: {}", idx, e);
                } else {
                    return Err(e);
                }
            }
        }
        Ok(())
    }

    /// Increase brightness of a display (or all if display is None).
    pub fn increase_with_service(&mut self, display: Option<usize>, percent: f64, service: Option<&DdcService>) -> Result<()> {
        let targets = self.resolve_targets(display)?;
        let is_all = display.is_none();

        for &idx in &targets {
            let current = self.displays[idx - 1].info.brightness_pct;
            let new_val = self.apply_caps(idx, current + percent);
            if let Err(e) = self.set_via_service(idx, new_val, service) {
                if is_all {
                    log::warn!("Failed to increase brightness for display {}: {}", idx, e);
                } else {
                    return Err(e);
                }
            }
        }
        Ok(())
    }

    /// Decrease brightness of a display (or all if display is None).
    pub fn decrease_with_service(&mut self, display: Option<usize>, percent: f64, service: Option<&DdcService>) -> Result<()> {
        let targets = self.resolve_targets(display)?;
        let is_all = display.is_none();

        for &idx in &targets {
            let current = self.displays[idx - 1].info.brightness_pct;
            let new_val = self.apply_caps(idx, current - percent);
            if let Err(e) = self.set_via_service(idx, new_val, service) {
                if is_all {
                    log::warn!("Failed to decrease brightness for display {}: {}", idx, e);
                } else {
                    return Err(e);
                }
            }
        }
        Ok(())
    }

    /// Rename a display and persist to config.
    pub fn rename(&mut self, display: usize, name: String) -> Result<()> {
        let d = self.display_by_index(display)?;
        let key = match &d.backend_key {
            BackendKey::Ddc(i) => format!("ddc:{}", i),
            BackendKey::Backlight(n) => format!("backlight:{}", n),
        };
        self.settings.set_custom_name(&key, &name);
        self.settings.save()?;
        self.displays[display - 1].info.name = name;
        println!(
            "Display {} renamed to \"{}\"",
            display,
            self.displays[display - 1].info.name
        );
        Ok(())
    }

    // --- Private helpers ---

    /// Set brightness routing through DdcService for DDC displays, or backlight directly.
    fn set_via_service(&mut self, display_idx: usize, percent: f64, service: Option<&DdcService>) -> Result<()> {
        let d = &self.displays[display_idx - 1];
        let raw = (percent / 100.0 * d.info.raw_max as f64).round() as u16;

        match &d.backend_key {
            BackendKey::Ddc(disp_num) => {
                if let Some(svc) = service {
                    svc.set_brightness(*disp_num as i32, raw, 0);
                    // DDC bus takes ~500ms per display. Wait for completion
                    // before sending the next command or shutting down.
                    std::thread::sleep(std::time::Duration::from_millis(600));
                } else {
                    anyhow::bail!("No DDC service available");
                }
            }
            BackendKey::Backlight(name) => {
                self.backlight.set_brightness(name, raw as u32)?;
            }
        }

        self.displays[display_idx - 1].info.brightness_pct = percent;
        self.displays[display_idx - 1].info.raw_current = raw as u64;
        Ok(())
    }

    fn display_by_index(&self, display: usize) -> Result<&InternalDisplay> {
        self.displays
            .get(display - 1)
            .ok_or_else(|| anyhow::anyhow!("Invalid display number: {}", display))
    }

    fn resolve_targets(&self, display: Option<usize>) -> Result<Vec<usize>> {
        match display {
            Some(idx) => {
                // Validate the display index exists
                if idx == 0 || idx > self.count() {
                    anyhow::bail!("Invalid display number: {}", idx);
                }
                Ok(vec![idx])
            }
            None => Ok((1..=self.count()).collect()),
        }
    }

    fn apply_caps(&self, display_idx: usize, value: f64) -> f64 {
        let key = match &self.displays.get(display_idx.saturating_sub(1)).map(|d| &d.backend_key) {
            Some(BackendKey::Ddc(n)) => format!("ddc:{}", n),
            Some(BackendKey::Backlight(n)) => format!("backlight:{}", n),
            None => return clamp_brightness(value, 0.0, 100.0),
        };
        let ds = self.settings.get_display(&key);
        clamp_brightness(value, ds.min_brightness, ds.max_brightness)
    }
}
