//! Types for the App Router (`app/` directory).

use crate::DynamicSegment;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// A segment in the app directory tree.
///
/// Each directory under `app/` produces a segment, which may contain a
/// `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`,
/// and/or `route.ts`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppSegment {
    /// Directory segment name, e.g. "blog", "[slug]", "(marketing)"
    pub segment: String,
    /// Layout component for this segment
    pub layout: Option<PathBuf>,
    /// Page component (makes this segment a routable endpoint)
    pub page: Option<PathBuf>,
    /// Route handler (makes this segment an API endpoint, mutually exclusive with page)
    pub route: Option<PathBuf>,
    /// Loading fallback (Suspense boundary)
    pub loading: Option<PathBuf>,
    /// Error boundary component
    pub error_boundary: Option<PathBuf>,
    /// Not-found component
    pub not_found: Option<PathBuf>,
    /// Child segments
    pub children: Vec<AppSegment>,
}

/// A flattened route derived from the app directory tree.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppRoute {
    /// URL pattern, e.g. "/blog/:slug"
    pub pattern: String,
    /// Absolute path to the page component
    pub page_path: PathBuf,
    /// Layout chain from root to nearest layout (ordered root-first)
    pub layout_chain: Vec<PathBuf>,
    /// Loading boundaries parallel to layout_chain (None if no loading at that level)
    pub loading_chain: Vec<Option<PathBuf>>,
    /// Error boundaries parallel to layout_chain
    pub error_chain: Vec<Option<PathBuf>>,
    /// Dynamic segments extracted from the pattern
    pub dynamic_segments: Vec<DynamicSegment>,
    /// Specificity score for route matching priority
    pub specificity: u32,
    /// Route group name (e.g. "app", "payload"), or `None` if not in a group.
    /// Used for per-group RSC bundle splitting so each group's heavy imports
    /// don't pollute other groups.
    pub route_group: Option<String>,
}

/// A flattened API route handler derived from the app directory tree.
///
/// Unlike `AppRoute` (which renders React pages with layouts), an `AppApiRoute`
/// exports HTTP method handlers (GET, POST, PUT, DELETE, etc.) and returns
/// raw responses without any React rendering.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppApiRoute {
    /// URL pattern, e.g. "/api/users/:id"
    pub pattern: String,
    /// Absolute path to the route handler file (route.ts)
    pub handler_path: PathBuf,
    /// Dynamic segments extracted from the pattern
    pub dynamic_segments: Vec<DynamicSegment>,
    /// Specificity score for route matching priority
    pub specificity: u32,
}

impl AppApiRoute {
    /// Convert to a [`crate::Route`] for use with `RouteTrie`.
    pub fn to_route(&self) -> crate::Route {
        crate::Route {
            pattern: self.pattern.clone(),
            file_path: self
                .handler_path
                .file_name()
                .map(PathBuf::from)
                .unwrap_or_default(),
            abs_path: self.handler_path.clone(),
            dynamic_segments: self.dynamic_segments.clone(),
            page_type: crate::PageType::AppApi,
            specificity: self.specificity,
        }
    }
}

/// Result of scanning the `app/` directory.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppScanResult {
    /// The segment tree rooted at `app/`
    pub root: AppSegment,
    /// Flattened routes with resolved layout chains
    pub routes: Vec<AppRoute>,
    /// API route handlers (route.ts files)
    pub api_routes: Vec<AppApiRoute>,
    /// Path to the root layout. `None` when route groups each provide their own layout.
    pub root_layout: Option<PathBuf>,
}

impl AppRoute {
    /// Convert to a [`crate::Route`] for use with `RouteTrie`.
    pub fn to_route(&self) -> crate::Route {
        crate::Route {
            pattern: self.pattern.clone(),
            file_path: self
                .page_path
                .file_name()
                .map(PathBuf::from)
                .unwrap_or_default(),
            abs_path: self.page_path.clone(),
            dynamic_segments: self.dynamic_segments.clone(),
            page_type: crate::PageType::Regular,
            specificity: self.specificity,
        }
    }
}

impl AppScanResult {
    /// Convert all app page routes to `Route` objects for building a `RouteTrie`.
    pub fn to_routes(&self) -> Vec<crate::Route> {
        self.routes.iter().map(|r| r.to_route()).collect()
    }

    /// Convert all app API routes to `Route` objects for building a `RouteTrie`.
    pub fn to_api_routes(&self) -> Vec<crate::Route> {
        self.api_routes.iter().map(|r| r.to_route()).collect()
    }

    /// Group routes by their route group name.
    ///
    /// Returns `(group_name, routes)` pairs sorted by group name.
    /// Routes not in any group are keyed by `None`.
    pub fn routes_by_group(&self) -> Vec<(Option<String>, Vec<&AppRoute>)> {
        use std::collections::BTreeMap;
        let mut groups: BTreeMap<Option<String>, Vec<&AppRoute>> = BTreeMap::new();
        for route in &self.routes {
            groups
                .entry(route.route_group.clone())
                .or_default()
                .push(route);
        }
        groups.into_iter().collect()
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;

    #[test]
    fn app_api_route_to_route() {
        let api = AppApiRoute {
            pattern: "/api/hello".to_string(),
            handler_path: PathBuf::from("/app/api/hello/route.ts"),
            dynamic_segments: vec![],
            specificity: 100,
        };
        let route = api.to_route();
        assert_eq!(route.pattern, "/api/hello");
        assert_eq!(route.page_type, crate::PageType::AppApi);
        assert_eq!(route.file_path, PathBuf::from("route.ts"));
    }

    #[test]
    fn to_api_routes_converts() {
        let result = AppScanResult {
            root: AppSegment {
                segment: String::new(),
                layout: None,
                page: None,
                route: None,
                loading: None,
                error_boundary: None,
                not_found: None,
                children: vec![],
            },
            routes: vec![],
            api_routes: vec![AppApiRoute {
                pattern: "/api/test".to_string(),
                handler_path: PathBuf::from("/app/api/test/route.ts"),
                dynamic_segments: vec![],
                specificity: 100,
            }],
            root_layout: None,
        };
        let routes = result.to_api_routes();
        assert_eq!(routes.len(), 1);
        assert_eq!(routes[0].pattern, "/api/test");
    }
}
