//! Enhanced Simple Content Grid Component
//!
//! Pure functional content grid with content-kinds.toml configuration support.
//! Uses direct async HTTP fetch (same pattern as UnifiedCategoryFilter) to
//! avoid race conditions from polling and reactive-owner loss.

use std::collections::HashMap;

use leptos::prelude::*;
use rustelo_core_lib::{
    content::UnifiedContentItem, create_content_kind_registry, fluent::load_content_index,
    i18n::create_content_provider,
};

use crate::content::card::UnifiedContentCard;
use crate::content::pagination::PaginationControls;

/// Async HTTP fetch for content index JSON — WASM only, mirrors
/// fetch_filter_data pattern. Parses the API JSON format via
/// UnifiedContentItem::from_json() which handles field mapping differences
/// (category vs categories, date vs created_at, etc.).
#[cfg(target_arch = "wasm32")]
async fn fetch_content_items_async(
    content_type: &str,
    language: &str,
    category_filter: &str,
    page_size: usize,
    page_offset: usize,
) -> Result<(Vec<UnifiedContentItem>, usize), String> {
    use wasm_bindgen::JsCast;
    use wasm_bindgen_futures::JsFuture;
    use web_sys::{Request, RequestInit, RequestMode, Response};

    let url = format!("/r/{}/{}/index.json", content_type, language);

    let opts = RequestInit::new();
    opts.set_method("GET");
    opts.set_mode(RequestMode::SameOrigin);

    let request = Request::new_with_str_and_init(&url, &opts)
        .map_err(|e| format!("request init failed: {:?}", e))?;

    let window = web_sys::window().ok_or("no window")?;
    let resp_value = JsFuture::from(window.fetch_with_request(&request))
        .await
        .map_err(|e| format!("fetch failed: {:?}", e))?;
    let resp: Response = resp_value
        .dyn_into()
        .map_err(|_| "cast to Response failed")?;

    if !resp.ok() {
        return Err(format!("HTTP {}", resp.status()));
    }

    let json_val = JsFuture::from(resp.json().map_err(|e| format!("json() failed: {:?}", e))?)
        .await
        .map_err(|e| format!("json future failed: {:?}", e))?;

    let text = js_sys::JSON::stringify(&json_val)
        .map_err(|e| format!("stringify failed: {:?}", e))?
        .as_string()
        .ok_or("stringify returned non-string")?;

    // Parse as generic JSON value — the API format uses different field names
    // (e.g. "posts" array, "category" singular, "date" instead of "created_at")
    // that don't match UnifiedContentItem's serde fields. Use from_json() which
    // handles all these mappings explicitly.
    let raw: serde_json::Value =
        serde_json::from_str(&text).map_err(|e| format!("json parse failed: {}", e))?;

    let posts_arr = raw
        .get("posts")
        .or_else(|| raw.get("items"))
        .and_then(|v| v.as_array())
        .ok_or("response has no posts/items array")?;

    let items: Vec<UnifiedContentItem> = posts_arr
        .iter()
        .filter_map(|v| UnifiedContentItem::from_json(v, content_type, language))
        .collect();

    Ok(process_content_index_items(
        items,
        category_filter,
        page_size,
        page_offset,
    ))
}

/// Filter, sort, and paginate content items.
fn process_content_index_items(
    items: Vec<UnifiedContentItem>,
    category_filter: &str,
    page_size: usize,
    page_offset: usize,
) -> (Vec<UnifiedContentItem>, usize) {
    let mut items: Vec<UnifiedContentItem> = items
        .into_iter()
        .filter(|item| item.published)
        .filter(|item| {
            category_filter.is_empty()
                || category_filter == "all"
                || item.categories.contains(&category_filter.to_string())
        })
        .collect();

    let total = items.len();

    items.sort_by(|a, b| {
        // Featured items first
        match (a.featured, b.featured) {
            (true, false) => return std::cmp::Ordering::Less,
            (false, true) => return std::cmp::Ordering::Greater,
            _ => {}
        }
        // Then by sort_order (lower first), treating 0 as "no preference" (last)
        match (a.sort_order, b.sort_order) {
            (0, 0) => {}
            (0, _) => return std::cmp::Ordering::Greater,
            (_, 0) => return std::cmp::Ordering::Less,
            (a_ord, b_ord) => {
                let cmp = a_ord.cmp(&b_ord);
                if cmp != std::cmp::Ordering::Equal {
                    return cmp;
                }
            }
        }
        // Finally by date descending
        b.created_at.cmp(&a.created_at)
    });

    let paged = items
        .into_iter()
        .skip(page_offset)
        .take(page_size)
        .collect();
    (paged, total)
}

