use self::add_attr::AddAnyAttr;
use crate::{
    html::attribute::any_attribute::AnyAttribute, hydration::Cursor,
    ssr::StreamBuilder,
};
use or_poisoned::OrPoisoned;
use std::{
    cell::RefCell,
    future::Future,
    rc::Rc,
    sync::{Arc, RwLock},
};

/// Add attributes to typed views.
pub mod add_attr;
/// A typed-erased view type.
pub mod any_view;
/// Allows choosing between one of several views.
pub mod either;
/// View rendering for `Result<_, _>` types.
pub mod error_boundary;
/// A type-erased view collection.
pub mod fragment;
/// View implementations for several iterable types.
pub mod iterators;
/// Keyed list iteration.
pub mod keyed;
mod primitives;
/// Optimized types for static strings known at compile time.
#[cfg(all(feature = "nightly", rustc_nightly))]
pub mod static_types;
/// View implementation for string types.
pub mod strings;
/// Optimizations for creating views via HTML `<template>` nodes.
pub mod template;
/// View implementations for tuples.
pub mod tuples;

/// The `Render` trait allows rendering something as part of the user interface.
pub trait Render: Sized {
    /// The “view state” for this type, which can be retained between updates.
    ///
    /// For example, for a text node, `State` might be the actual DOM text node
    /// and the previous string, to allow for diffing between updates.
    type State: Mountable;

    /// Creates the view for the first time, without hydrating from existing HTML.
    fn build(self) -> Self::State;

    /// Updates the view with new data.
    fn rebuild(self, state: &mut Self::State);
}

#[doc(hidden)]
pub trait MarkBranch {
    fn open_branch(&mut self, branch_id: &str);

    fn close_branch(&mut self, branch_id: &str);
}

impl MarkBranch for String {
    fn open_branch(&mut self, branch_id: &str) {
        self.push_str("<!--bo-");
        self.push_str(branch_id);
        self.push_str("-->");
    }

    fn close_branch(&mut self, branch_id: &str) {
        self.push_str("<!--bc-");
        self.push_str(branch_id);
        self.push_str("-->");
    }
}

impl MarkBranch for StreamBuilder {
    fn open_branch(&mut self, branch_id: &str) {
        self.sync_buf.push_str("<!--bo-");
        self.sync_buf.push_str(branch_id);
        self.sync_buf.push_str("-->");
    }

    fn close_branch(&mut self, branch_id: &str) {
        self.sync_buf.push_str("<!--bc-");
        self.sync_buf.push_str(branch_id);
        self.sync_buf.push_str("-->");
    }
}

