use std::sync::Arc;
use std::time::{Duration, Instant};

use arboard::Clipboard;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use simd_json::OwnedValue as Value;
use unicode_width::UnicodeWidthStr;

use crate::error::FilterError;
use crate::filter;
use crate::loader::Loader;
use crate::text_buffer::TextBuffer;
use crate::ui::{self, RenderOptions};
use crate::worker::{EvalRequest, Worker};

const DEBOUNCE_DURATION: Duration = Duration::from_millis(250);

pub enum AppAction {
    Continue,
    Quit,
}

pub enum LoadingState {
    Loading,
    Ready,
    Failed(String),
}

pub struct App {
    input_values: Option<Arc<Vec<Value>>>,
    loading_state: LoadingState,
    loader: Option<Loader>,
    filter_buffer: TextBuffer,
    filter_error: Option<FilterError>,
    pending_filter_update: Option<Instant>,
    partial_eval_mode: bool,
    // Virtualized rendering: store values + line count instead of pre-rendered lines
    output_values: Vec<Value>,
    total_lines: usize,
    scroll_offset: usize,
    horizontal_offset: usize,
    viewport_height: usize,
    viewport_width: usize,
    show_help: bool,
    help_for_filter_mode: bool,
    help_scroll: usize,
    wrap_lines: bool,
    editing_filter: bool,
    raw_output: bool,
    slurp_mode: bool,
    compact_output: bool,
    eval_paused: bool,
    worker: Worker,
    eval_request_id: u64,
    pending_eval_id: Option<u64>,
    eval_started_at: Option<Instant>,
}

impl App {
    pub fn new(input_values: Vec<Value>) -> Self {
        let input_values = Arc::new(input_values);
        let worker = Worker::spawn();
        let filter_buffer = TextBuffer::new(".");

        // Initial evaluation (synchronous for startup)
        let filter_result = filter::evaluate_all(filter_buffer.text(), &input_values);
        let (output_values, filter_error) = match filter_result {
            Ok(values) => (values, None),
            Err(e) => (vec![], Some(e)),
        };
        let options = RenderOptions::default();
        let total_lines = ui::count_total_lines(&output_values, &options);

        Self {
            input_values: Some(input_values),
            loading_state: LoadingState::Ready,
            loader: None,
            filter_buffer,
            filter_error,
            pending_filter_update: None,
            partial_eval_mode: false,
            output_values,
            total_lines,
            scroll_offset: 0,
            horizontal_offset: 0,
            viewport_height: 0,
            viewport_width: 0,
            show_help: false,
            help_for_filter_mode: false,
            help_scroll: 0,
            wrap_lines: false,
            editing_filter: false,
            raw_output: false,
            slurp_mode: false,
            compact_output: false,
            eval_paused: false,
            worker,
            eval_request_id: 0,
            pending_eval_id: None,
            eval_started_at: None,
        }
    }

    pub fn new_loading(loader: Loader) -> Self {
        Self {
            input_values: None,
            loading_state: LoadingState::Loading,
            loader: Some(loader),
            filter_buffer: TextBuffer::new("."),
            filter_error: None,
            pending_filter_update: None,
            partial_eval_mode: false,
            output_values: vec![],
            total_lines: 0,
            scroll_offset: 0,
            horizontal_offset: 0,
            viewport_height: 0,
            viewport_width: 0,
            show_help: false,
            help_for_filter_mode: false,
            help_scroll: 0,
            wrap_lines: false,
            editing_filter: false,
            raw_output: false,
            slurp_mode: false,
            compact_output: false,
            eval_paused: false,
            worker: Worker::spawn(),
            eval_request_id: 0,
            pending_eval_id: None,
            eval_started_at: None,
        }
    }

    pub fn input_count(&self) -> usize {
        self.input_values.as_ref().map(|v| v.len()).unwrap_or(0)
    }

    pub fn loading_state(&self) -> &LoadingState {
        &self.loading_state
    }

    pub fn render_options(&self) -> RenderOptions {
        RenderOptions {
            raw_output: self.raw_output,
            compact: self.compact_output,
        }
    }

    pub fn output_values(&self) -> &[Value] {
        &self.output_values
    }

    fn recompute_total_lines(&mut self) {
        let options = self.render_options();
        self.total_lines = ui::count_total_lines(&self.output_values, &options);
    }

