use crate::tui::state::{FormCommand, FormEngine, FormState};
use crate::tui::view::{self, CompositeOverlay, HelpOverlayRender, UiContext};
use anyhow::{Result, anyhow};
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use jsonschema::Validator;
use ratatui::layout::Rect;
use serde_json::Value;
use std::{collections::HashMap, sync::Arc};

use super::{
    input::{AppCommand, CommandDispatch, InputRouter},
    keymap::{KeymapContext, KeymapStore},
    options::UiOptions,
    popup::PopupState,
    status::StatusLine,
    terminal::TerminalGuard,
    validation::{ValidationOutcome, validate_form},
};

mod list_ops;
mod overlay;

use overlay::CompositeEditorOverlay;

#[derive(Clone)]
pub(crate) enum PopupOwner {
    Root,
    Composite,
    /// Variant selector for composite list add_entry
    VariantSelector {
        field_pointer: String,
        overlay_host: Option<overlay::OverlayHost>,
    },
}

struct AppPopup {
    owner: PopupOwner,
    state: PopupState,
}

pub(crate) struct App {
    form_state: FormState,
    validator: Validator,
    options: UiOptions,
    status: StatusLine,
    global_errors: Vec<String>,
    validation_errors: usize,
    exit_armed: bool,
    should_quit: bool,
    result: Option<Value>,
    popup: Option<AppPopup>,
    overlay_stack: Vec<CompositeEditorOverlay>,
    overlay_validator_cache: HashMap<String, Arc<Validator>>,
    input_router: InputRouter,
    keymap_store: Arc<KeymapStore>,
    help_overlay: Option<HelpOverlayState>,
}

struct HelpOverlayState {
    pages: Vec<Vec<String>>,
    page: usize,
}

impl App {
    fn current_help_text(&self) -> Option<String> {
        if !self.options.show_help {
            return None;
        }
        let context = if self.overlay_depth() > 0 {
            KeymapContext::Overlay
        } else if let Some(field) = self.form_state.focused_field()
            && field.is_composite_list()
        {
            KeymapContext::Collection
        } else {
            KeymapContext::Default
        };
        self.keymap_store.help_text(context)
    }

    fn handle_popup_key(&mut self, key: KeyEvent) -> Result<bool> {
        if let Some(app_popup) = &mut self.popup {
            let popup = &mut app_popup.state;
            match key.code {
                KeyCode::Esc => {
                    self.popup = None;
                    self.status.ready();
                }
                KeyCode::Up => popup.select_previous(),
                KeyCode::Down => popup.select_next(),
                KeyCode::Char(' ') if popup.is_multi() => {
                    popup.toggle_current();
                    return Ok(true);
                }
                KeyCode::Enter => {
                    let (pointer, selection, multi_flags) = {
                        let pointer = popup.pointer().to_string();
                        let selection = popup.selection();
                        let multi_flags = popup.active().map(|flags| flags.to_vec());
                        (pointer, selection, multi_flags)
                    };
                    let owner = app_popup.owner.clone();
                    self.popup = None;
                    self.apply_popup_selection_data(owner, &pointer, selection, multi_flags);
                    if self.options.auto_validate {
                        self.run_validation(false);
                    }
                    self.status.value_updated();
                }
                _ => {}
            }
            return Ok(true);
        }
        Ok(false)
    }

    fn dispatch_form_command(&mut self, command: FormCommand) {
        let mut engine = FormEngine::new(&mut self.form_state, &self.validator);
        if let Err(message) = engine.dispatch(command) {
            self.status.set_raw(&message);
        }
        self.validation_errors = self.form_state.error_count();
    }