/// The `RenderHtml` trait allows rendering something to HTML, and transforming
/// that HTML into an interactive interface.
///
/// This process is traditionally called “server rendering” and “hydration.” As a
/// metaphor, this means that the structure of the view is created on the server, then
/// “dehydrated” to HTML, sent across the network, and “rehydrated” with interactivity
/// in the browser.
///
/// However, the same process can be done entirely in the browser: for example, a view
/// can be transformed into some HTML that is used to create a `<template>` node, which
/// can be cloned many times and “hydrated,” which is more efficient than creating the
/// whole view piece by piece.
pub trait RenderHtml
where
    Self: Render + AddAnyAttr + Send,
{
    /// The type of the view after waiting for all asynchronous data to load.
    type AsyncOutput: RenderHtml;

    /// An equivalent value that is `'static`.
    type Owned: RenderHtml + 'static;

    /// The minimum length of HTML created when this view is rendered.
    const MIN_LENGTH: usize;

    /// Whether this should actually exist in the DOM, if it is the child of an element.
    const EXISTS: bool = true;

    /// “Runs” the view without other side effects. For primitive types, this is a no-op. For
    /// reactive types, this can be used to gather data about reactivity or about asynchronous data
    /// that needs to be loaded.
    fn dry_resolve(&mut self);

    /// Waits for any asynchronous sections of the view to load and returns the output.
    fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;

    /// An estimated length for this view, when rendered to HTML.
    ///
    /// This is used for calculating the string buffer size when rendering HTML. It does not need
    /// to be precise, but should be an appropriate estimate. The more accurate, the fewer
    /// reallocations will be required and the faster server-side rendering will be.
    fn html_len(&self) -> usize {
        Self::MIN_LENGTH
    }

    /// Renders a view to an HTML string.
    fn to_html(self) -> String
    where
        Self: Sized,
    {
        let mut buf = String::with_capacity(self.html_len());
        self.to_html_with_buf(
            &mut buf,
            &mut Position::FirstChild,
            true,
            false,
            vec![],
        );
        buf
    }

    /// Renders a view to HTML with branch markers. This can be used to support libraries that diff
    /// HTML pages against one another, by marking sections of the view that branch to different
    /// types with marker comments.
    fn to_html_branching(self) -> String
    where
        Self: Sized,
    {
        let mut buf = String::with_capacity(self.html_len());
        self.to_html_with_buf(
            &mut buf,
            &mut Position::FirstChild,
            true,
            true,
            vec![],
        );
        buf
    }

    /// Renders a view to an in-order stream of HTML.
    fn to_html_stream_in_order(self) -> StreamBuilder
    where
        Self: Sized,
    {
        let mut builder = StreamBuilder::with_capacity(self.html_len(), None);
        self.to_html_async_with_buf::<false>(
            &mut builder,
            &mut Position::FirstChild,
            true,
            false,
            vec![],
        );
        builder.finish()
    }

    /// Renders a view to an in-order stream of HTML with branch markers. This can be used to support libraries that diff
    /// HTML pages against one another, by marking sections of the view that branch to different
    /// types with marker comments.
    fn to_html_stream_in_order_branching(self) -> StreamBuilder
    where
        Self: Sized,
    {
        let mut builder = StreamBuilder::with_capacity(self.html_len(), None);
        self.to_html_async_with_buf::<false>(
            &mut builder,
            &mut Position::FirstChild,
            true,
            true,
            vec![],
        );
        builder.finish()
    }

    /// Renders a view to an out-of-order stream of HTML.
    fn to_html_stream_out_of_order(self) -> StreamBuilder
    where
        Self: Sized,
    {
        //let capacity = self.html_len();
        let mut builder =
            StreamBuilder::with_capacity(self.html_len(), Some(vec![0]));

        self.to_html_async_with_buf::<true>(
            &mut builder,
            &mut Position::FirstChild,
            true,
            false,
            vec![],
        );
        builder.finish()
    }

    /// Renders a view to an out-of-order stream of HTML with branch markers. This can be used to support libraries that diff
    /// HTML pages against one another, by marking sections of the view that branch to different
    /// types with marker comments.
    fn to_html_stream_out_of_order_branching(self) -> StreamBuilder
    where
        Self: Sized,
    {
        let mut builder =
            StreamBuilder::with_capacity(self.html_len(), Some(vec![0]));

        self.to_html_async_with_buf::<true>(
            &mut builder,
            &mut Position::FirstChild,
            true,
            true,
            vec![],
        );
        builder.finish()
    }

    /// Renders a view to HTML, writing it into the given buffer.
    fn to_html_with_buf(
        self,
        buf: &mut String,
        position: &mut Position,
        escape: bool,
        mark_branches: bool,
        extra_attrs: Vec<AnyAttribute>,
    );

    /// Renders a view into a buffer of (synchronous or asynchronous) HTML chunks.
    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
        self,
        buf: &mut StreamBuilder,
        position: &mut Position,
        escape: bool,
        mark_branches: bool,
        extra_attrs: Vec<AnyAttribute>,
    ) where
        Self: Sized,
    {
        buf.with_buf(|buf| {
            self.to_html_with_buf(
                buf,
                position,
                escape,
                mark_branches,
                extra_attrs,
            )
        });
    }

    /// Makes a set of DOM nodes rendered from HTML interactive.
    ///
    /// If `FROM_SERVER` is `true`, this HTML was rendered using [`RenderHtml::to_html`]
    /// (e.g., during server-side rendering ).
    ///
    /// If `FROM_SERVER` is `false`, the HTML was rendered using [`ToTemplate::to_template`]
    /// (e.g., into a `<template>` element).
    fn hydrate<const FROM_SERVER: bool>(
        self,
        cursor: &Cursor,
        position: &PositionState,
    ) -> Self::State;

    /// Asynchronously makes a set of DOM nodes rendered from HTML interactive.
    ///
    /// Async hydration is useful for types that may need to wait before being hydrated:
    /// for example, lazily-loaded routes need async hydration, because the client code
    /// may be loading asynchronously, while the server HTML was already rendered.
    fn hydrate_async(
        self,
        cursor: &Cursor,
        position: &PositionState,
    ) -> impl Future<Output = Self::State> {
        async { self.hydrate::<true>(cursor, position) }
    }

    /// Hydrates using [`RenderHtml::hydrate`], beginning at the given element.
    fn hydrate_from<const FROM_SERVER: bool>(
        self,
        el: &crate::renderer::types::Element,
    ) -> Self::State
    where
        Self: Sized,
    {
        self.hydrate_from_position::<FROM_SERVER>(el, Position::default())
    }

    /// Hydrates using [`RenderHtml::hydrate`], beginning at the given element and position.
    fn hydrate_from_position<const FROM_SERVER: bool>(
        self,
        el: &crate::renderer::types::Element,
        position: Position,
    ) -> Self::State
    where
        Self: Sized,
    {
        let cursor = Cursor::new(el.clone());
        let position = PositionState::new(position);
        self.hydrate::<FROM_SERVER>(&cursor, &position)
    }

    /// Convert into the equivalent value that is `'static`.
    fn into_owned(self) -> Self::Owned;
}

/// Allows a type to be mounted to the DOM.
pub trait Mountable {
    /// Detaches the view from the DOM.
    fn unmount(&mut self);

    /// Mounts a node to the interface.
    fn mount(
        &mut self,
        parent: &crate::renderer::types::Element,
        marker: Option<&crate::renderer::types::Node>,
    );

