//! Unified Content Loading Traits
//!
//! Provides common interfaces for content loading operations across
//! server utilities, client components, and hot-reload systems.

use std::collections::HashMap;

use crate::content::UnifiedContentItem;
use crate::fluent::{ContentIndex, MarkdownMetadata};

/// Result type for content loading operations
pub type ContentResult<T> = Result<T, ContentLoadError>;

/// Errors that can occur during content loading
#[derive(Debug, thiserror::Error)]
pub enum ContentLoadError {
    #[error("Content not found: {0}")]
    NotFound(String),

    #[error("Content index load failed: {0}")]
    IndexLoadFailed(String),

    #[error("Content parse error: {0}")]
    ParseError(String),

    #[error("IO error: {0}")]
    IoError(String),

    #[error("Configuration error: {0}")]
    ConfigError(String),
}

/// Unified trait for loading content across different contexts
pub trait ContentLoader {
    /// Load a content index for a specific content type and language
    fn load_index(&self, content_type: &str, language: &str) -> ContentResult<ContentIndex>;

    /// Load a specific content item by slug
    fn load_item(
        &self,
        content_type: &str,
        language: &str,
        slug: &str,
    ) -> ContentResult<UnifiedContentItem>;

    /// Load raw markdown content by slug
    fn load_markdown(
        &self,
        content_type: &str,
        language: &str,
        slug: &str,
    ) -> ContentResult<String>;

    /// Process markdown content and return metadata and HTML
    fn process_markdown(&self, content: &str) -> ContentResult<(MarkdownMetadata, String)>;
}

/// Extended trait for content loaders that can handle path resolution
pub trait PathResolvingContentLoader: ContentLoader {
    /// Map URL slug to content ID using slug resolution
    fn resolve_slug(&self, url_slug: &str, content_type: &str, language: &str) -> String;

    /// Generate HTML path for content with category support
    fn get_html_path_with_category(
        &self,
        content_type: &str,
        language: &str,
        item: &UnifiedContentItem,
        category_from_path: Option<&str>,
    ) -> String;
}

/// Standard implementation that uses the existing fluent modules
pub struct StandardContentLoader;

impl ContentLoader for StandardContentLoader {
    fn load_index(&self, content_type: &str, language: &str) -> ContentResult<ContentIndex> {
        crate::fluent::load_content_index(content_type, language)
            .map_err(|e| ContentLoadError::IndexLoadFailed(e.to_string()))
    }

    fn load_item(
        &self,
        content_type: &str,
        language: &str,
        slug: &str,
    ) -> ContentResult<UnifiedContentItem> {
        let index = self.load_index(content_type, language)?;
        index
            .items
            .into_iter()
            .find(|item| item.slug == slug)
            .ok_or_else(|| {
                ContentLoadError::NotFound(format!("{}:{}:{}", content_type, language, slug))
            })
    }

    fn load_markdown(
        &self,
        content_type: &str,
        language: &str,
        slug: &str,
    ) -> ContentResult<String> {
        crate::fluent::load_content_by_type(content_type, slug, language)
            .map_err(|e| ContentLoadError::IoError(e.to_string()))
    }

    fn process_markdown(&self, content: &str) -> ContentResult<(MarkdownMetadata, String)> {
        crate::fluent::process_markdown_file(content)
            .map_err(|e| ContentLoadError::ParseError(e.to_string()))
    }
}

impl PathResolvingContentLoader for StandardContentLoader {
    fn resolve_slug(&self, url_slug: &str, content_type: &str, language: &str) -> String {
        match crate::fluent::resolve_content_slug_with_language_priority(
            url_slug,
            content_type,
            language,
        ) {
            Some(resolution) => resolution.canonical_id,
            None => url_slug.to_string(),
        }
    }

