use crate::codec::ron::RonCodec;
use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
use crate::models;
use crate::models::HoldPosition;
use crate::models::Wall;
use leptos::ev::Event;
use leptos::html::Input;
use leptos::prelude::*;
use serde::Deserialize;
use serde::Serialize;
use server_fn::codec::Cbor;
use std::ops::Deref;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::FileList;

#[component]
pub fn EditWall() -> 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![HeaderItem {
            text: "← Ascend".to_string(),
            link: Some("/".to_string()),
        }],
        middle: vec![HeaderItem {
            text: "HOLDS".to_string(),
            link: None,
        }],
        right: vec![],
    };

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

            <div class="container mx-auto mt-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 mut holds = vec![];
    for hold in data.wall.holds.values().cloned() {
        holds.push(view! { <Hold hold /> });
    }

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

    view! {
        <div>
            <p class="my-4 font-semibold">"Click hold to replace image"</p>
            <div class=move || { grid_classes.clone() }>{holds}</div>
        </div>
    }
}

#[component]
fn Hold(hold: models::Hold) -> impl leptos::IntoView {
    let hold_position = hold.position;
    let file_input_ref = NodeRef::<Input>::new();

    let open_camera = move |_| {
        if let Some(input) = file_input_ref.get() {
            input.click(); // Trigger the file input click programmatically
        }
    };

    let upload = Action::from(ServerAction::<SetImage>::new());

    let hold = Signal::derive(move || {
        let refreshed = upload.value().get().map(Result::unwrap);
        refreshed.unwrap_or(hold.clone())
    });

    // Callback to handle file selection
    let on_file_input = move |event: Event| {
        let files: FileList = event.target().unwrap().unchecked_ref::<web_sys::HtmlInputElement>().files().unwrap();
        let file = files.item(0).unwrap();

        let file_reader = web_sys::FileReader::new().unwrap();
        file_reader.read_as_array_buffer(&file).unwrap();

        let on_load = Closure::wrap(Box::new(move |event: Event| {
            let file_reader: web_sys::FileReader = event.target().unwrap().dyn_into().unwrap();
            let file = file_reader.result().unwrap();
            let file = web_sys::js_sys::Uint8Array::new(&file);

            let mut file_contents = vec![0; file.length() as usize];
            file.copy_to(&mut file_contents);

            tracing::debug!("bytes: {:?}", &file_contents.len());

            let image = Image {
                file_name: "foo".to_string(),
                file_contents,
            };

            upload.dispatch(SetImage { hold_position, image });
        }) as Box<dyn FnMut(_)>);

        file_reader.set_onload(Some(on_load.as_ref().unchecked_ref()));
        on_load.forget();
    };

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

    view! {
        <button on:click=open_camera>
            <div class="bg-indigo-100 aspect-square rounded">{img}</div>
        </button>

        <input
            node_ref=file_input_ref
            type="file"
            accept="image/*"
            capture="user"
            style="display: none;"
            on:input=on_file_input
        />
    }
}

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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Image {
    file_name: String,
    file_contents: Vec<u8>,
}

#[server]
async fn load_initial_data() -> Result<RonCodec<InitialData>, ServerFnError> {
    todo!()
    // let wall = state.persistent.with(|s| s.wall.clone()).await;
    // Ok(RonCodec::new(InitialData { wall }))
}

#[server(name = SetImage, input = Cbor)]
#[tracing::instrument(skip(image))]
async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> {
    tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());

    // TODO: Fix file extension presumption, and possibly use uuid
    let filename = format!("row{}_col{}.jpg", hold_position.row, hold_position.col);
    tokio::fs::create_dir_all("datastore/public/holds").await?;
    tokio::fs::write(format!("datastore/public/holds/{filename}"), image.file_contents).await?;

    todo!()
    // let state = expect_context::<State>();
    // state
    //     .persistent
    //     .update(|s| {
    //         if let Some(hold) = s.wall.holds.get_mut(&hold_position) {
    //             hold.image = Some(models::Image { filename });
    //         }
    //     })
    //     .await?;

    // // Return updated hold
    // let hold = state.persistent.with(|s| s.wall.holds.get(&hold_position).cloned().unwrap()).await;

    // Ok(hold)
}
