//! Reusable hooks for the admin UI.
//!
//! These hooks provide common patterns for data fetching, pagination,
//! and form submissions to reduce duplication across pages.

use leptos::prelude::*;
use leptos_router::hooks::use_query_map;

/// A hook for fetching data from an API endpoint.
///
/// Handles the `#[cfg(feature = "hydrate")]` boilerplate and provides
/// a consistent pattern for client-side data fetching.
///
/// # Example
/// ```rust
/// let users = use_fetch::<Vec<User>>(move || format!("/api/users?page={}", page()));
/// ```
pub fn use_fetch<T>(url_fn: impl Fn() -> String + Send + Sync + 'static) -> LocalResource<Option<T>>
where
    T: serde::de::DeserializeOwned + Send + 'static,
{
    LocalResource::new(move || {
        let url = url_fn();
        async move {
            #[cfg(feature = "hydrate")]
            {
                use gloo_net::http::Request;
                match Request::get(&url).send().await {
                    Ok(r) if r.ok() => r.json::<T>().await.ok(),
                    _ => None,
                }
            }
            #[cfg(not(feature = "hydrate"))]
            {
                let _ = url;
                None::<T>
            }
        }
    })
}

/// A hook for fetching data with a condition.
///
/// Similar to `use_fetch` but allows skipping the fetch if a condition is false.
///
/// # Example
/// ```rust
/// let user = use_fetch_if::<UserDetail>(
///     move || !user_id().is_empty(),
///     move || format!("/api/users/{}", user_id())
/// );
/// ```
pub fn use_fetch_if<T>(
    condition: impl Fn() -> bool + Send + Sync + 'static,
    url_fn: impl Fn() -> String + Send + Sync + 'static,
) -> LocalResource<Option<T>>
where
    T: serde::de::DeserializeOwned + Send + 'static,
{
    LocalResource::new(move || {
        let should_fetch = condition();
        let url = url_fn();
        async move {
            if !should_fetch {
                return None;
            }
            #[cfg(feature = "hydrate")]
            {
                use gloo_net::http::Request;
                match Request::get(&url).send().await {
                    Ok(r) if r.ok() => r.json::<T>().await.ok(),
                    _ => None,
                }
            }
            #[cfg(not(feature = "hydrate"))]
            {
                let _ = url;
                None::<T>
            }
        }
    })
}

/// Pagination state extracted from URL query parameters.
pub struct PaginationState {
    /// The current search query (from `?q=...`).
    pub search_query: Signal<String>,
    /// The current page number (from `?page=...`, defaults to 1).
    pub page: Signal<i64>,
    /// Signal for the search input value (for controlled input).
    pub search_input: RwSignal<String>,
}

/// A hook for extracting pagination state from URL query parameters.
///
/// Returns search query, page number, and a controlled search input signal.
///
/// # Example
/// ```rust
/// let pagination = use_pagination();
/// let url = format!("/api/users?q={}&page={}", pagination.search_query.get(), pagination.page.get());
/// ```
pub fn use_pagination() -> PaginationState {
    let query = use_query_map();

    let search_query = Signal::derive(move || query.get().get("q").unwrap_or_default());

    let page = Signal::derive(move || {
        query
            .get()
            .get("page")
            .and_then(|p| p.parse().ok())
            .unwrap_or(1i64)
    });

    // Use get_untracked for initial value to avoid reactive tracking warning
    let initial_search = query.get_untracked().get("q").unwrap_or_default();
    let search_input = RwSignal::new(initial_search);

    PaginationState {
        search_query,
        page,
        search_input,
    }
}

/// Message state for form feedback (message text, is_success).
pub type MessageSignal = RwSignal<Option<(String, bool)>>;

/// Creates a message signal for form feedback.
pub fn use_message() -> MessageSignal {
    RwSignal::new(None)
}

/// A helper for making POST/PUT/DELETE requests with JSON body.
///
/// Returns the response or error message.
#[cfg(feature = "hydrate")]
pub async fn api_request<T>(
    method: &str,
    url: &str,
    body: Option<&serde_json::Value>,
) -> Result<T, String>
where
    T: serde::de::DeserializeOwned,
{
    use gloo_net::http::Request;

    let request = match method {
        "POST" => Request::post(url),
        "PUT" => Request::put(url),
        "DELETE" => Request::delete(url),
        _ => Request::get(url),
    };

    let response = if let Some(body) = body {
        request.json(body).map_err(|e| e.to_string())?.send().await
    } else {
        request.send().await
    }
    .map_err(|_| "Network error".to_string())?;

    if response.ok() {
        response
            .json::<T>()
            .await
            .map_err(|_| "Failed to parse response".to_string())
    } else {
        // Try to parse error response
        #[derive(serde::Deserialize)]
        struct ErrorResp {
            error: String,
        }
        if let Ok(err) = response.json::<ErrorResp>().await {
            Err(err.error)
        } else {
            Err("Request failed".to_string())
        }
    }
}

/// A helper for making POST/PUT/DELETE requests that return success/failure.
#[cfg(feature = "hydrate")]
pub async fn api_request_simple(
    method: &str,
    url: &str,
    body: Option<&serde_json::Value>,
) -> Result<(), String> {
    use gloo_net::http::Request;

    let request = match method {
        "POST" => Request::post(url),
        "PUT" => Request::put(url),
        "DELETE" => Request::delete(url),
        _ => Request::get(url),
    };

    let response = if let Some(body) = body {
        request.json(body).map_err(|e| e.to_string())?.send().await
    } else {
        request.send().await
    }
    .map_err(|_| "Network error".to_string())?;

    if response.ok() {
        Ok(())
    } else {
        #[derive(serde::Deserialize)]
        struct ErrorResp {
            error: String,
        }
        if let Ok(err) = response.json::<ErrorResp>().await {
            Err(err.error)
        } else {
            Err("Request failed".to_string())
        }
    }
}
