use crate::openapi::{Parameter, ResponseKind};

/// Request body shape for POST/PUT/PATCH endpoints.
#[derive(Debug, Clone)]
pub struct RequestBody {
    /// Body content type as a name (e.g. "string" for text/plain, "Foo" for an `application/json` $ref).
    pub body_type: String,
    /// Whether the body is required.
    pub required: bool,
}

/// Endpoint information extracted from OpenAPI spec.
#[derive(Debug, Clone)]
pub struct Endpoint {
    /// HTTP method (GET, POST, etc.)
    pub method: String,
    /// Path template (e.g., "/blocks/{hash}")
    pub path: String,
    /// Operation ID (e.g., "getBlockByHash")
    pub operation_id: Option<String>,
    /// Short summary
    pub summary: Option<String>,
    /// Detailed description
    pub description: Option<String>,
    /// Path parameters
    pub path_params: Vec<Parameter>,
    /// Query parameters
    pub query_params: Vec<Parameter>,
    /// Request body, if any (POST/PUT/PATCH).
    pub request_body: Option<RequestBody>,
    /// Body kind for the 200 response.
    pub response_kind: ResponseKind,
    /// Whether this endpoint is deprecated
    pub deprecated: bool,
    /// Whether this endpoint supports CSV format (text/csv content type)
    pub supports_csv: bool,
}

impl Endpoint {
    /// Returns true if this endpoint should be included in client generation.
    /// Non-deprecated GET and POST endpoints are included.
    pub fn should_generate(&self) -> bool {
        !self.deprecated && (self.method == "GET" || self.method == "POST")
    }

    /// Returns true if this endpoint returns JSON.
    pub fn returns_json(&self) -> bool {
        matches!(self.response_kind, ResponseKind::Json(_))
    }

    /// Returns true if this endpoint returns binary data (application/octet-stream).
    pub fn returns_binary(&self) -> bool {
        matches!(self.response_kind, ResponseKind::Binary)
    }

    /// Returns true if this endpoint returns plain text (typed or opaque).
    pub fn returns_text(&self) -> bool {
        matches!(self.response_kind, ResponseKind::Text(_))
    }

    /// Schema name attached to the response, if any.
    pub fn schema_name(&self) -> Option<&str> {
        self.response_kind.schema_name()
    }

    /// Returns the operation ID or generates one from the path.
    /// The returned string uses the raw case from the spec (typically camelCase).
    pub fn operation_name(&self) -> String {
        if let Some(op_id) = &self.operation_id {
            return op_id.clone();
        }
        let mut parts: Vec<String> = Vec::new();
        let mut prev_segment = "";

        for segment in self.path.split('/').filter(|s| !s.is_empty()) {
            if segment == "api" {
                continue;
            }
            if let Some(param) = segment.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
                let prev_normalized = prev_segment.replace('-', "_");
                if !prev_normalized.ends_with(param) {
                    parts.push(format!("by_{}", param));
                }
            } else {
                let normalized = segment.replace('-', "_");
                parts.push(normalized);
                prev_segment = segment;
            }
        }
        format!("get_{}", parts.join("_"))
    }
}
