use std::io::Result;
use std::io::stdout;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::PoisonError;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU16;
use std::sync::atomic::Ordering;

use crossterm::cursor::MoveTo;
use crossterm::cursor::Show;
use crossterm::event::DisableMouseCapture;
use crossterm::event::EnableMouseCapture;
use crossterm::event::KeyCode;
use crossterm::terminal::EnterAlternateScreen;
use crossterm::terminal::LeaveAlternateScreen;
use ratatui::crossterm::execute;
use ratatui::layout::Rect;

use crate::product::tui_app::key_hint;

use super::DisableAlternateScroll;
use super::Terminal;

pub const SUSPEND_KEY: key_hint::KeyBinding = key_hint::ctrl(KeyCode::Char('z'));

/// Coordinates suspend/resume handling so the TUI can restore terminal context after SIGTSTP.
///
/// On suspend, it records whether the fullscreen alternate screen should be restored and caches
/// the cursor row so the cursor can be placed meaningfully before yielding.
///
/// After resume, `prepare_resume_action` consumes the pending intent and returns a
/// `PreparedResumeAction` describing any alternate-screen restoration to apply inside the
/// synchronized draw.
///
/// Callers keep `suspend_cursor_y` up to date during normal drawing so the suspend step always
/// has the latest cursor position.
///
/// The type is `Clone`, using Arc/atomic internals so bookkeeping can be shared across tasks
/// and moved into the boxed `'static` event stream without borrowing `self`.
#[derive(Clone)]
pub struct SuspendContext {
    /// Resume intent captured at suspend time; cleared once applied after resume.
    resume_pending: Arc<Mutex<Option<ResumeAction>>>,
    /// Cursor row used to place the cursor before yielding during suspend.
    suspend_cursor_y: Arc<AtomicU16>,
}

impl SuspendContext {
    pub(crate) fn new() -> Self {
        Self {
            resume_pending: Arc::new(Mutex::new(None)),
            suspend_cursor_y: Arc::new(AtomicU16::new(0)),
        }
    }

    /// Capture how to resume, stash cursor position, and temporarily yield during SIGTSTP.
    ///
    /// - If the alt screen is active, exit alt-scroll/alt-screen and record `RestoreAlt`.
    /// - Update the cached cursor row so suspend can place the cursor meaningfully.
    /// - Trigger SIGTSTP so the process can be resumed and continue drawing with the saved state.
    pub(crate) fn suspend(
        &self,
        alt_screen_active: &Arc<AtomicBool>,
        use_mouse_capture: bool,
    ) -> Result<()> {
        if alt_screen_active.load(Ordering::Relaxed) {
            // Leave alt-screen so the terminal returns to the normal buffer while suspended.
            if use_mouse_capture {
                let _ = execute!(stdout(), DisableMouseCapture);
            }
            let _ = execute!(stdout(), DisableAlternateScroll);
            let _ = execute!(stdout(), LeaveAlternateScreen);
            self.set_resume_action(ResumeAction::RestoreAlt { use_mouse_capture });
        }
        let y = self.suspend_cursor_y.load(Ordering::Relaxed);
        let _ = execute!(stdout(), MoveTo(0, y), Show);
        suspend_process(use_mouse_capture)
    }

    /// Consume the pending resume intent and precompute any alternate-screen restoration needed
    /// post-resume. Returns `None` when there was no pending suspend intent.
    pub(crate) fn prepare_resume_action(
        &self,
        _terminal: &mut Terminal,
        alt_saved_viewport: &mut Option<Rect>,
    ) -> Option<PreparedResumeAction> {
        let action = self.take_resume_action()?;
        match action {
            ResumeAction::RestoreAlt { use_mouse_capture } => {
                if let Ok(position) = _terminal.get_cursor_position()
                    && let Some(saved) = alt_saved_viewport.as_mut()
                {
                    saved.y = position.y;
                }
                Some(PreparedResumeAction::RestoreAltScreen { use_mouse_capture })
            }
        }
    }

    /// Set the cached cursor row so suspend can place the cursor meaningfully.
    pub(crate) fn set_cursor_y(&self, value: u16) {
        self.suspend_cursor_y.store(value, Ordering::Relaxed);
    }

    /// Record a pending resume action to apply after SIGTSTP returns control.
    fn set_resume_action(&self, value: ResumeAction) {
        *self
            .resume_pending
            .lock()
            .unwrap_or_else(PoisonError::into_inner) = Some(value);
    }

    /// Take and clear any pending resume action captured at suspend time.
    fn take_resume_action(&self) -> Option<ResumeAction> {
        self.resume_pending
            .lock()
            .unwrap_or_else(PoisonError::into_inner)
            .take()
    }
}

/// Captures what should happen when returning from suspend.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ResumeAction {
    /// Re-enter the alt screen and restore the fullscreen TUI.
    RestoreAlt { use_mouse_capture: bool },
}

/// Describes the terminal change to apply when resuming from suspend during the synchronized draw.
#[derive(Clone, Debug)]
pub(crate) enum PreparedResumeAction {
    /// Re-enter the alt screen and reset the viewport to the terminal dimensions.
    RestoreAltScreen { use_mouse_capture: bool },
}

impl PreparedResumeAction {
    pub(crate) fn apply(self, terminal: &mut Terminal) -> Result<()> {
        match self {
            PreparedResumeAction::RestoreAltScreen { use_mouse_capture } => {
                execute!(terminal.backend_mut(), EnterAlternateScreen)?;
                if use_mouse_capture {
                    execute!(terminal.backend_mut(), EnableMouseCapture)?;
                }
                if let Ok(size) = terminal.size() {
                    terminal.set_viewport_area(Rect::new(0, 0, size.width, size.height));
                    terminal.clear()?;
                }
            }
        }
        Ok(())
    }
}

/// Deliver SIGTSTP after restoring terminal state, then re-applies terminal modes once resumed.
fn suspend_process(use_mouse_capture: bool) -> Result<()> {
    super::restore()?;
    unsafe { libc::kill(0, libc::SIGTSTP) };
    // After the process resumes, reapply terminal modes so drawing can continue.
    super::set_modes(use_mouse_capture)?;
    Ok(())
}