    fn mark_filter_dirty(&mut self) {
        // Schedule a debounced syntax check and evaluation
        self.pending_filter_update = Some(Instant::now());
    }

    fn request_eval(&mut self) {
        // Don't try to evaluate if we don't have input yet
        let Some(ref input_values) = self.input_values else {
            return;
        };

        self.eval_request_id += 1;

        let filter_text = if self.partial_eval_mode {
            let byte_pos = self.filter_buffer.cursor_byte_pos();
            self.filter_buffer.text()[..byte_pos].to_string()
        } else {
            self.filter_buffer.text().to_string()
        };

        self.worker.send(EvalRequest {
            id: self.eval_request_id,
            filter_text,
            inputs: Arc::clone(input_values),
            slurp_mode: self.slurp_mode,
        });

        self.pending_eval_id = Some(self.eval_request_id);
        self.eval_started_at = Some(Instant::now());
        self.pending_filter_update = None;
    }

    pub fn tick(&mut self) {
        // Poll for loading completion
        let load_result = self.loader.as_ref().and_then(|l| l.try_recv());
        if let Some(result) = load_result {
            self.loader = None;
            match result.result {
                Ok(values) => {
                    self.input_values = Some(Arc::new(values));
                    self.loading_state = LoadingState::Ready;
                    self.request_eval(); // Initial evaluation
                }
                Err(e) => {
                    self.loading_state = LoadingState::Failed(e);
                }
            }
        }

        // Poll for completed evaluations
        while let Some(result) = self.worker.try_recv() {
            if Some(result.id) == self.pending_eval_id {
                self.pending_eval_id = None;
                self.eval_started_at = None;

                match result.result {
                    Ok(values) => {
                        self.output_values = values;
                        self.filter_error = None;
                        self.recompute_total_lines();
                        self.scroll_offset = 0;
                        self.horizontal_offset = 0;
                    }
                    Err(e) => {
                        self.filter_error = Some(e);
                    }
                }
            }
        }

        // Fire syntax check and evaluation when debounce expires (unless paused)
        if let Some(changed_at) = self.pending_filter_update
            && changed_at.elapsed() >= DEBOUNCE_DURATION
        {
            self.pending_filter_update = None;

            // Check syntax first
            let filter_text = if self.partial_eval_mode {
                let byte_pos = self.filter_buffer.cursor_byte_pos();
                &self.filter_buffer.text()[..byte_pos]
            } else {
                self.filter_buffer.text()
            };

            match filter::parse_check(filter_text) {
                Ok(()) => {
                    self.filter_error = None;
                    if !self.eval_paused {
                        self.request_eval();
                    }
                }
                Err(e) => {
                    self.filter_error = Some(FilterError::Parse(e));
                }
            }
        }
    }

    pub fn scroll_offset(&self) -> usize {
        self.scroll_offset
    }

    pub fn total_lines(&self) -> usize {
        self.total_lines
    }

    pub fn horizontal_offset(&self) -> usize {
        self.horizontal_offset
    }

    pub fn show_help(&self) -> bool {
        self.show_help
    }

    pub fn help_scroll(&self) -> usize {
        self.help_scroll
    }

    pub fn clamp_help_scroll(&mut self, max_scroll: usize) {
        self.help_scroll = self.help_scroll.min(max_scroll);
    }

    pub fn wrap_lines(&self) -> bool {
        self.wrap_lines
    }

    pub fn editing_filter(&self) -> bool {
        self.editing_filter
    }

    pub fn partial_eval_mode(&self) -> bool {
        self.partial_eval_mode
    }

    pub fn raw_output(&self) -> bool {
        self.raw_output
    }

    pub fn slurp_mode(&self) -> bool {
        self.slurp_mode
    }

    pub fn compact_output(&self) -> bool {
        self.compact_output
    }

    pub fn is_evaluating(&self) -> bool {
        // Only show evaluating indicator after 100ms to avoid flicker for fast evals
        const EVAL_INDICATOR_DELAY: Duration = Duration::from_millis(100);
        self.eval_started_at
            .is_some_and(|started| started.elapsed() >= EVAL_INDICATOR_DELAY)
    }

    pub fn eval_paused(&self) -> bool {
        self.eval_paused
    }

    /// Cancel the current evaluation - ignores result when it arrives
    fn cancel_eval(&mut self) {
        self.pending_eval_id = None;
        self.eval_started_at = None;
        self.pending_filter_update = None;
    }

