//! Extension configuration view.
//!
//! Layout (no borders — uses background colour to separate panels):
//!
//!   Extensions (30%)          README + status (70%)
//!   ──────────────────────────────────────────────────
//!    Extensions                oo-rust  [Global] [Project] [Auto] [Disabled]
//!    oo-cpp                   README content…
//!    oo-example
//!    oo-rust ◀ selected

use std::cell::Cell;
use std::io::Read;
use std::path::PathBuf;

use ratatui::{
    Frame,
    layout::{Constraint, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Paragraph, Wrap},
};

use crate::prelude::*;

use input::{Key, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
use operation::{Event, ExtensionConfigOp, Operation};
use settings::Settings;
use crate::theme::Theme;
use views::View;
use crate::interraction::button::hit_test;

const STATUS_LABELS: &[&str] = &["Global", "Project", "Auto", "Disabled"];
const STATUS_COLORS: &[Color] = &[Color::Cyan, Color::Green, Color::Yellow, Color::Red];

#[derive(Debug)]
pub struct ExtensionConfigView {
    extensions: Vec<ExtensionInfo>,
    selected_index: usize,
    selected_status: usize,
    pub pending_save: bool,
    // Interior-mutable rects updated during render for mouse hit-testing.
    button_rects: [Cell<Rect>; 4],
    list_rect: Cell<Rect>,
}

#[derive(Debug, Clone)]
struct ExtensionInfo {
    name: String,
    version: String,
    readme: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtensionStatus {
    Global,
    Project,
    Auto,
    Disabled,
}

impl ExtensionConfigView {
    // ExtensionConfigView save_state helper will be called via the View trait impl.

    pub fn new(settings: &Settings) -> Self {
        let extensions = load_extensions_info(settings);
        let mut view = Self {
            extensions,
            selected_index: 0,
            selected_status: 2, // Auto
            pending_save: false,
            button_rects: [
                Cell::new(Rect::default()),
                Cell::new(Rect::default()),
                Cell::new(Rect::default()),
                Cell::new(Rect::default()),
            ],
            list_rect: Cell::new(Rect::default()),
        };
        // Initialize selected status based on current config if any
        if !view.extensions.is_empty() {
            view.select_extension(0, settings);
        }
        view
    }

    /// Return the currently selected extension's name (if any).
    pub fn selected_extension_name(&self) -> Option<String> {
        self.extensions.get(self.selected_index).map(|e| e.name.clone())
    }

    fn select_extension(&mut self, index: usize, settings: &Settings) {
        if index >= self.extensions.len() {
            return;
        }
        self.selected_index = index;

        // Determine current persisted status by checking project then global config
        let name = &self.extensions[index].name;
        let mut status_idx = 3; // Default: Disabled

        // Check project-local .oo/extension.yaml first
        let project_path = settings.project_config_dir().join("extension.yaml");
        if project_path.exists()
            && let Ok(data) = std::fs::read_to_string(&project_path)
                && let Ok(map) = serde_saphyr::from_str::<std::collections::HashMap<String, String>>(&data)
                    && let Some(val) = map.get(name) {
                        match val.as_str() {
                            "enabled" => status_idx = 1, // Project
                            "auto" => status_idx = 2,
                            "disabled" => status_idx = 3,
                            _ => status_idx = 3,
                        }
                        self.selected_status = status_idx;
                        return;
                    }

        // Fallback: check global config
        let global_path = settings.global_config_dir().join("extension.yaml");
        if global_path.exists()
            && let Ok(data) = std::fs::read_to_string(&global_path)
                && let Ok(map) = serde_saphyr::from_str::<std::collections::HashMap<String, String>>(&data)
                    && let Some(val) = map.get(name) {
                        match val.as_str() {
                            "enabled" => status_idx = 0, // Global
                            "auto" => status_idx = 2,
                            "disabled" => status_idx = 3,
                            _ => status_idx = 3,
                        }
                    }

        self.selected_status = status_idx;
    }

    fn set_status(&mut self, status: ExtensionStatus) {
        self.selected_status = match status {
            ExtensionStatus::Global => 0,
            ExtensionStatus::Project => 1,
            ExtensionStatus::Auto => 2,
            ExtensionStatus::Disabled => 3,
        };
        if self.selected_index < self.extensions.len() {
            let ext_name = self.extensions[self.selected_index].name.clone();
            self.pending_save = true;
            log::info!("Setting extension '{}' status to '{:?}'", ext_name, status);
        }
    }

    fn render_content(&self, f: &mut Frame, area: Rect, theme: &Theme) {
        let [left_area, right_area] = Layout::horizontal([
            Constraint::Percentage(30),
            Constraint::Percentage(70),
        ]).areas(area);

        self.render_extension_list(f, left_area, theme);
        self.render_right_panel(f, right_area, theme);
    }

    fn render_extension_list(&self, f: &mut Frame, area: Rect, theme: &Theme) {
        let left_bg = theme.bg_inactive();
        f.render_widget(Block::default().style(Style::new().bg(left_bg)), area);

        // Title row
        let title_area = Rect::new(area.x, area.y, area.width, 1);
        f.render_widget(
            Paragraph::new(Span::styled(
                " Extensions",
                Style::new().fg(theme.fg_dim()).bg(left_bg).add_modifier(Modifier::BOLD),
            )),
            title_area,
        );

        let list_area = Rect::new(area.x, area.y + 1, area.width, area.height.saturating_sub(1));
        // remember the list area for mouse hit-testing
        self.list_rect.set(list_area);

        if self.extensions.is_empty() {
            f.render_widget(
                Paragraph::new(Span::styled(
                    " No extensions found",
                    Style::new().fg(theme.fg_dim()).bg(left_bg),
                )),
                list_area,
            );
            return;
        }

        for (i, ext) in self.extensions.iter().enumerate() {
            let y = list_area.y + i as u16;
            if y >= list_area.y + list_area.height {
                break;
            }
            let (fg, bg) = if i == self.selected_index {
                (theme.selection_fg(), theme.selection_bg())
            } else {
                (theme.fg_active(), left_bg)
            };
            f.render_widget(
                Paragraph::new(Span::styled(
                    format!(" {} v{}", ext.name, ext.version),
                    Style::new().fg(fg).bg(bg),
                )),
                Rect::new(list_area.x, y, list_area.width, 1),
            );
        }
    }

    fn render_right_panel(&self, f: &mut Frame, area: Rect, theme: &Theme) {
        let right_bg = theme.bg_active();
        f.render_widget(Block::default().style(Style::new().bg(right_bg)), area);

        let [header_area, readme_area] = Layout::vertical([
            Constraint::Length(1),
            Constraint::Min(0),
        ]).areas(area);

        self.render_status_header(f, header_area, theme, right_bg);
        self.render_readme(f, readme_area, right_bg);
    }

    fn render_status_header(&self, f: &mut Frame, area: Rect, theme: &Theme, bg: Color) {
        let ext_name = self.extensions.get(self.selected_index)
            .map(|e| e.name.as_str())
            .unwrap_or("");

        let mut spans = vec![Span::styled(
            format!(" {}  ", ext_name),
            Style::new().fg(theme.accent()).bg(bg).add_modifier(Modifier::BOLD),
        )];

        let mut x = area.x + 1 + ext_name.len() as u16 + 2;

        for (i, (label, color)) in STATUS_LABELS.iter().zip(STATUS_COLORS.iter()).enumerate() {
            let is_selected = i == self.selected_status;
            let width = (label.len() + 2) as u16;
            self.button_rects[i].set(Rect::new(x, area.y, width, 1));

            if is_selected {
                spans.push(Span::styled(
                    format!(" {} ", label),
                    Style::new().fg(Color::Black).bg(*color).add_modifier(Modifier::BOLD),
                ));
            } else {
                spans.push(Span::styled(
                    format!(" {} ", label),
                    Style::new().fg(*color).bg(bg).add_modifier(Modifier::DIM),
                ));
            }
            spans.push(Span::styled(" ", Style::new().bg(bg)));
            x += width + 1;
        }

        f.render_widget(Paragraph::new(Line::from(spans)), area);
    }

    fn render_readme(&self, f: &mut Frame, area: Rect, bg: Color) {
        let content = self.extensions.get(self.selected_index)
            .map(|e| e.readme.as_str())
            .unwrap_or("Select an extension to view its README");

        f.render_widget(
            Paragraph::new(content)
                .style(Style::new().bg(bg))
                .wrap(Wrap { trim: false }),
            area,
        );
    }

    fn hit_button(&self, col: u16, row: u16) -> Option<usize> {
        for (i, cell) in self.button_rects.iter().enumerate() {
            let rect = cell.get();
            if rect.width > 0 && hit_test((col, row), rect) {
                return Some(i);
            }
        }
        None
    }
}

fn load_extensions_info(settings: &Settings) -> Vec<ExtensionInfo> {
    let mut extensions = Vec::new();

    let ext_dir = get_extensions_dir(settings);

    if ext_dir.exists() {
        if let Ok(entries) = std::fs::read_dir(&ext_dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.extension().and_then(|e| e.to_str()) != Some("cyix") {
                    continue;
                }
                if let Some(ext) = load_extension_from_cyix(&path) {
                    extensions.push(ext);
                }
            }
        }
    } else {
        log::debug!("extensions dir {:?} does not exist", ext_dir);
    }

    extensions.sort_by(|a, b| a.name.cmp(&b.name));
    extensions
}

fn get_extensions_dir(settings: &Settings) -> PathBuf {
    let configured = settings.get::<String>("extensions.extensions_dir");
    if !configured.is_empty() {
        return PathBuf::from(configured);
    }
    directories::ProjectDirs::from("com", "cyloncore", "oo")
        .map(|d| d.data_dir().join("extensions"))
        .unwrap_or_else(|| PathBuf::from("extensions"))
}

fn load_extension_from_cyix(path: &std::path::Path) -> Option<ExtensionInfo> {
    use crate::extension::loader::load_cyix;
    let pkg = load_cyix(path)
        .map_err(|e| log::warn!("failed to read {:?}: {}", path, e))
        .ok()?;

    let readme = read_readme_from_cyix(path)
        .unwrap_or_else(|| pkg.manifest.description.clone());

    Some(ExtensionInfo {
        name: pkg.manifest.name,
        version: pkg.manifest.version,
        readme,
    })
}

fn read_readme_from_cyix(path: &std::path::Path) -> Option<String> {
    let file = std::fs::File::open(path).ok()?;
    let mut archive = zip::ZipArchive::new(file).ok()?;
    for i in 0..archive.len() {
        if let Ok(mut entry) = archive.by_index(i) {
            let name = entry.name().to_owned();
            if name.eq_ignore_ascii_case("readme.md") || name.eq_ignore_ascii_case("readme.txt") {
                let mut s = String::new();
                if entry.read_to_string(&mut s).is_ok() {
                    return Some(s);
                }
            }
        }
    }
    None
}

impl View for ExtensionConfigView {
    const KIND: crate::views::ViewKind = crate::views::ViewKind::Modal;
    fn save_state(&mut self, app: &mut crate::app_state::AppState) {
        crate::views::save_state::extension_config_pre_save(self, app);
        // If pending_save is set, write project/global config here. The view owns
        // details on where to persist; the helper logs the state.
        if self.pending_save {
            // Persist per-project selection in settings.project_config_dir()/extension.yaml
            // Minimal best-effort write: map[name] = status
            let mut map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
            for ext in &self.extensions {
                // default to "auto" unless selected differently
                map.insert(ext.name.clone(), match self.selected_status {
                    0 => "enabled".to_string(),
                    1 => "enabled".to_string(),
                    2 => "auto".to_string(),
                    3 => "disabled".to_string(),
                    _ => "disabled".to_string(),
                });
            }
            if let Ok(y) = serde_saphyr::to_string(&map) {
                let path = app.settings.project_config_dir().join("extension.yaml");
                if let Some(parent) = path.parent() {
                    let _ = std::fs::create_dir_all(parent);
                }
                let _ = std::fs::write(&path, y);
            }
            self.pending_save = false;
        }
    }
    fn handle_key(&self, key: KeyEvent) -> Vec<Operation> {
        match (key.modifiers, key.key) {
            
            (_, Key::ArrowUp) | (_, Key::Char('k')) => {
                vec![Operation::ExtensionConfig(ExtensionConfigOp::MoveUp)]
            }
            (_, Key::ArrowDown) | (_, Key::Char('j')) => {
                vec![Operation::ExtensionConfig(ExtensionConfigOp::MoveDown)]
            }
            (_, Key::ArrowLeft) => {
                let prev = if self.selected_status == 0 { 3 } else { self.selected_status - 1 };
                vec![Operation::ExtensionConfig(ExtensionConfigOp::SetStatus(prev))]
            }
            (_, Key::ArrowRight) | (_, Key::Enter) => {
                let next = (self.selected_status + 1) % 4;
                vec![Operation::ExtensionConfig(ExtensionConfigOp::SetStatus(next))]
            }
            _ => vec![],
        }
    }

    fn handle_mouse(&self, mouse: MouseEvent) -> Vec<Operation> {
        if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
            if let Some(btn_idx) = self.hit_button(mouse.column, mouse.row) {
                return vec![Operation::ExtensionConfig(ExtensionConfigOp::SetStatus(btn_idx))];
            }
            // Check clicks inside the extensions list
            let rect = self.list_rect.get();
            if rect.width > 0 && hit_test((mouse.column, mouse.row), rect) {
                let idx = (mouse.row - rect.y) as usize;
                return vec![Operation::ExtensionConfig(ExtensionConfigOp::Select(idx))];
            }
        }
        vec![]
    }

    fn handle_operation(&mut self, op: &Operation, _settings: &Settings) -> Option<Event> {
        match op {
            Operation::ExtensionConfig(ext_op) => {
                match ext_op {
                    ExtensionConfigOp::MoveUp => {
                        if self.selected_index > 0 {
                            self.select_extension(self.selected_index - 1, _settings);
                        }
                        Some(Event::applied("extension_config", op.clone()))
                    }
                    ExtensionConfigOp::MoveDown => {
                        if self.selected_index < self.extensions.len().saturating_sub(1) {
                            self.select_extension(self.selected_index + 1, _settings);
                        }
                        Some(Event::applied("extension_config", op.clone()))
                    }
                    ExtensionConfigOp::Select(idx) => {
                        if *idx < self.extensions.len() {
                            self.select_extension(*idx, _settings);
                        }
                        Some(Event::applied("extension_config", op.clone()))
                    }
                    ExtensionConfigOp::SetStatus(idx) => {
                        if *idx < 4 {
                            let status = match idx {
                                0 => ExtensionStatus::Global,
                                1 => ExtensionStatus::Project,
                                2 => ExtensionStatus::Auto,
                                _ => ExtensionStatus::Disabled,
                            };
                            self.set_status(status);
                        }
                        Some(Event::applied("extension_config", op.clone()))
                    }
                }
            }
            _ => None,
        }
    }

    fn render(&self, f: &mut Frame, area: Rect, theme: &Theme) {
        self.render_content(f, area, theme);
    }
}
