use core::fmt::Display;
use core::marker::PhantomData;

use crate::frame::FrameCtx;
use crate::input::NamedKey;
use crate::layout::LayoutRect;
use crate::strings::StringKey;
use crate::widget_id::WidgetId;

use super::keys::{TakeKey, take_key};
use super::paint::WidgetPaint;
use super::text_input::{
    Clipboard, TextInput, TextInputResponse, TextInputState, TextInputValidation, show_text_input,
};

pub trait ParsedValue: Sized + Clone + PartialEq {
    type Error: Display + Clone + PartialEq;

    fn parse(text: &str) -> Result<Self, Self::Error>;
}

pub struct ParsedInput<'state, T: ParsedValue> {
    pub id: WidgetId,
    pub rect: LayoutRect,
    pub placeholder: StringKey,
    pub state: &'state mut TextInputState,
    pub disabled: bool,
    _ty: PhantomData<T>,
}

impl<'state, T: ParsedValue> ParsedInput<'state, T> {
    #[must_use]
    pub fn new(
        id: WidgetId,
        rect: LayoutRect,
        placeholder: StringKey,
        state: &'state mut TextInputState,
    ) -> Self {
        Self {
            id,
            rect,
            placeholder,
            state,
            disabled: false,
            _ty: PhantomData,
        }
    }

    #[must_use]
    pub fn disabled(self, disabled: bool) -> Self {
        Self { disabled, ..self }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct ParsedInputResponse<T: ParsedValue> {
    pub interaction: crate::hit_test::Interaction,
    pub value: Option<T>,
    pub committed: Option<T>,
    pub paint: Vec<WidgetPaint>,
    pub error: Option<T::Error>,
}

struct ParsedValidator<T: ParsedValue>(PhantomData<T>);

impl<T: ParsedValue> TextInputValidation for ParsedValidator<T> {
    type Error = T::Error;
    fn validate(&self, text: &str) -> Result<(), Self::Error> {
        if text.trim().is_empty() {
            return Ok(());
        }
        T::parse(text).map(drop)
    }
}

#[must_use]
pub fn show_parsed_input<T: ParsedValue, C: Clipboard + ?Sized>(
    ctx: &mut FrameCtx<'_>,
    input: ParsedInput<'_, T>,
    clipboard: &mut C,
) -> ParsedInputResponse<T> {
    let ParsedInput {
        id,
        rect,
        placeholder,
        state,
        disabled,
        ..
    } = input;
    let was_focused = state.was_focused;
    let live_focused = ctx.is_focused(id);
    let commit_via_enter = !disabled
        && live_focused
        && take_key(ctx.input, &[TakeKey::named(NamedKey::Enter)]).is_some();
    let TextInputResponse {
        interaction,
        error,
        paint,
        ..
    } = {
        let widget = TextInput {
            id,
            rect,
            placeholder,
            state: &mut *state,
            disabled,
            validator: ParsedValidator::<T>(PhantomData),
        };
        show_text_input(ctx, widget, clipboard)
    };
    let value = if state.text.trim().is_empty() {
        None
    } else {
        T::parse(&state.text).ok()
    };
    let lost_focus = was_focused && !live_focused;
    let committed = if commit_via_enter || lost_focus {
        value.clone()
    } else {
        None
    };
    ParsedInputResponse {
        interaction,
        value,
        committed,
        paint,
        error,
    }
}
