use std::{borrow::Cow, cmp::Ordering, ops::Range};

use rand::{rngs::OsRng, Rng, TryRngCore};
use ratatui::{
    buffer::Buffer,
    layout::{Alignment, Constraint, Direction, Flex, Layout, Position, Rect},
    style::{Color, Style, Stylize},
    widgets::{Block, Borders, Paragraph, Widget},
};
#[cfg(target_arch = "wasm32")]
use ratzilla::ratatui;
use tachyonfx::{fx, Effect, Interpolation, Shader};
use web_time::Instant;

const MEASURING_TAPE: [&'static str; 4] = ["⣇", "⣄", "⣆", "⣄"];
const NUMBERS: [u32; 3] = [0, 4, 9];
pub const PASSWORD_BACKGROUND: Color = Color::Rgb(35, 35, 35);

pub struct PasswordLocker {
    numbers_guessed: heapless::Vec<u32, 4>,
    transition_instant: Instant,
    guess_hint: Option<(u32, u32)>,
    range: Range<u32>,
    number_to_guess: usize,
    failed_guesses: usize,
    cursor_position: Option<Position>,
    current_effect: Effect,
    unlocked: bool,
}

impl Default for PasswordLocker {
    fn default() -> Self {
        let this = Self {
            numbers_guessed: heapless::Vec::new(),
            transition_instant: Instant::now(),
            range: 0..10,
            guess_hint: None,
            number_to_guess: OsRng.unwrap_err().random_range(0..2),
            failed_guesses: 0,
            cursor_position: None,
            current_effect: fx::fade_from_fg(Color::Black, (200000, Interpolation::CircIn)),
            unlocked: false,
        };
        #[cfg(target_arch = "wasm32")]
        match this.number_to_guess {
            0 => ratzilla::web_sys::console::log_1(&"Secret code: 5210".into()),
            1 => ratzilla::web_sys::console::log_1(&"Secret code: 5234".into()),
            2 => ratzilla::web_sys::console::log_1(&"Secret code: 5789".into()),
            _ => {}
        }
        this
    }
}

impl PasswordLocker {
    pub fn add_char_to_number_guess(&mut self, c: char) -> bool {
        if let Some(number) = c.to_digit(10) {
            if self.guess_hint.is_some() {
                self.numbers_guessed.clear();
                self.range = 0..10;
                self.guess_hint = None;
            }
            self.numbers_guessed.push(number).unwrap();
            let guess_hint = self.range.start + ((self.range.end - self.range.start) / 2);
            if number != guess_hint {
                self.guess_hint = Some((number, guess_hint));
                self.numbers_guessed.clear();
                self.number_to_guess += 1;
                self.failed_guesses += 1;
                if self.number_to_guess == NUMBERS.len() {
                    self.number_to_guess = 0;
                }
                #[cfg(target_arch = "wasm32")]
                match self.number_to_guess {
                    0 => ratzilla::web_sys::console::log_1(&"Secret code: 5210".into()),
                    1 => ratzilla::web_sys::console::log_1(&"Secret code: 5234".into()),
                    2 => ratzilla::web_sys::console::log_1(&"Secret code: 5789".into()),
                    _ => {}
                }
                return false;
            }
            match number.cmp(&NUMBERS[self.number_to_guess]) {
                Ordering::Less => {
                    self.range = number..self.range.end;
                }
                Ordering::Greater => {
                    self.range = self.range.start..number;
                }
                Ordering::Equal => {
                    self.guess_hint = Some((number, number));
                    self.transition_instant = Instant::now();
                    self.current_effect = fx::parallel(&[
                        fx::effect_fn((), (200000, Interpolation::CircIn), |_, _, cells| {
                            cells.for_each(|(_, c)| c.fg = Color::Green);
                        }),
                        fx::fade_to_fg(PASSWORD_BACKGROUND, (200000, Interpolation::CircIn)),
                    ]);
                    return true;
                }
            }
        }

        false
    }

    pub fn done(&self) -> bool {
        self.guess_hint.map(|(a, b)| a == b).unwrap_or_default()
    }

    pub fn current_effect_done(&self) -> bool {
        self.current_effect.done()
    }

    pub fn reset(&mut self) {
        self.numbers_guessed.clear();
        self.guess_hint = None;
        self.range = 0..10;
    }

    pub fn cursor_position(&self) -> Option<Position> {
        self.cursor_position
    }

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

impl Widget for &mut PasswordLocker {
    fn render(self, area: Rect, buf: &mut Buffer)
    where
        Self: Sized,
    {
        if !self.unlocked {
            Block::new().bg(PASSWORD_BACKGROUND).render(area, buf);

            let middle_vertical = Layout::new(Direction::Vertical, [Constraint::Length(8)])
                .flex(Flex::Center)
                .split(area)[0];

            let middle = Layout::new(Direction::Horizontal, [Constraint::Length(28)])
                .flex(Flex::Center)
                .split(middle_vertical)[0];

            let layout = Layout::new(
                Direction::Vertical,
                [
                    Constraint::Length(1),
                    Constraint::Length(3),
                    Constraint::Length(3),
                    Constraint::Length(1),
                    Constraint::Min(0),
                ],
            )
            .split(middle);

            let layout_code_blocks = Layout::new(
                Direction::Horizontal,
                [
                    Constraint::Length(3),
                    Constraint::Length(3),
                    Constraint::Length(3),
                    Constraint::Length(3),
                ],
            )
            .flex(Flex::SpaceBetween)
            .split(layout[2]);

            Paragraph::new("Enter the secret code")
                .alignment(Alignment::Center)
                .fg(Color::Rgb(226, 190, 89))
                .bg(PASSWORD_BACKGROUND)
                .render(layout[0], buf);

            let mut cursor_set = false;
            self.cursor_position = None;
            for (block, num) in layout_code_blocks.iter().copied().zip(
                self.numbers_guessed
                    .iter()
                    .map(|num| Cow::Owned(format!("{}", *num)))
                    .chain(std::iter::repeat(Cow::Borrowed(" "))),
            ) {
                if !cursor_set && num == " " {
                    self.cursor_position = Some(Position {
                        x: block.x + 1,
                        y: block.y + 1,
                    });
                    cursor_set = true;
                }
                Paragraph::new(num)
                    .block(Block::new().borders(Borders::all()))
                    .fg(Color::Rgb(226, 190, 89))
                    .bg(PASSWORD_BACKGROUND)
                    .render(block, buf);
            }

            Paragraph::new(if self.failed_guesses >= 20 {
                "Search the binary"
            } else {
                "Maximize efficiency"
            })
            .alignment(Alignment::Center)
            .fg(Color::Rgb(226, 190, 89))
            .bg(PASSWORD_BACKGROUND)
            .render(layout[3], buf);

            let ruler_scale = 9.0 / middle.width as f64;
            for (number_on, (x, measures)) in (middle.x..middle.right())
                .zip(std::iter::from_fn(|| Some(MEASURING_TAPE.iter())).flatten())
                .enumerate()
            {
                let number_on = (number_on as f64 * ruler_scale) as u32;
                let mut style = Style::default()
                    .fg(Color::Rgb(226, 190, 89))
                    .bg(PASSWORD_BACKGROUND);
                if !self.range.contains(&number_on) {
                    style = style.fg(Color::Rgb(117, 97, 42));
                }
                buf.set_string(x, middle.y + 1, measures, style);
            }

            for number_guessed in &self.numbers_guessed {
                let number_x = middle.x + (*number_guessed as f64 / ruler_scale) as u16;
                let style = Style::default()
                    .fg(Color::Rgb(226, 190, 89))
                    .bg(PASSWORD_BACKGROUND);
                buf.set_string(number_x, middle.y + 1, "|", style);
                if self.failed_guesses >= 30 {
                    buf.set_string(number_x, middle.y, format!("{}", *number_guessed), style);
                }
                match NUMBERS[self.number_to_guess].cmp(number_guessed) {
                    Ordering::Less => buf.set_string(number_x, middle.y + 2, "<", style),
                    Ordering::Greater => buf.set_string(number_x, middle.y + 2, ">", style),
                    Ordering::Equal => buf.set_string(number_x, middle.y + 2, "=", style),
                }
            }

            for number_hint in [0, 9] {
                let number_x = middle.x + (number_hint as f64 / ruler_scale) as u16;
                let style = Style::default()
                    .fg(Color::Rgb(226, 190, 89))
                    .bg(PASSWORD_BACKGROUND);
                buf.set_string(number_x, middle.y + 1, "|", style);
                buf.set_string(number_x, middle.y, format!("{}", number_hint), style);
            }

            if self.failed_guesses >= 30 {
                let guess_hint = self.range.start + ((self.range.end - self.range.start) / 2);
                let number_x = middle.x + (guess_hint as f64 / ruler_scale) as u16;
                let style = Style::default()
                    .fg(Color::Rgb(226, 190, 89))
                    .bg(PASSWORD_BACKGROUND);
                buf.set_string(number_x, middle.y + 2, "↑", style);
            }

            if let Some((last_guessed, guess_hint)) = self.guess_hint {
                let number_x = middle.x + (last_guessed as f64 / ruler_scale) as u16;
                let style = Style::default().fg(Color::Red).bg(PASSWORD_BACKGROUND);
                buf.set_string(number_x, middle.y + 1, "✗", style);
                let number_x = middle.x + (guess_hint as f64 / ruler_scale) as u16;
                let style = Style::default().fg(Color::Green).bg(PASSWORD_BACKGROUND);
                buf.set_string(number_x, middle.y + 1, "✓", style);
            }

            if self.current_effect.done() && self.done() {
                self.transition_instant = Instant::now();
                self.current_effect = fx::slide_in(
                    tachyonfx::Motion::UpToDown,
                    10,
                    0,
                    PASSWORD_BACKGROUND,
                    (200000, Interpolation::Linear),
                );
                self.unlocked = true;
                self.reset();
            }
        }
        self.current_effect
            .process(self.transition_instant.elapsed().into(), buf, area);
    }
}