    /// Mounts a node to the interface. Returns `false` if it could not be mounted.
    fn try_mount(
        &mut self,
        parent: &crate::renderer::types::Element,
        marker: Option<&crate::renderer::types::Node>,
    ) -> bool {
        self.mount(parent, marker);
        true
    }

    /// Inserts another `Mountable` type before this one. Returns `false` if
    /// this does not actually exist in the UI (for example, `()`).
    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool;

    /// Inserts another `Mountable` type before this one, or before the marker
    /// if this one doesn't exist in the UI (for example, `()`).
    fn insert_before_this_or_marker(
        &self,
        parent: &crate::renderer::types::Element,
        child: &mut dyn Mountable,
        marker: Option<&crate::renderer::types::Node>,
    ) {
        if !self.insert_before_this(child) {
            child.mount(parent, marker);
        }
    }

    /// wip
    fn elements(&self) -> Vec<crate::renderer::types::Element>;
}

/// Indicates where a node should be mounted to its parent.
pub enum MountKind {
    /// Node should be mounted before this marker node.
    Before(crate::renderer::types::Node),
    /// Node should be appended to the parent’s children.
    Append,
}

impl<T> Mountable for Option<T>
where
    T: Mountable,
{
    fn unmount(&mut self) {
        if let Some(ref mut mounted) = self {
            mounted.unmount()
        }
    }

    fn mount(
        &mut self,
        parent: &crate::renderer::types::Element,
        marker: Option<&crate::renderer::types::Node>,
    ) {
        if let Some(ref mut inner) = self {
            inner.mount(parent, marker);
        }
    }

    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
        self.as_ref()
            .map(|inner| inner.insert_before_this(child))
            .unwrap_or(false)
    }

    fn elements(&self) -> Vec<crate::renderer::types::Element> {
        self.as_ref()
            .map(|inner| inner.elements())
            .unwrap_or_default()
    }
}

impl<T> Mountable for Rc<RefCell<T>>
where
    T: Mountable,
{
    fn unmount(&mut self) {
        self.borrow_mut().unmount()
    }

    fn mount(
        &mut self,
        parent: &crate::renderer::types::Element,
        marker: Option<&crate::renderer::types::Node>,
    ) {
        self.borrow_mut().mount(parent, marker);
    }

    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
        self.borrow().insert_before_this(child)
    }

    fn elements(&self) -> Vec<crate::renderer::types::Element> {
        self.borrow().elements()
    }
}

/// Allows data to be added to a static template.
pub trait ToTemplate {
    /// The HTML content of the static template.
    const TEMPLATE: &'static str = "";
    /// The `class` attribute content known at compile time.
    const CLASS: &'static str = "";
    /// The `style` attribute content known at compile time.
    const STYLE: &'static str = "";
    /// The length of the template.
    const LEN: usize = Self::TEMPLATE.len();

    /// Renders a view type to a template. This does not take actual view data,
    /// but can be used for constructing part of an HTML `<template>` that corresponds
    /// to a view of a particular type.
    fn to_template(
        buf: &mut String,
        class: &mut String,
        style: &mut String,
        inner_html: &mut String,
        position: &mut Position,
    );

    /// Renders a view type to a template in attribute position.
    fn to_template_attribute(
        buf: &mut String,
        class: &mut String,
        style: &mut String,
        inner_html: &mut String,
        position: &mut Position,
    ) {
        Self::to_template(buf, class, style, inner_html, position);
    }
}

/// Keeps track of what position the item currently being hydrated is in, relative to its siblings
/// and parents.
#[derive(Debug, Default, Clone)]
pub struct PositionState(Arc<RwLock<Position>>);

impl PositionState {
    /// Creates a new position tracker.
    pub fn new(position: Position) -> Self {
        Self(Arc::new(RwLock::new(position)))
    }

    /// Sets the current position.
    pub fn set(&self, position: Position) {
        *self.0.write().or_poisoned() = position;
    }

    /// Gets the current position.
    pub fn get(&self) -> Position {
        *self.0.read().or_poisoned()
    }

    /// Creates a new [`PositionState`], which starts with the same [`Position`], but no longer
    /// shares data with this `PositionState`.
    pub fn deep_clone(&self) -> Self {
        let current = self.get();
        Self(Arc::new(RwLock::new(current)))
    }
}

/// The position of this element, relative to others.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum Position {
    /// This is the current node.
    Current,
    /// This is the first child of its parent.
    #[default]
    FirstChild,
    /// This is the next child after another child.
    NextChild,
    /// This is the next child after a text node.
    NextChildAfterText,
    /// This is the only child of its parent.
    OnlyChild,
    /// This is the last child of its parent.
    LastChild,
}

/// Declares that this type can be converted into some other type, which can be rendered.
pub trait IntoRender {
    /// The renderable type into which this type can be converted.
    type Output;

    /// Consumes this value, transforming it into the renderable type.
    fn into_render(self) -> Self::Output;
}

impl<T> IntoRender for T
where
    T: Render,
{
    type Output = Self;

    fn into_render(self) -> Self::Output {
        self
    }
}