    fn get_html_path_with_category(
        &self,
        content_type: &str,
        language: &str,
        item: &UnifiedContentItem,
        category_from_path: Option<&str>,
    ) -> String {
        let server_root = crate::generated_config::get_server_content_url();

        // Check if item has html_file metadata
        if let Some(html_file) = item.metadata.get("html_file").and_then(|h| h.as_str()) {
            if html_file.contains('/') {
                // html_file includes category (e.g., "rust/filename.html")
                format!(
                    "{}/{}/{}/{}",
                    server_root, content_type, language, html_file
                )
            } else {
                // html_file is just the filename, use category from path if available
                if let Some(category) = category_from_path {
                    format!(
                        "{}/{}/{}/{}/{}",
                        server_root, content_type, language, category, html_file
                    )
                } else {
                    // No category from path, try to extract from source file structure
                    let source_parts: Vec<&str> = item.source_file.split('/').collect();
                    if source_parts.len() >= 4 {
                        let item_language = source_parts[1]; // Extract language from source path
                        let category = source_parts[2];
                        format!(
                            "{}/{}/{}/{}/{}",
                            server_root, content_type, item_language, category, html_file
                        )
                    } else {
                        // Final fallback: no category
                        format!(
                            "{}/{}/{}/{}",
                            server_root, content_type, language, html_file
                        )
                    }
                }
            }
        } else {
            // Fallback: use category from path if available
            if let Some(category) = category_from_path {
                format!(
                    "{}/{}/{}/{}/{}.html",
                    server_root,
                    content_type,
                    language,
                    category,
                    item.id.replace(".html", "")
                )
            } else {
                crate::fluent::get_html_path(content_type, language, &item.id.replace(".html", ""))
            }
        }
    }
}

/// Async trait for content loaders that work in WASM/browser contexts
#[cfg(target_arch = "wasm32")]
pub trait AsyncContentLoader {
    /// Load HTML content from a URL
    fn load_html_content(
        &self,
        path: &str,
    ) -> impl std::future::Future<Output = ContentResult<String>>;
}

#[cfg(target_arch = "wasm32")]
impl AsyncContentLoader for StandardContentLoader {
    async fn load_html_content(&self, path: &str) -> ContentResult<String> {
        use leptos::web_sys::{Request, RequestInit, Response};
        use wasm_bindgen::JsCast;
        use wasm_bindgen_futures::JsFuture;

        // Use relative URLs to avoid CSP issues - browser will automatically use same
        // origin
        let fetch_url = if path.starts_with("http://") || path.starts_with("https://") {
            // Path is already a full URL - keep as is
            path.to_string()
        } else {
            // Path is relative - keep it relative to avoid CSP violations
            // Browser will automatically resolve relative URLs against the current origin
            path.to_string()
        };

        let window = web_sys::window()
            .ok_or_else(|| ContentLoadError::ConfigError("No window available".to_string()))?;
        let opts = RequestInit::new();
        opts.set_method("GET");

        let request = Request::new_with_str_and_init(&fetch_url, &opts)
            .map_err(|e| ContentLoadError::IoError(format!("Request creation failed: {:?}", e)))?;

        let resp_value = JsFuture::from(window.fetch_with_request(&request))
            .await
            .map_err(|e| ContentLoadError::IoError(format!("Fetch failed: {:?}", e)))?;

        let resp: Response = resp_value
            .dyn_into()
            .map_err(|e| ContentLoadError::IoError(format!("Response cast failed: {:?}", e)))?;

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

        let text_promise = resp
            .text()
            .map_err(|e| ContentLoadError::IoError(format!("Text promise failed: {:?}", e)))?;

        let text_value = JsFuture::from(text_promise)
            .await
            .map_err(|e| ContentLoadError::IoError(format!("Text extraction failed: {:?}", e)))?;

        let html_content = text_value.as_string().ok_or_else(|| {
            ContentLoadError::IoError("Failed to convert response to string".to_string())
        })?;

        Ok(html_content)
    }
}
