use std::io;

use scrin::{
    Rect, Terminal, TerminalOptions,
    core::buffer::Buffer,
    widgets::{Block, List, ListState, Widget},
};

use crate::error::Result;
use crate::{normalize_bg, theme_surface, theme_white};

pub(crate) struct TerminalSession {
    pub(crate) terminal: Terminal,
}

impl TerminalSession {
    pub(crate) fn new() -> Result<Self> {
        let mut terminal = Terminal::init_with(TerminalOptions {
            mouse_capture: true,
            ..TerminalOptions::default()
        })?;
        terminal.clear();
        Ok(Self { terminal })
    }
}

impl Drop for TerminalSession {
    fn drop(&mut self) {
        let _ = self.terminal.restore();
    }
}

pub(crate) struct RenderFrame<'a> {
    area: Rect,
    buffer: &'a mut Buffer,
    cursor_position: Option<(u16, u16)>,
}

impl RenderFrame<'_> {
    pub(crate) fn area(&self) -> Rect {
        self.area
    }

    pub(crate) fn buffer(&mut self) -> &mut Buffer {
        self.buffer
    }

    pub(crate) fn set_cursor_position(&mut self, x: u16, y: u16) {
        self.cursor_position = Some((x, y));
    }

    pub(crate) fn render_widget<W>(&mut self, widget: W, area: Rect)
    where
        W: Widget,
    {
        widget.render(&mut *self.buffer, area);
    }
}

pub(crate) fn draw_full_frame<F>(terminal: &mut Terminal, f: F) -> io::Result<()>
where
    F: FnOnce(&mut RenderFrame<'_>),
{
    terminal.size_changed()?;
    terminal.clear();

    let area = terminal.size();
    let cursor_position = {
        let mut frame = RenderFrame {
            area,
            buffer: terminal.back_buffer(),
            cursor_position: None,
        };
        f(&mut frame);
        frame.cursor_position
    };

    terminal.present_direct_timed()?;
    if let Some((x, y)) = cursor_position {
        terminal.set_cursor(x, y)?;
    }
    Ok(())
}

pub(crate) trait FrameRenderExt {
    fn render_block_widget<'item, W: Widget>(&mut self, area: Rect, widget: W, block: Block<'item>);
    fn render_block_stateful_list<'item>(
        &mut self,
        area: Rect,
        list: &List<'item>,
        state: &mut ListState,
        block: Block<'item>,
    );
}

impl FrameRenderExt for RenderFrame<'_> {
    fn render_block_widget<'item, W: Widget>(
        &mut self,
        area: Rect,
        widget: W,
        block: Block<'item>,
    ) {
        block.render(self.buffer(), area);
        let inner = block.inner(area);
        if inner.width > 0 && inner.height > 0 {
            self.buffer()
                .fill(inner, ' ', theme_white(), Some(theme_surface()));
            widget.render(self.buffer(), inner);
            normalize_bg(self.buffer(), inner, theme_surface());
        }
    }

    fn render_block_stateful_list<'b>(
        &mut self,
        area: Rect,
        list: &List<'b>,
        state: &mut ListState,
        block: Block<'b>,
    ) {
        block.render(self.buffer(), area);
        let inner = block.inner(area);
        if inner.width > 0 && inner.height > 0 {
            self.buffer()
                .fill(inner, ' ', theme_white(), Some(theme_surface()));
            list.render_stateful(self.buffer(), inner, state);
            normalize_bg(self.buffer(), inner, theme_surface());
        }
    }
}
