//! Path-handling and endpoint extraction for OpenAPI / Swagger specs.
//!
//! Reads `paths.<path>.<method>` entries from a parsed YAML/JSON document
//! and emits one `Route` Symbol per operation, including operationId,
//! summary, tags, and parameters (both path-level and operation-level).

use ckg_core::{Kind, Symbol, node_id};
use serde_yml::Value;

use crate::ParseOutput;

pub(super) const HTTP_METHODS: &[&str] = &[
    "get", "post", "put", "patch", "delete", "head", "options", "trace",
];

pub(super) fn extract_paths(doc: &Value, repo_id: &str, relpath: &str, out: &mut ParseOutput) {
    let Some(paths) = doc.get("paths").and_then(Value::as_mapping) else {
        return;
    };
    for (path_key, ops) in paths {
        let Some(p) = path_key.as_str() else { continue };
        let Some(ops_map) = ops.as_mapping() else {
            continue;
        };
        for (verb_key, op) in ops_map {
            let Some(verb) = verb_key.as_str() else {
                continue;
            };
            if !HTTP_METHODS.contains(&verb.to_ascii_lowercase().as_str()) {
                continue;
            }
            let method = verb.to_ascii_uppercase();
            let mut doc_lines: Vec<String> = Vec::new();
            if let Some(id) = op.get("operationId").and_then(Value::as_str) {
                doc_lines.push(format!("operationId={id}"));
            }
            if let Some(summary) = op.get("summary").and_then(Value::as_str) {
                doc_lines.push(format!("summary={summary}"));
            }
            if let Some(tags) = op.get("tags").and_then(Value::as_sequence) {
                let tag_list: Vec<&str> = tags.iter().filter_map(Value::as_str).collect();
                if !tag_list.is_empty() {
                    doc_lines.push(format!("tags=[{}]", tag_list.join(", ")));
                }
            }
            // Issue #10: extract `paths.<path>.<method>.parameters`
            // and `paths.<path>.parameters` (path-level shared params).
            let mut params: Vec<String> = Vec::new();
            for params_node in [
                ops.get("parameters").and_then(Value::as_sequence),
                op.get("parameters").and_then(Value::as_sequence),
            ]
            .into_iter()
            .flatten()
            {
                for param in params_node {
                    if let Some(name) = param.get("name").and_then(Value::as_str) {
                        let location = param.get("in").and_then(Value::as_str).unwrap_or("query");
                        let required = param
                            .get("required")
                            .and_then(Value::as_bool)
                            .unwrap_or(false);
                        params.push(format!(
                            "{name}:{location}{}",
                            if required { ":required" } else { "" }
                        ));
                    }
                }
            }
            if !params.is_empty() {
                doc_lines.push(format!("params=[{}]", params.join(", ")));
            }
            let qname = format!("{method} {p}");
            // Item #25: include relpath in id so duplicate `METHOD path`
            // across multiple OpenAPI files in one repo don't collide
            // (UPSERT silently overwrote the first). `node_id` already
            // factors relpath in for schemas; routes now match.
            let id = node_id(repo_id, "openapi", relpath, &qname, Kind::Route);
            out.symbols.push(Symbol {
                id,
                qname: qname.clone(),
                name: qname,
                kind: Kind::Route,
                file: relpath.to_string(),
                line: 0,
                col: 0,
                is_public: true,
                doc: doc_lines.join("\n"),
                hash: blake3::hash(format!("{method}{p}{relpath}").as_bytes()).to_hex()[..16]
                    .to_string(),
            });
        }
    }
}