/// Enhanced Simple Content Grid with content-kinds.toml configuration support
#[component]
pub fn SimpleContentGrid(
    content_type: String,
    language: String,
    #[prop(default = HashMap::new())] lang_content: HashMap<String, String>,
    #[prop(default = "".to_string())] category_filter: String,
    #[prop(default = 12)] limit: usize,
    #[prop(default = 1)] current_page: u32,
    #[prop(default = true)] enable_pagination: bool,
) -> impl IntoView {
    tracing::info!(
        "🔍 SimpleContentGrid: content_type='{}', language='{}', category_filter='{}'",
        content_type,
        language,
        category_filter
    );

    let content_provider = create_content_provider(Some(lang_content.clone()));

    let content_type_for_cards = content_type.clone();
    let content_type_for_pagination = content_type.clone();
    let content_type_for_empty = content_type.clone();

    // Resolve the base path once so UnifiedContentCard generates correct category
    // URLs. Falls back to "/{content_type}" only when no route config is
    // available.
    let card_base_path =
        rustelo_core_lib::routing::utils::get_content_type_base_path(&content_type, &language)
            .unwrap_or_else(|| format!("/{}", content_type));

    let content_config = {
        let registry = create_content_kind_registry();
        registry
            .kinds
            .get(&content_type)
            .cloned()
            .unwrap_or_else(|| {
                tracing::error!(
                    "❌ CONFIG_ERROR: Unknown content_type: '{}', using default",
                    content_type
                );
                rustelo_core_lib::ContentConfig::from_env()
            })
    };

    let effective_page_size = if enable_pagination { 12 } else { limit };
    let page_offset = if enable_pagination {
        ((current_page.saturating_sub(1)) as usize) * effective_page_size
    } else {
        0
    };

    let page = RwSignal::new(current_page);
    let page_size = RwSignal::new(effective_page_size as u32);

    // Signal that holds the loaded items. Starts empty; updated after async fetch.
    let content_signal = RwSignal::new((Vec::<UnifiedContentItem>::new(), 0usize));

    // Synchronous initial load — works on SSR and on WASM cache hit.
    let initial = load_content_for_type_sync(
        &content_type,
        &language,
        &category_filter,
        &content_config,
        effective_page_size,
        page_offset,
    );
    // SSR: store initial data for non-reactive reads in DynChild closures.
    // StoredValue::get_value() is non-reactive — eliminates the
    // "outside reactive tracking context" warnings from signal reads in SSR
    // DynChild.
    #[cfg(not(target_arch = "wasm32"))]
    let ssr_data = StoredValue::new((initial.0.clone(), initial.1));
    content_signal.set(initial);

    // WASM: if sync load returned empty, fetch via HTTP directly (same pattern as
    // UnifiedCategoryFilter) — single await, no polling, no race with reactive
    // owner.
    #[cfg(target_arch = "wasm32")]
    {
        let ct = content_type.clone();
        let lang = language.clone();
        let cat = category_filter.clone();
        let write = content_signal.write_only();

        web_sys::console::log_1(
            &format!(
                "🗂️ SimpleContentGrid WASM init: ct='{}', lang='{}', initial_empty={}",
                ct,
                lang,
                content_signal.get_untracked().0.is_empty()
            )
            .into(),
        );

        Effect::new(move |_| {
            if !content_signal.get_untracked().0.is_empty() {
                web_sys::console::log_1(
                    &format!(
                        "🗂️ SimpleContentGrid Effect: data already loaded ({} items), skipping \
                         fetch",
                        content_signal.get_untracked().0.len()
                    )
                    .into(),
                );
                return;
            }
            let ct = ct.clone();
            let lang = lang.clone();
            let cat = cat.clone();

            web_sys::console::log_1(
                &format!(
                    "🗂️ SimpleContentGrid Effect: spawning fetch for {}/{}",
                    ct, lang
                )
                .into(),
            );

            wasm_bindgen_futures::spawn_local(async move {
                web_sys::console::log_1(
                    &format!(
                        "🗂️ SimpleContentGrid fetch_content_items_async: starting for {}/{}",
                        ct, lang
                    )
                    .into(),
                );
                match fetch_content_items_async(&ct, &lang, &cat, effective_page_size, page_offset)
                    .await
                {
                    Ok(result) => {
                        web_sys::console::log_1(
                            &format!(
                                "✅ SimpleContentGrid: loaded {} items for {}/{}",
                                result.0.len(),
                                ct,
                                lang
                            )
                            .into(),
                        );
                        write.set(result);
                        web_sys::console::log_1(&"✅ SimpleContentGrid: write.set() called".into());
                    }
                    Err(e) => {
                        web_sys::console::warn_1(
                            &format!(
                                "❌ SimpleContentGrid: fetch failed for {}/{}: {}",
                                ct, lang, e
                            )
                            .into(),
                        );
                    }
                }
            });
        });
    }

    let config_page_size_options = vec![6u32, 12, 24, 48];
    let config_show_page_info = true;

    let update_page_in_url = move |new_page: u32, _new_page_size: u32| {
        tracing::debug!("Page changed to: {}", new_page);
    };

    // Only needed in WASM where content loads async and closures must re-run.
    // SSR reads from ssr_data.get_value() directly — non-reactive, no tracking
    // warning.
    #[cfg(target_arch = "wasm32")]
    let pagination_total = Memo::new(move |_| content_signal.get().1);

    let css_classes = format!(
        "enhanced-content-grid default content-type-{} layout-grid-layout",
        &content_type
    );
    let grid_classes = "grid md:grid-cols-2 lg:grid-cols-3 gap-6";

    view! {
        <div class={css_classes}>
            <div class={grid_classes}>
                {move || {
                    // SSR: get_value() is non-reactive — no "outside reactive tracking context" warning.
                    // WASM: get() subscribes so the grid re-renders when async fetch completes.
                    #[cfg(not(target_arch = "wasm32"))]
                    let items = ssr_data.get_value().0;
                    #[cfg(target_arch = "wasm32")]
                    let items = content_signal.get().0;
                    #[cfg(target_arch = "wasm32")]
                    web_sys::console::log_1(
                        &format!("🗂️ SimpleContentGrid DynChild: rendering {} items", items.len()).into()
                    );
                    items.into_iter().map(|item| {
                        view! {
                            <UnifiedContentCard
                                content_item=item.clone()
                                content_type=content_type_for_cards.to_string()
                                language=language.clone()
                                lang_content=lang_content.clone()
                                _content_config=content_config.clone()
                                base_path=card_base_path.clone()
                            />
                        }
                    }).collect_view()
                }}
            </div>

            {
                let options_clone = config_page_size_options.clone();
                let show_info_clone = config_show_page_info;
                move || {
                    // SSR: get_value() is non-reactive — no "outside reactive tracking context" warning.
                    // WASM: Memo::get() subscribes so the closure re-runs when content loads async.
                    #[cfg(not(target_arch = "wasm32"))]
                    let total = ssr_data.get_value().1;
                    #[cfg(target_arch = "wasm32")]
                    let total = pagination_total.get();
                    if enable_pagination && total > effective_page_size {
                        let total_pages = (total + effective_page_size - 1) / effective_page_size;
                        let page_change_cb = Callback::new(move |new_page: u32| {
                            page.set(new_page);
                            update_page_in_url(new_page, page_size.get_untracked());
                        });
                        let page_size_change_cb = Callback::new(move |new_size: u32| {
                            page_size.set(new_size);
                            page.set(1);
                            update_page_in_url(1, new_size);
                        });
                        view! {
                            <PaginationControls
                                current_page=Signal::from(page)
                                total_pages=Signal::from(total_pages as u32)
                                total_items=total as u32
                                items_per_page=Signal::from(page_size)
                                content_type=content_type_for_pagination.to_string()
                                lang_content=std::collections::HashMap::new()
                                on_page_change=page_change_cb
                                on_page_size_change=page_size_change_cb
                                page_size_options=options_clone.clone()
                                show_page_info=show_info_clone
                            />
                        }.into_any()
                    } else {
                        view! { <></> }.into_any()
                    }
                }
            }

            {move || {
                // SSR: get_value() is non-reactive — no "outside reactive tracking context" warning.
                // WASM: get() subscribes so the empty-state hides reactively once content loads.
                #[cfg(not(target_arch = "wasm32"))]
                let (items, _) = ssr_data.get_value();
                #[cfg(target_arch = "wasm32")]
                let (items, _) = content_signal.get();
                if items.is_empty() {
                    view! {
                        <div class="empty-state text-center py-12">
                            <div class="max-w-md mx-auto">
                                <div class="text-4xl mb-4 opacity-50">{"📄"}</div>
                                <h3 class="text-lg font-medium ds-text mb-2">
                                    {content_provider.t_with_prefixes("no-content", &[&content_type_for_empty, "content"], Some("No content available"))}
                                </h3>
                                <p class="text-sm ds-text-secondary">
                                    {if !category_filter.is_empty() {
                                        format!("No {} content found for category: {}", content_type_for_empty, category_filter)
                                    } else {
                                        format!("No {} content is currently available", content_type_for_empty)
                                    }}
                                </p>
                            </div>
                        </div>
                    }.into_any()
                } else {
                    view! { <></> }.into_any()
                }
            }}
        </div>
    }
}

/// Synchronous content load — uses embedded resources or cache, never HTTP.
fn load_content_for_type_sync(
    content_type: &str,
    language: &str,
    category_filter: &str,
    _content_config: &rustelo_core_lib::ContentConfig,
    page_size: usize,
    page_offset: usize,
) -> (Vec<UnifiedContentItem>, usize) {
    match load_content_index(content_type, language) {
        Ok(index) => {
            process_content_index_items(index.items, category_filter, page_size, page_offset)
        }
        Err(e) => {
            tracing::warn!("sync load failed for {}/{}: {}", content_type, language, e);
            (vec![], 0)
        }
    }
}