    pub fn filter_text(&self) -> &str {
        self.filter_buffer.text()
    }

    fn copy_filter_to_clipboard(&self) {
        if let Ok(mut clipboard) = Clipboard::new() {
            let mut flags = String::new();
            if self.slurp_mode {
                flags.push('s');
            }
            if self.raw_output {
                flags.push('r');
            }
            if self.compact_output {
                flags.push('c');
            }

            let command = if flags.is_empty() {
                format!("jq '{}'", self.filter_buffer.text())
            } else {
                format!("jq -{} '{}'", flags, self.filter_buffer.text())
            };

            let _ = clipboard.set_text(command);
        }
    }

    pub fn filter_cursor_display_col(&self) -> usize {
        let byte_pos = self.filter_buffer.cursor_byte_pos();
        self.filter_buffer.text()[..byte_pos].width()
    }

    pub fn filter_error(&self) -> Option<&FilterError> {
        self.filter_error.as_ref()
    }

    pub fn set_viewport(&mut self, width: usize, height: usize) {
        self.viewport_width = width;
        self.viewport_height = height;
        self.clamp_scroll();
    }

    pub fn handle_key(&mut self, key: KeyEvent) -> AppAction {
        // Ctrl-H toggles help in any mode
        if key.code == KeyCode::Char('h') && key.modifiers.contains(KeyModifiers::CONTROL) {
            if self.show_help {
                self.show_help = false;
            } else {
                self.show_help = true;
                self.help_scroll = 0;
                self.help_for_filter_mode = self.editing_filter;
            }
            return AppAction::Continue;
        }

        if self.show_help {
            // Scroll keys navigate help, other keys close it
            match key.code {
                KeyCode::Char('j') | KeyCode::Down => {
                    self.help_scroll = self.help_scroll.saturating_add(1);
                }
                KeyCode::Char('k') | KeyCode::Up => {
                    self.help_scroll = self.help_scroll.saturating_sub(1);
                }
                KeyCode::PageDown => {
                    self.help_scroll = self.help_scroll.saturating_add(10);
                }
                KeyCode::PageUp => {
                    self.help_scroll = self.help_scroll.saturating_sub(10);
                }
                KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                    self.help_scroll = self.help_scroll.saturating_add(10);
                }
                KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                    self.help_scroll = self.help_scroll.saturating_sub(10);
                }
                KeyCode::Char('g') => {
                    self.help_scroll = 0;
                }
                KeyCode::Char('G') => {
                    // Set to large value, UI will clamp
                    self.help_scroll = usize::MAX;
                }
                _ => {
                    self.show_help = false;
                }
            }
            return AppAction::Continue;
        }

        if self.editing_filter {
            return self.handle_filter_key(key);
        }

        self.handle_navigation_key(key)
    }

    pub fn help_for_filter_mode(&self) -> bool {
        self.help_for_filter_mode
    }

    fn handle_navigation_key(&mut self, key: KeyEvent) -> AppAction {
        match key.code {
            KeyCode::Char('q') | KeyCode::Esc => AppAction::Quit,
            KeyCode::Char('i') | KeyCode::Char('/') => {
                self.editing_filter = true;
                AppAction::Continue
            }
            KeyCode::Char('j') | KeyCode::Down => {
                self.scroll_down(1);
                AppAction::Continue
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.scroll_up(1);
                AppAction::Continue
            }
            KeyCode::Char('h') | KeyCode::Left => {
                if !self.wrap_lines {
                    self.scroll_left(4);
                }
                AppAction::Continue
            }
            KeyCode::Char('l') | KeyCode::Right => {
                if !self.wrap_lines {
                    self.scroll_right(4);
                }
                AppAction::Continue
            }
            KeyCode::Char('g') => {
                self.scroll_to_top();
                AppAction::Continue
            }
            KeyCode::Char('G') => {
                self.scroll_to_bottom();
                AppAction::Continue
            }
            KeyCode::Char('0') => {
                self.horizontal_offset = 0;
                AppAction::Continue
            }
            KeyCode::Char('w') => {
                self.wrap_lines = !self.wrap_lines;
                if self.wrap_lines {
                    self.horizontal_offset = 0;
                }
                AppAction::Continue
            }
            KeyCode::Char('r') => {
                self.raw_output = !self.raw_output;
                self.recompute_total_lines();
                AppAction::Continue
            }
            KeyCode::Char('s') => {
                self.slurp_mode = !self.slurp_mode;
                self.request_eval();
                AppAction::Continue
            }
            KeyCode::Char('o') => {
                self.compact_output = !self.compact_output;
                self.recompute_total_lines();
                AppAction::Continue
            }
            KeyCode::Char('y') => {
                self.copy_filter_to_clipboard();
                AppAction::Continue
            }
            KeyCode::PageDown => {
                self.scroll_down(self.viewport_height.saturating_sub(1));
                AppAction::Continue
            }
            KeyCode::PageUp => {
                self.scroll_up(self.viewport_height.saturating_sub(1));
                AppAction::Continue
            }
            KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.scroll_down(self.viewport_height / 2);
                AppAction::Continue
            }
            KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.scroll_up(self.viewport_height / 2);
                AppAction::Continue
            }
            _ => AppAction::Continue,
        }
    }

    fn handle_filter_key(&mut self, key: KeyEvent) -> AppAction {
        match key.code {
            KeyCode::Esc => {
                self.exit_filter_edit();
            }
            KeyCode::Enter => {
                // In paused mode, Enter triggers evaluation
                if self.eval_paused {
                    self.request_eval();
                }
            }
            KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                // Cancel eval and enter pause mode
                if self.eval_started_at.is_some() {
                    self.cancel_eval();
                    self.eval_paused = true;
                }
            }
            KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.exit_filter_edit();
            }
            KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.eval_paused = !self.eval_paused;
                if !self.eval_paused && self.pending_filter_update.is_some() {
                    self.request_eval();
                }
            }
            KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.slurp_mode = !self.slurp_mode;
                self.request_eval();
            }
            KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.raw_output = !self.raw_output;
                self.recompute_total_lines();
            }
            KeyCode::Char('o') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.compact_output = !self.compact_output;
                self.recompute_total_lines();
            }
            KeyCode::Char('y') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.copy_filter_to_clipboard();
            }
            KeyCode::Backspace => {
                if self.filter_buffer.backspace() {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Delete => {
                if self.filter_buffer.delete() {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Left => {
                self.filter_buffer.move_left();
                if self.partial_eval_mode {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Right => {
                self.filter_buffer.move_right();
                if self.partial_eval_mode {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Tab => {
                self.partial_eval_mode = !self.partial_eval_mode;
                self.mark_filter_dirty();
            }
            KeyCode::Home => {
                self.filter_buffer.move_to_start();
            }
            KeyCode::End => {
                self.filter_buffer.move_to_end();
            }
            KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.filter_buffer.move_to_start();
            }
            KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.filter_buffer.move_to_end();
            }
            KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                self.filter_buffer = TextBuffer::new(".");
                self.mark_filter_dirty();
            }
            KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                if self.filter_buffer.delete_word_back() {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
                self.filter_buffer.jump_word_back();
                if self.partial_eval_mode {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
                self.filter_buffer.jump_word_forward();
                if self.partial_eval_mode {
                    self.mark_filter_dirty();
                }
            }
            KeyCode::Char(c) => {
                self.filter_buffer.insert(c);
                self.mark_filter_dirty();
            }
            _ => {}
        }
        AppAction::Continue
    }

    fn exit_filter_edit(&mut self) {
        if self.pending_filter_update.is_some() {
            self.request_eval();
        }
        self.editing_filter = false;
    }

    fn scroll_down(&mut self, lines: usize) {
        self.scroll_offset = self.scroll_offset.saturating_add(lines);
        self.clamp_scroll();
    }

    fn scroll_up(&mut self, lines: usize) {
        self.scroll_offset = self.scroll_offset.saturating_sub(lines);
    }

    fn scroll_left(&mut self, cols: usize) {
        self.horizontal_offset = self.horizontal_offset.saturating_sub(cols);
    }

    fn scroll_right(&mut self, cols: usize) {
        self.horizontal_offset = self.horizontal_offset.saturating_add(cols);
    }

    fn scroll_to_top(&mut self) {
        self.scroll_offset = 0;
    }

    fn scroll_to_bottom(&mut self) {
        if self.total_lines > self.viewport_height {
            self.scroll_offset = self.total_lines - self.viewport_height;
        }
    }

    fn clamp_scroll(&mut self) {
        let max_scroll = self.total_lines.saturating_sub(self.viewport_height);
        self.scroll_offset = self.scroll_offset.min(max_scroll);
    }
}