    fn handle_app_command(&mut self, command: AppCommand) -> bool {
        match command {
            AppCommand::Save => {
                self.exit_armed = false;
                if self.overlay_depth() > 0 {
                    let _ = self.save_active_overlay();
                } else {
                    self.on_save();
                }
            }
            AppCommand::Quit => {
                if self.overlay_depth() > 0 {
                    self.request_overlay_exit();
                    return true;
                }
                self.on_exit();
            }
            AppCommand::ResetStatus => {
                self.exit_armed = false;
                self.status.ready();
            }
            AppCommand::ShowHelp => {
                self.toggle_help_overlay();
                return true;
            }
            AppCommand::TogglePopup => {
                if self.try_open_popup(PopupOwner::Root) {
                    return true;
                }
            }
            AppCommand::EditComposite => {
                self.try_open_composite_editor();
            }
            AppCommand::ListAddEntry => {
                if self.handle_list_add_entry() {
                    return true;
                }
            }
            AppCommand::ListRemoveEntry => {
                if self.handle_list_remove_entry() {
                    return true;
                }
            }
            AppCommand::ListMove(delta) => {
                if self.handle_list_move_entry(delta) {
                    return true;
                }
            }
            AppCommand::ListSelect(delta) => {
                if self.handle_list_select_entry(delta) {
                    return true;
                }
            }
        }
        false
    }

    fn toggle_help_overlay(&mut self) {
        if self.help_overlay.is_some() {
            self.help_overlay = None;
            return;
        }

        let pages = self.build_help_overlay_pages();
        if pages.is_empty() {
            return;
        }

        // Help overlay is global and should not compete with other popups.
        self.popup = None;
        self.help_overlay = Some(HelpOverlayState { pages, page: 0 });
    }

    fn handle_help_overlay_key(&mut self, key: &KeyEvent) -> bool {
        let Some(state) = self.help_overlay.as_mut() else {
            return false;
        };

        match key.code {
            KeyCode::Esc => {
                self.help_overlay = None;
                true
            }
            KeyCode::Tab => {
                let total = state.pages.len();
                if total > 0 {
                    state.page = (state.page + 1) % total;
                }
                true
            }
            KeyCode::BackTab => {
                let total = state.pages.len();
                if total > 0 {
                    if state.page == 0 {
                        state.page = total - 1;
                    } else {
                        state.page -= 1;
                    }
                }
                true
            }
            _ => false,
        }
    }

    fn build_help_overlay_pages(&self) -> Vec<Vec<String>> {
        const MAX_ERROR_MSG_CHARS: usize = 80;
        const ERRORS_PER_PAGE: usize = 10;

        use crate::tui::app::keymap::KeymapContext;

        // --- Collect shortcut rows ---------------------------------------------------------
        struct ShortcutRow {
            ctx_label: &'static str,
            key: String,
            action: String,
        }

        let mut shortcuts: Vec<ShortcutRow> = Vec::new();
        let sections = [
            ("Default", KeymapContext::Default),
            ("Collection", KeymapContext::Collection),
            ("Overlay", KeymapContext::Overlay),
        ];

        for (label, ctx) in sections {
            if let Some(text) = self.keymap_store.help_text(ctx) {
                for snippet in text.split('•') {
                    let s = snippet.trim();
                    if s.is_empty() {
                        continue;
                    }
                    // Snippets are in the form "keys -> description".
                    let (keys, action) = match s.split_once("->") {
                        Some((k, a)) => (k.trim().to_string(), a.trim().to_string()),
                        None => (s.to_string(), String::new()),
                    };
                    shortcuts.push(ShortcutRow {
                        ctx_label: label,
                        key: keys,
                        action,
                    });
                }
            }
        }

        // --- Collect error rows ------------------------------------------------------------
        let mut errors: Vec<(String, String)> = Vec::new();
        for (pointer, message) in self.form_state.error_entries() {
            let short = truncate_with_ellipsis(&message, MAX_ERROR_MSG_CHARS);
            errors.push((pointer, short));
        }
        for msg in &self.global_errors {
            let short = truncate_with_ellipsis(msg, MAX_ERROR_MSG_CHARS);
            errors.push((String::new(), short));
        }

        let total_errors = errors.len();
        if total_errors == 0 && shortcuts.is_empty() {
            return Vec::new();
        }

        let total_pages = if total_errors == 0 {
            1
        } else {
            total_errors.div_ceil(ERRORS_PER_PAGE)
        };

        fn build_pages_header(current: usize, total: usize) -> String {
            if total <= 1 {
                return "Pages: [1]".to_string();
            }
            let mut parts = Vec::with_capacity(total);
            for idx in 0..total {
                let label = idx + 1;
                if idx == current {
                    parts.push(format!("[{label}]"));
                } else {
                    parts.push(label.to_string());
                }
            }
            format!("Pages: {}", parts.join(" | "))
        }

        let mut pages: Vec<Vec<String>> = Vec::with_capacity(total_pages.max(1));

        for page_idx in 0..total_pages {
            let mut lines: Vec<String> = Vec::new();

            // Keymap/table section
            lines.push("Keymap shortcuts".to_string());
            lines.push("CTX      | KEY                   | ACTION".to_string());
            lines.push("-----------------------------------------------".to_string());
            if shortcuts.is_empty() {
                lines.push("(no shortcuts available)".to_string());
            } else {
                for row in &shortcuts {
                    lines.push(format!(
                        "{:<8} | {:<20} | {}",
                        row.ctx_label, row.key, row.action
                    ));
                }
            }
            lines.push(String::new());

            // Errors section
            lines.push(format!("Errors ({} issues)", total_errors));
            if total_errors == 0 {
                lines.push("No field errors.".to_string());
            } else {
                lines.push(build_pages_header(page_idx, total_pages));
                lines.push("IDX  | POINTER                       | MESSAGE".to_string());
                lines.push("-----------------------------------------------------".to_string());

                let start = page_idx * ERRORS_PER_PAGE;
                let end = (start + ERRORS_PER_PAGE).min(total_errors);
                for (offset, (pointer, message)) in errors[start..end].iter().enumerate() {
                    let idx = start + offset + 1;
                    let ptr_display = if pointer.is_empty() {
                        "<global>".to_string()
                    } else {
                        pointer.clone()
                    };
                    lines.push(format!("{:<4} | {:<28} | {}", idx, ptr_display, message));
                }
            }

            lines.push(String::new());
            lines.push("Tab: next page  |  Shift+Tab: previous page  |  Esc: close".to_string());

            pages.push(lines);
        }

        pages
    }

