use crate::{ComponentRender, Context, Event, Html, LiveResult};

/// Trait implemented by server-owned interactive views.
///
/// A LiveView owns its state. Browser events are delivered through
/// `handle_event`, and the resulting HTML is returned by `render`.
///
/// # Examples
///
/// ```
/// use shelly::{Context, Event, Html, LiveResult, LiveView};
///
/// struct Greeter;
///
/// impl LiveView for Greeter {
///     fn handle_event(&mut self, event: Event, _ctx: &mut Context) -> LiveResult {
///         if event.name == "noop" {
///             // no-op
///         }
///         Ok(())
///     }
///
///     fn render(&self) -> Html {
///         Html::new("<main>Hello</main>")
///     }
/// }
///
/// let mut view = Greeter;
/// let mut ctx = Context::new("root");
/// view.handle_event(Event::new("noop"), &mut ctx).expect("event should succeed");
/// assert_eq!(view.render().as_str(), "<main>Hello</main>");
/// ```
pub trait LiveView: Send + 'static {
    /// Called before the first render for a given session.
    fn mount(&mut self, _ctx: &mut Context) -> LiveResult {
        Ok(())
    }

    /// Called after mount and after internal URL patches update route params.
    fn handle_params(&mut self, _ctx: &mut Context) -> LiveResult {
        Ok(())
    }

    /// Called when the browser sends an event.
    fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> LiveResult {
        Ok(())
    }

    /// Called when an event has a target that may identify a child component.
    ///
    /// Returning `Some(ComponentRender)` tells the session runtime to patch only
    /// that component id. Returning `None` falls back to normal view handling.
    fn handle_component_event(
        &mut self,
        _target: &str,
        _event: Event,
        _ctx: &mut Context,
    ) -> LiveResult<Option<ComponentRender>> {
        Ok(None)
    }

    /// Render this view into an HTML fragment.
    fn render(&self) -> Html;
}

#[cfg(test)]
mod tests {
    use super::LiveView;
    use crate::{Context, Event, Html};

    #[derive(Default)]
    struct StubView;

    impl LiveView for StubView {
        fn render(&self) -> Html {
            Html::new("<main>stub</main>")
        }
    }

    #[test]
    fn live_view_default_callbacks_return_ok() {
        let mut view = StubView;
        let mut ctx = Context::new("root");

        assert!(view.mount(&mut ctx).is_ok());
        assert!(view.handle_params(&mut ctx).is_ok());
        assert!(view.handle_event(Event::new("noop"), &mut ctx).is_ok());
        assert!(view
            .handle_component_event("child-1", Event::new("noop"), &mut ctx)
            .expect("component callback should succeed")
            .is_none());
        assert_eq!(view.render().as_str(), "<main>stub</main>");
    }
}
