#![no_std]
pub mod config;

use config::KeyConfig;
use core::marker::PhantomData;

pub type TU = u16; // TU (Time Unit, milliseconds based)

/// Key driver trait.
///
/// Implementations of this trait provide hardware access for reading Key state.
///
/// - `now()` returns the current time value.
/// - `elapsed()` returns the time elapsed since the given timestamp.
/// - `scan()` scans the Key and returns the Key code if pressed.
pub trait KeyDriver<I: Copy + PartialEq> {
    fn now(&self) -> I;
    fn elapsed(&self, last: I) -> TU;
    fn scan(&mut self) -> Option<u8>;
}

#[cfg(feature = "long-press")]
#[derive(Clone, Debug, PartialEq, Eq)]
enum LPState {
    Idle,
    Triggered,
    Handled,
}

/// Represents current Key state.
///
/// State machine diagram:
/// ```ignore
/// Released --pressed--> Down
/// Down --debounced--> Pressed
/// Down --released--> Released
/// Pressed --hold timeout--> Held
/// Pressed --released--> Up
/// Up --pressed (within timeout)--> Down
/// Up --timeout--> Ready
/// Held --released--> Up
/// Ready --reset--> Released
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State<I> {
    /// Fully released state, idle.
    Released,
    /// The Key has been just pressed, so it is in *down* position.
    Down(I),
    /// Debounced press.
    Pressed(I),
    /// The Key is being held.
    Held(I),
    /// The Key has been just released, so it is in *up* position.
    Up(I),
    /// The Key has been released and click count is ready to be read.
    Ready,
}

#[derive(Debug)]
pub struct KeyInput<I, D, CFG> {
    driver: D,
    state: State<I>,
    clicks: u8,
    key_code: u8,
    #[cfg(feature = "long-press")]
    lp_state: LPState,
    _phantom: PhantomData<CFG>,
}

impl<I, D, CFG: KeyConfig> KeyInput<I, D, CFG>
where
    I: PartialEq + Copy,
    D: KeyDriver<I>,
{
    /// Creates a new [KeyInput].
    pub const fn new(driver: D) -> Self {
        Self {
            driver,
            state: State::Released,
            clicks: 0,
            key_code: 0,
            #[cfg(feature = "long-press")]
            lp_state: LPState::Idle,
            _phantom: PhantomData,
        }
    }

    /// Returns current Key code.
    pub fn key_code(&self) -> u8 {
        self.key_code
    }

    /// Returns the number of clicks recorded since the last release.
    /// Returns 0 unless the state is [State::Ready].
    pub fn clicks(&self) -> u8 {
        if self.state == State::Ready {
            self.clicks
        } else {
            0
        }
    }

    /// Returns `true` if the Key was pressed once before release.
    pub fn is_clicked(&self) -> bool {
        self.clicks() == 1
    }

    /// Returns `true` if the Key was pressed twice before release.
    pub fn is_double_clicked(&self) -> bool {
        self.clicks() == 2
    }

    /// Returns `true` if the Key was pressed three times before release.
    pub fn is_triple_clicked(&self) -> bool {
        self.clicks() == 3
    }

    /// Returns `true` if a long press event was detected during the last [poll](Self::poll) call.
    ///
    /// - [LongPress::Single]: Returns `true` once when the hold duration exceeds the configured threshold.
    /// - [LongPress::Repeat]: Returns `true` repeatedly at the configured interval while held.
    #[cfg(feature = "long-press")]
    pub fn is_long_pressed(&mut self) -> bool {
        if matches!(self.lp_state, LPState::Triggered) {
            self.lp_state = LPState::Handled;
            true
        } else {
            false
        }
    }

    /// Returns the current holding duration, or `None` if the Key is not being held.
    pub fn holding_time(&self) -> Option<TU> {
        if let State::Held(elapsed) = self.state {
            Some(self.driver.elapsed(elapsed))
        } else {
            None
        }
    }

    /// Resets the state from [State::Ready] to [State::Released], clearing clicks and long press state.
    pub fn reset(&mut self) {
        if self.state == State::Ready {
            self.state = State::Released;
            self.clicks = 0;
            #[cfg(feature = "long-press")]
            {
                self.lp_state = LPState::Idle;
            }
        }
    }

    /// Polls the Key state. Should be called periodically in the main loop.
    pub fn poll(&mut self) {
        let is_pressed = self.scan();
        match self.state {
            State::Ready => {}
            State::Released => {
                if is_pressed {
                    self.clicks += 1;
                    self.state = State::Down(self.driver.now());
                }
            }
            State::Down(elapsed) => {
                if is_pressed {
                    if self.driver.elapsed(elapsed) >= CFG::DEBOUNCE {
                        self.state = State::Pressed(elapsed);
                    }
                } else {
                    self.state = State::Released;
                    self.clicks = 0;
                    #[cfg(feature = "long-press")]
                    {
                        self.lp_state = LPState::Idle;
                    }
                }
            }
            State::Pressed(elapsed) => {
                if is_pressed {
                    if self.driver.elapsed(elapsed) >= CFG::HOLD {
                        self.clicks -= 1;
                        self.state = State::Held(elapsed);
                        #[cfg(feature = "long-press")]
                        {
                            self.lp_state = LPState::Idle;
                        }
                    }
                } else {
                    self.state = State::Up(self.driver.now())
                }
            }
            State::Up(elapsed) => {
                if (self.driver.elapsed(elapsed)) < CFG::RELEASE {
                    if is_pressed {
                        self.clicks = self.clicks.saturating_add(1);
                        self.state = State::Down(self.driver.now());
                    }
                } else {
                    self.state = State::Ready;
                }
            }
            State::Held(_elapsed) => {
                if !is_pressed {
                    self.state = State::Up(self.driver.now());
                    #[cfg(feature = "long-press")]
                    {
                        self.lp_state = LPState::Idle;
                    }
                } else {
                    #[cfg(feature = "long-press")]
                    match CFG::LONG_PRESS {
                        config::LongPress::Single(duration) => {
                            if matches!(self.lp_state, LPState::Idle)
                                && self.driver.elapsed(_elapsed) >= duration
                            {
                                self.lp_state = LPState::Triggered;
                            }
                        }
                        #[cfg(feature = "long-press-repeat")]
                        config::LongPress::Repeat(duration, repeat_duration) => match self.lp_state
                        {
                            LPState::Idle => {
                                if self.driver.elapsed(_elapsed) >= duration {
                                    self.lp_state = LPState::Triggered;
                                    self.state = State::Held(self.driver.now());
                                }
                            }
                            LPState::Triggered => {}
                            LPState::Handled => {
                                if self.driver.elapsed(_elapsed) >= repeat_duration {
                                    self.lp_state = LPState::Triggered;
                                    self.state = State::Held(self.driver.now());
                                }
                            }
                        },
                    }
                }
            }
        }
    }

    /// Reads current status, returns [true] if the Key is pressed without debouncing.
    fn scan(&mut self) -> bool {
        self.driver.scan().is_some_and(|code| {
            self.key_code = code;
            true
        })
    }
}