    fn handle_field_input(&mut self, event: &KeyEvent) {
        if let Some(field) = self.form_state.focused_field_mut()
            && field.handle_key(event)
        {
            let pointer = field.schema.pointer.clone();
            self.exit_armed = false;
            self.status.editing(&field.schema.display_label());
            if self.options.auto_validate {
                self.dispatch_form_command(FormCommand::FieldEdited { pointer });
            }
        }
    }

    pub fn new(form_state: FormState, validator: Validator, options: UiOptions) -> Self {
        let keymap_store = options.keymap_store.clone();
        Self {
            form_state,
            validator,
            options,
            status: StatusLine::new(),
            global_errors: Vec::new(),
            validation_errors: 0,
            exit_armed: false,
            should_quit: false,
            result: None,
            popup: None,
            overlay_stack: Vec::new(),
            overlay_validator_cache: HashMap::new(),
            input_router: InputRouter::new(keymap_store.clone()),
            keymap_store,
            help_overlay: None,
        }
    }

    pub fn run(&mut self) -> Result<Value> {
        let mut terminal = TerminalGuard::new()?;
        while !self.should_quit {
            terminal.autoresize()?;
            terminal.draw(|frame| self.draw(frame))?;
            if !event::poll(self.options.tick_rate)? {
                continue;
            }
            match event::read()? {
                Event::Key(key) => self.handle_key(key)?,
                Event::Resize(width, height) => {
                    terminal.resize(Rect::new(0, 0, width, height))?;
                    continue;
                }
                Event::Mouse(_) => {}
                Event::FocusGained | Event::FocusLost | Event::Paste(_) => {}
            }
        }

        if let Some(value) = self.result.take() {
            Ok(value)
        } else {
            Err(anyhow!("user exited without saving"))
        }
    }

