use crate::codec::ron::RonCodec;
use crate::components::button::Button;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
use crate::models;
use crate::models::HoldRole;
use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode;
use serde::Deserialize;
use serde::Serialize;
use std::ops::Deref;

#[component]
pub fn Wall() -> impl leptos::IntoView {
    let load = async move {
        // TODO: What to do about this unwrap?
        load_initial_data().await.unwrap()
    };

    let header_items = HeaderItems {
        left: vec![],
        middle: vec![HeaderItem {
            text: "ASCEND".to_string(),
            link: None,
        }],
        right: vec![
            HeaderItem {
                text: "Routes".to_string(),
                link: Some("/wall/routes".to_string()),
            },
            HeaderItem {
                text: "Holds".to_string(),
                link: Some("/wall/edit".to_string()),
            },
        ],
    };

    leptos::view! {
        <div class="min-w-screen min-h-screen bg-slate-900">
            <StyledHeader items=header_items />

            <div class="m-2">
                <Await future=load let:data>
                    <Ready data=data.deref().to_owned() />
                </Await>
            </div>
        </div>
    }
}

#[component]
fn Ready(data: InitialData) -> impl leptos::IntoView {
    tracing::debug!("ready");

    let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
    let problem_fetcher = LocalResource::new(move || async move {
        tracing::info!("Loading random problem");
        let problem = get_random_problem().await.expect("cannot get random problem");
        if problem.is_none() {
            tracing::info!("No problem returned by server in response to request for random problem");
        }
        current_problem_writer.set(problem.into_inner());
    });

    let mut cells = vec![];
    for (&hold_position, hold) in &data.wall.holds {
        let role = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied());
        let role = Signal::derive(role);

        let cell = view! { <Hold role hold=hold.clone() /> };
        cells.push(cell);
    }

    let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", data.wall.rows, data.wall.cols);

    view! {
        <div class="grid grid-cols-[auto,1fr] gap-8">
            // Render the wall
            <div style="max-height: 90vh; max-width: 90vh;" class=move || { grid_classes.clone() }>{cells}</div>

            <div>
                <div>
                    // TODO:
                    // <p>{current_problem.read().as_ref().map(|p| p.name.clone())}</p>
                    // <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
                </div>

                <Button onclick=move |_| problem_fetcher.mark_dirty() text="➤ Next problem" />
            </ div>
        </div>
    }
}

#[component]
fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> impl leptos::IntoView {
    let class = move || {
        let role_classes = match role.get() {
            Some(HoldRole::Start) => Some("outline outline-offset-2 outline-green-500"),
            Some(HoldRole::Normal) => Some("outline outline-offset-2 outline-blue-500"),
            Some(HoldRole::Zone) => Some("outline outline-offset-2 outline-amber-500"),
            Some(HoldRole::End) => Some("outline outline-offset-2 outline-red-500"),
            None => Some("brightness-50"),
            // None => None,
        };
        let mut s = "bg-sky-100 aspect-square rounded".to_string();
        if let Some(c) = role_classes {
            s.push(' ');
            s.push_str(c);
        }
        s
    };

    let img = hold.image.map(|img| {
        let src = format!("/files/holds/{}", img.filename);
        view! { <img class="object-cover w-full h-full" src=src /> }
    });

    view! { <div class=class>{img}</div> }
}

#[derive(Serialize, Deserialize, Clone)]
pub struct InitialData {
    wall: models::Wall,
}

#[server]
async fn load_initial_data() -> Result<RonCodec<InitialData>, ServerFnError> {
    todo!()
    // let state = expect_context::<State>();

    // let wall = state.persistent.with(|s| s.wall.clone()).await;
    // Ok(RonCodec::new(InitialData { wall }))
}

#[server]
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
    todo!()
    // use rand::seq::IteratorRandom;

    // let state = expect_context::<State>();

    // let problem = state
    //     .persistent
    //     .with(|s| {
    //         let problems = &s.problems.problems;
    //         let rng = &mut rand::thread_rng();
    //         problems.iter().choose(rng).cloned()
    //     })
    //     .await;

    // tracing::debug!("Returning randomized problem: {problem:?}");

    // Ok(RonCodec::new(problem))
}
