use crate::codegen::naming::to_go_name;
use super::optional_renderers::{
render_c, render_dart, render_kotlin_android, render_pascal_dot, render_php, render_r,
};
use super::types::{PathSegment, SwiftFirstClassMap};
use heck::{ToLowerCamelCase, ToSnakeCase};
use std::collections::HashSet;
pub(super) fn render_accessor(segments: &[PathSegment], language: &str, result_var: &str) -> String {
match language {
"rust" => render_rust(segments, result_var),
"python" => render_dot_access(segments, result_var, "python"),
"typescript" | "node" => render_typescript(segments, result_var),
"wasm" => render_wasm(segments, result_var),
"go" => render_go(segments, result_var),
"java" => render_java(segments, result_var),
"kotlin" => render_kotlin(segments, result_var),
"kotlin_android" => render_kotlin_android(segments, result_var),
"csharp" => render_pascal_dot(segments, result_var),
"ruby" => render_dot_access(segments, result_var, "ruby"),
"php" => render_php(segments, result_var),
"elixir" => render_dot_access(segments, result_var, "elixir"),
"r" => render_r(segments, result_var),
"c" => render_c(segments, result_var),
"swift" => render_swift(segments, result_var),
"dart" => render_dart(segments, result_var),
_ => render_dot_access(segments, result_var, language),
}
}
pub(super) fn render_swift(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_lower_camel_case());
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
}
}
PathSegment::Length => {
out.push_str(".count");
}
}
}
out
}
pub(super) fn render_swift_with_first_class_map(
segments: &[PathSegment],
result_var: &str,
optional_fields: &HashSet<String>,
map: &SwiftFirstClassMap,
) -> String {
let mut out = result_var.to_string();
let mut path_so_far = String::new();
let mut current_type: Option<String> = map.root_type.clone();
let mut via_rust_vec = false;
let mut via_opaque = false;
let total = segments.len();
for (i, seg) in segments.iter().enumerate() {
let is_leaf = i == total - 1;
let property_syntax = !via_rust_vec && !via_opaque && map.is_first_class(current_type.as_deref());
if !property_syntax {
via_opaque = true;
}
match seg {
PathSegment::Field(f) => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(f);
out.push('.');
out.push_str(&f.to_lower_camel_case());
if !property_syntax {
out.push_str("()");
}
if !is_leaf && optional_fields.contains(&path_so_far) {
out.push('?');
}
current_type = map.advance(current_type.as_deref(), f);
}
PathSegment::ArrayField { name, index } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(name);
let is_optional = optional_fields.contains(&path_so_far);
out.push('.');
out.push_str(&name.to_lower_camel_case());
let access = if property_syntax { "" } else { "()" };
if is_optional {
out.push_str(&format!("{access}?[{index}]"));
} else {
out.push_str(&format!("{access}[{index}]"));
}
path_so_far.push_str("[0]");
current_type = map.advance(current_type.as_deref(), name);
if !property_syntax {
via_rust_vec = true;
}
}
PathSegment::MapAccess { field, key } => {
if !path_so_far.is_empty() {
path_so_far.push('.');
}
path_so_far.push_str(field);
out.push('.');
out.push_str(&field.to_lower_camel_case());
let access = if property_syntax { "" } else { "()" };
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("{access}[{key}]"));
} else {
out.push_str(&format!("{access}[\"{key}\"]"));
}
current_type = map.advance(current_type.as_deref(), field);
}
PathSegment::Length => {
out.push_str(".count");
}
}
}
out
}
pub(super) fn render_rust(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_snake_case());
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_snake_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_snake_case());
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!(".get(\"{key}\").map(|s| s.as_str())"));
}
}
PathSegment::Length => {
out.push_str(".len()");
}
}
}
out
}
pub(super) fn render_dot_access(segments: &[PathSegment], result_var: &str, language: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(f);
}
PathSegment::ArrayField { name, index } => {
if language == "elixir" {
let current = std::mem::take(&mut out);
out = format!("Enum.at({current}.{name}, {index})");
} else {
out.push('.');
out.push_str(name);
out.push_str(&format!("[{index}]"));
}
}
PathSegment::MapAccess { field, key } => {
let is_numeric = key.chars().all(|c| c.is_ascii_digit());
if is_numeric && language == "elixir" {
let current = std::mem::take(&mut out);
out = format!("Enum.at({current}.{field}, {key})");
} else {
out.push('.');
out.push_str(field);
if is_numeric {
let idx: usize = key.parse().unwrap_or(0);
out.push_str(&format!("[{idx}]"));
} else if language == "elixir" || language == "ruby" {
out.push_str(&format!("[\"{key}\"]"));
} else {
out.push_str(&format!(".get(\"{key}\")"));
}
}
}
PathSegment::Length => match language {
"ruby" => out.push_str(".length"),
"elixir" => {
let current = std::mem::take(&mut out);
out = format!("length({current})");
}
"gleam" => {
let current = std::mem::take(&mut out);
out = format!("list.length({current})");
}
_ => {
let current = std::mem::take(&mut out);
out = format!("len({current})");
}
},
}
}
out
}
pub(super) fn render_typescript(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_lower_camel_case());
if !key.is_empty() && key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
}
}
PathSegment::Length => {
out.push_str(".length");
}
}
}
out
}
pub(super) fn render_wasm(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_lower_camel_case());
out.push_str(&format!(".get(\"{key}\")"));
}
PathSegment::Length => {
out.push_str(".length");
}
}
}
out
}
pub(super) fn render_go(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&to_go_name(f));
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&to_go_name(name));
out.push_str(&format!("[{index}]"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&to_go_name(field));
if key.chars().all(|c| c.is_ascii_digit()) {
out.push_str(&format!("[{key}]"));
} else {
out.push_str(&format!("[\"{key}\"]"));
}
}
PathSegment::Length => {
let current = std::mem::take(&mut out);
out = format!("len({current})");
}
}
}
out
}
pub(super) fn render_java(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&f.to_lower_camel_case());
out.push_str("()");
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&name.to_lower_camel_case());
out.push_str(&format!("().get({index})"));
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&field.to_lower_camel_case());
let is_numeric = !key.is_empty() && key.chars().all(|c| c.is_ascii_digit());
if is_numeric {
out.push_str(&format!("().get({key})"));
} else {
out.push_str(&format!("().get(\"{key}\")"));
}
}
PathSegment::Length => {
out.push_str(".size()");
}
}
}
out
}
pub(super) fn kotlin_getter(name: &str) -> String {
let camel = name.to_lower_camel_case();
match camel.as_str() {
"as" | "break" | "class" | "continue" | "do" | "else" | "false" | "for" | "fun" | "if" | "in" | "interface"
| "is" | "null" | "object" | "package" | "return" | "super" | "this" | "throw" | "true" | "try"
| "typealias" | "typeof" | "val" | "var" | "when" | "while" => format!("`{camel}`"),
_ => camel,
}
}
pub(super) fn render_kotlin(segments: &[PathSegment], result_var: &str) -> String {
let mut out = result_var.to_string();
for seg in segments {
match seg {
PathSegment::Field(f) => {
out.push('.');
out.push_str(&kotlin_getter(f));
out.push_str("()");
}
PathSegment::ArrayField { name, index } => {
out.push('.');
out.push_str(&kotlin_getter(name));
if *index == 0 {
out.push_str("().first()");
} else {
out.push_str(&format!("().get({index})"));
}
}
PathSegment::MapAccess { field, key } => {
out.push('.');
out.push_str(&kotlin_getter(field));
let is_numeric = !key.is_empty() && key.chars().all(|c| c.is_ascii_digit());
if is_numeric {
out.push_str(&format!("().get({key})"));
} else {
out.push_str(&format!("().get(\"{key}\")"));
}
}
PathSegment::Length => {
out.push_str(".size");
}
}
}
out
}