    fn draw(&mut self, frame: &mut ratatui::Frame<'_>) {
        let help = self.current_help_text();
        let form_dirty = self.form_state.is_dirty();

        let help_overlay_render = self.help_overlay.as_ref().and_then(|state| {
            if state.pages.is_empty() {
                return None;
            }
            let total = state.pages.len();
            let idx = state.page.min(total.saturating_sub(1));
            let lines = &state.pages[idx];
            Some(HelpOverlayRender { lines })
        });

        if let Some(editor) = self.overlay_stack.last_mut() {
            let child = editor
                .form_state()
                .focused_field()
                .map(|field| field.schema.display_label())
                .unwrap_or_else(|| "<none>".to_string());
            let focus_label = Some(format!("{} › {}", editor.field_label(), child));
            let overlay_meta = CompositeOverlay {
                title: editor.title().to_string(),
                description: editor.description().cloned(),
                dirty: editor.form_state().is_dirty(),
                instructions: editor.instructions().to_string(),
                list_entries: editor.entry_tabs_entries().map(|entries| entries.to_vec()),
                list_selected: editor.entry_tabs_selected(),
                entry_label: editor.entry_tabs_label().map(|label| label.to_string()),
                level: editor.level(),
            };
            let overlay_form_state = editor.form_state_mut();
            view::draw(
                frame,
                &mut self.form_state,
                Some(overlay_form_state),
                UiContext {
                    status_message: self.status.message(),
                    dirty: form_dirty,
                    error_count: self.validation_errors,
                    help: help.as_deref(),
                    global_errors: &self.global_errors,
                    focus_label,
                    popup: self.popup.as_ref().map(|popup| popup.state.as_render()),
                    composite_overlay: Some(overlay_meta),
                    help_overlay: help_overlay_render,
                },
            );
            return;
        }

        let focus_label = self
            .form_state
            .focused_field()
            .map(|field| field.schema.display_label());

        view::draw(
            frame,
            &mut self.form_state,
            None,
            UiContext {
                status_message: self.status.message(),
                dirty: form_dirty,
                error_count: self.validation_errors,
                help: help.as_deref(),
                global_errors: &self.global_errors,
                focus_label,
                popup: self.popup.as_ref().map(|popup| popup.state.as_render()),
                composite_overlay: None,
                help_overlay: help_overlay_render,
            },
        );
    }

    fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
        if key.kind != KeyEventKind::Press {
            return Ok(());
        }

        if self.handle_help_overlay_key(&key) {
            return Ok(());
        }

        if self.handle_popup_key(key)? {
            return Ok(());
        }

        if self.overlay_depth() > 0 {
            self.handle_composite_editor_key(key)?;
            return Ok(());
        }

        let dispatch = self
            .options
            .keymap
            .resolve(self.input_router.classify(&key));
        match dispatch {
            CommandDispatch::Form(command) => {
                self.dispatch_form_command(command);
                self.exit_armed = false;
            }
            CommandDispatch::App(command) => {
                if self.handle_app_command(command) {
                    return Ok(());
                }
            }
            CommandDispatch::Input(event) => {
                self.handle_field_input(&event);
            }
            CommandDispatch::None => {}
        }

        Ok(())
    }

    fn try_open_popup(&mut self, owner: PopupOwner) -> bool {
        if self.popup.is_some() {
            return true;
        }

        match &owner {
            PopupOwner::Root => {
                if let Some(field) = self.form_state.focused_field_mut() {
                    field.ensure_composite_list_popup_entry();
                }
            }
            PopupOwner::Composite => {
                if let Some(editor) = self.active_overlay_mut()
                    && let Some(field) = editor.form_state_mut().focused_field_mut()
                {
                    field.ensure_composite_list_popup_entry();
                }
            }
            PopupOwner::VariantSelector { .. } => {
                // Variant selector popup is created directly, no field preparation needed
            }
        }

        let field_opt = match &owner {
            PopupOwner::Root => self.form_state.focused_field(),
            PopupOwner::Composite => self
                .active_overlay()
                .and_then(|editor| editor.form_state().focused_field()),
            PopupOwner::VariantSelector { .. } => {
                // Variant selector popup doesn't use focused field
                return false;
            }
        };
        let Some(field) = field_opt else {
            return false;
        };
        if let Some(popup) = PopupState::from_field(field) {
            let message = if popup.is_multi() {
                "Use ↑/↓ to move, Space to toggle, Enter to apply"
            } else {
                "Use ↑/↓ and Enter to choose"
            };
            self.status.set_raw(message);
            self.popup = Some(AppPopup {
                owner,
                state: popup,
            });
            return true;
        }
        false
    }

    fn on_save(&mut self) {
        if let Some(value) = self.run_validation(true) {
            self.status
                .set_raw("Configuration saved. Press Ctrl+Q to exit.");
            self.result = Some(value);
            self.form_state.mark_clean();
            self.exit_armed = false;
        }
    }

    fn on_exit(&mut self) {
        // First attempt: validate the full form. If it passes, treat Ctrl+Q
        // as a final save-and-exit, even if the user never pressed Ctrl+S.
        if !self.exit_armed {
            if let Some(value) = self.run_validation(true) {
                self.result = Some(value);
                self.form_state.mark_clean();
                self.should_quit = true;
                self.exit_armed = false;
                return;
            }

            // Validation failed. When confirm_exit is enabled, require a
            // second Ctrl+Q to force exit without saving the current
            // (invalid) state. The last successfully validated result, if
            // any, is preserved.
            if self.options.confirm_exit {
                self.exit_armed = true;
                self.status.pending_exit();
                return;
            }

            // confirm_exit disabled: exit immediately without updating
            // result; callers will see "user exited without saving" unless a
            // previous successful save populated result.
            self.should_quit = true;
            return;
        }

        // Second Ctrl+Q after a failed validation: force exit without
        // changing result. This mirrors the behavior of quitting without a
        // successful save.
        self.should_quit = true;
    }

    fn run_validation(&mut self, announce: bool) -> Option<Value> {
        match validate_form(&mut self.form_state, &self.validator) {
            ValidationOutcome::Valid(value) => {
                self.global_errors.clear();
                self.validation_errors = 0;
                if announce {
                    self.status.validation_passed();
                }
                Some(value)
            }
            ValidationOutcome::Invalid {
                issues,
                global_errors,
            } => {
                self.global_errors = global_errors;
                self.validation_errors = issues;
                if announce {
                    self.status.issues_remaining(issues);
                }
                None
            }
            ValidationOutcome::BuildError { message } => {
                self.global_errors = vec![message.clone()];
                self.validation_errors = 1;
                self.status.set_raw(message);
                None
            }
        }
    }
}

fn truncate_with_ellipsis(s: &str, max_chars: usize) -> String {
    if max_chars == 0 {
        return String::new();
    }
    let mut result = String::new();
    let mut count = 0usize;
    for ch in s.chars() {
        if count + 1 >= max_chars {
            break;
        }
        result.push(ch);
        count += 1;
    }
    if s.chars().count() > count {
        result.push('…');
    }
    result
}

#[cfg(test)]
impl App {
    pub(crate) fn form_state_mut_for_test(&mut self) -> &mut FormState {
        &mut self.form_state
    }

    pub(crate) fn handle_key_for_test(&mut self, key: KeyEvent) -> Result<()> {
        self.handle_key(key)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{schema::build_form_schema, tui::app::options::UiOptions};
    use jsonschema::validator_for;
    use serde_json::json;

    fn app_with_single_field() -> App {
        let schema = json!({
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            }
        });
        let form_schema = build_form_schema(&schema).expect("schema");
        let form_state = FormState::from_schema(&form_schema);
        let validator = validator_for(&schema).expect("validator");
        App::new(form_state, validator, UiOptions::default())
    }

    #[test]
    fn exit_after_save_keeps_last_saved_result() {
        let mut app = app_with_single_field();
        app.on_save();
        assert!(app.result.is_some(), "save should populate result");
        assert_eq!(
            app.status.message(),
            "Configuration saved. Press Ctrl+Q to exit."
        );
        assert!(
            !app.form_state.is_dirty(),
            "successful save should clear dirty flags"
        );
        assert!(!app.exit_armed, "save should reset exit confirmation");
        app.on_exit();
        assert!(app.result.is_some(), "exiting should keep last saved value");
        assert!(app.should_quit, "exit flag should be set");
    }

    #[test]
    fn exit_without_explicit_save_validates_and_sets_result() {
        let mut app = app_with_single_field();
        app.on_exit();
        assert!(
            app.result.is_some(),
            "Ctrl+Q should validate and produce a result for a valid form"
        );
    }
}
