use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::path::PathBuf;

use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
use indexmap::IndexMap;
use prost_types::FieldDescriptorProto;

use crate::SupportFiles;
use crate::api_plan::{
    ApiPlan, PlannedEnum, PlannedField, PlannedFieldKind, PlannedFlags, PlannedMessageSource,
    PlannedMessageType, PlannedOperation, PlannedOperationOutput,
    PlannedOperationResourceFieldBinding, PlannedOperationResourceReturn, PlannedResource,
    PlannedResourceMethod, PlannedResourceMethodBindingSpec, PlannedResourceMethodResultKind,
    PlannedScalarType, PlannedSourcedField, PlannedTypeInfo, PlannedValueType, PlannedVariant,
    relative_descriptor_name,
};
use crate::error::Result;
use crate::generator::{GeneratedFiles, ModelCapabilities};
use crate::language::Language;
use crate::resources::{
    RequestPlan, RequestPlanSource, ResolvedResourceBindingSource, render_request_plan,
};
use crate::spec::{
    AuthoredFieldTypeSpec, FunctionArgsSpec, FunctionFieldSpec, FunctionResultSpec,
    LanguageImportSpec, LanguageImportStyle, TypeReplacementSpec,
};

const GENERATED_HEADER: &str = "// Generated by nex-gen. DO NOT EDIT!";
const TYPESCRIPT_FORMAT_LINE_LENGTH: usize = 88;
const EXPERIMENTAL_WARNING: &str = "This API is experimental and subject to change.";

pub(crate) fn generate(
    api_plan: &ApiPlan,
    support: &SupportFiles,
    language_imports: &[LanguageImportSpec],
) -> Result<GeneratedFiles> {
    let mut enums = IndexMap::new();
    let mut flags = IndexMap::new();
    let mut variants = IndexMap::new();
    let mut models = IndexMap::new();
    let services = api_plan
        .services
        .iter()
        .map(|service| {
            let operations = service
                .operations
                .iter()
                .map(|operation| {
                    resolve_operation(
                        operation,
                        api_plan,
                        &mut enums,
                        &mut flags,
                        &mut variants,
                        &mut models,
                    )
                })
                .collect::<Result<Vec<_>>>()?;

            Ok(RenderedService {
                name: service.name.as_str(),
                wire_name: service.wire_name.as_str(),
                endpoint: service.endpoint.clone(),
                experimental: service.experimental,
                operations,
                resources: service.resources.clone(),
            })
        })
        .collect::<Result<Vec<_>>>()?;

    for service in &services {
        for resource in &service.resources {
            ensure_resource_field_types(
                resource,
                api_plan,
                &mut enums,
                &mut flags,
                &mut variants,
                &mut models,
            );
        }
    }

    let requirements = collect_typescript_requirements(
        variants.values().collect::<Vec<_>>().as_slice(),
        models.values().collect::<Vec<_>>().as_slice(),
        &services,
    );

    Ok(render_module_files(
        enums.values().collect::<Vec<_>>().as_slice(),
        flags.values().collect::<Vec<_>>().as_slice(),
        variants.values().collect::<Vec<_>>().as_slice(),
        models.values().collect::<Vec<_>>().as_slice(),
        &services,
        &requirements,
        language_imports,
        support.typescript.as_deref(),
    ))
}

fn ensure_resource_field_types(
    resource: &PlannedResource,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) {
    for field in &resource.fields {
        ensure_resource_field_type(&field.kind, api_plan, enums, flags, variants, models);
    }
    for method in &resource.methods {
        for param in &method.params {
            ensure_resource_field_type(&param.kind, api_plan, enums, flags, variants, models);
        }
    }
}

fn ensure_resource_field_type(
    kind: &PlannedFieldKind,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) {
    match kind {
        PlannedFieldKind::Singular(value) | PlannedFieldKind::Repeated(value) => {
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models);
        }
        PlannedFieldKind::Map { key, value } => {
            resolve_planned_value_type(key, api_plan, enums, flags, variants, models);
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models);
        }
    }
}

fn generated_model_type_parameters(
    generated_model: &crate::spec::GeneratedModelSpec,
) -> Vec<RenderedTypeParameter> {
    if generated_model.is_empty() {
        return Vec::new();
    }

    let mut type_parameters = generated_model
        .functions
        .iter()
        .filter(|(field_name, _)| !generated_model.with_arguments.contains_key(*field_name))
        .map(|(field_name, function)| {
            let generated_name = generated_model
                .field_name_override(field_name)
                .unwrap_or(field_name);
            let type_parameter_name = function_type_parameter_name(generated_name);
            let constraint = function_constraint(function);
            (
                !function.primary,
                generated_name.to_string(),
                0,
                RenderedTypeParameter {
                    name: type_parameter_name,
                    constraint: constraint.clone(),
                    default: constraint,
                    infer_from_operation_request: true,
                },
            )
        })
        .collect::<Vec<_>>();

    for (field_name, with_arguments) in &generated_model.with_arguments {
        let generated_name = generated_model
            .field_name_override(field_name)
            .unwrap_or(field_name);
        let value_parameter_name = with_arguments_type_parameter_name(generated_name);
        let args_parameter_name = with_arguments_args_type_parameter_name(generated_name);
        let constraint = with_arguments.value_type.clone();
        type_parameters.push((
            true,
            generated_name.to_string(),
            0,
            RenderedTypeParameter {
                name: value_parameter_name.clone(),
                constraint: constraint.clone(),
                default: constraint,
                infer_from_operation_request: true,
            },
        ));
        type_parameters.push((
            true,
            generated_name.to_string(),
            1,
            RenderedTypeParameter {
                name: args_parameter_name,
                constraint: "any[]".to_string(),
                default: with_arguments_args_type_expression(
                    &with_arguments.args_type,
                    &value_parameter_name,
                ),
                infer_from_operation_request: false,
            },
        ));
    }

    type_parameters.sort_by(|left, right| {
        left.0
            .cmp(&right.0)
            .then_with(|| left.1.cmp(&right.1))
            .then_with(|| left.2.cmp(&right.2))
    });
    type_parameters
        .into_iter()
        .map(|(_, _, _, type_parameter)| type_parameter)
        .collect()
}

fn typescript_replacement_type_name(replacement: &TypeReplacementSpec) -> Option<String> {
    replacement
        .type_name
        .for_language(Language::TypeScript)
        .map(str::to_string)
}

fn typescript_function_result_annotation(result: &FunctionResultSpec) -> String {
    match result {
        FunctionResultSpec::Annotation(annotation) => annotation
            .for_language(Language::TypeScript)
            .unwrap_or("unknown")
            .to_string(),
        FunctionResultSpec::Wit(wit_type) => typescript_authored_type_annotation(wit_type),
    }
}

fn typescript_authored_type_annotation(wit_type: &AuthoredFieldTypeSpec) -> String {
    match wit_type {
        AuthoredFieldTypeSpec::Bool => "boolean".to_string(),
        AuthoredFieldTypeSpec::Int | AuthoredFieldTypeSpec::Float => "number".to_string(),
        AuthoredFieldTypeSpec::String => "string".to_string(),
        AuthoredFieldTypeSpec::Bytes => "Uint8Array".to_string(),
        AuthoredFieldTypeSpec::Option(inner) => {
            format!("{} | undefined", typescript_authored_type_annotation(inner))
        }
        AuthoredFieldTypeSpec::List(inner) => {
            format!("{}[]", typescript_authored_type_annotation(inner))
        }
        AuthoredFieldTypeSpec::Tuple(items) => format!(
            "[{}]",
            items
                .iter()
                .map(typescript_authored_type_annotation)
                .collect::<Vec<_>>()
                .join(", ")
        ),
        AuthoredFieldTypeSpec::Map(key, value) => format!(
            "Record<{}, {}>",
            typescript_authored_type_annotation(key),
            typescript_authored_type_annotation(value)
        ),
        AuthoredFieldTypeSpec::Result { ok, err } => typescript_result_annotation(
            ok.as_ref()
                .map(|ok| typescript_authored_type_annotation(ok).to_string()),
            err.as_ref()
                .map(|err| typescript_authored_type_annotation(err).to_string()),
        ),
        AuthoredFieldTypeSpec::Alias {
            type_name, target, ..
        } => type_name
            .for_language(Language::TypeScript)
            .map(str::to_string)
            .unwrap_or_else(|| typescript_authored_type_annotation(target)),
        AuthoredFieldTypeSpec::Proto(proto_name) => proto_name
            .rsplit('.')
            .next()
            .unwrap_or(proto_name)
            .to_string(),
        AuthoredFieldTypeSpec::Record(name)
        | AuthoredFieldTypeSpec::Enum(name)
        | AuthoredFieldTypeSpec::Flags(name)
        | AuthoredFieldTypeSpec::Variant(name)
        | AuthoredFieldTypeSpec::Resource(name) => name
            .rsplit('.')
            .next()
            .unwrap_or(name)
            .to_upper_camel_case(),
    }
}

fn typescript_result_annotation(ok: Option<String>, err: Option<String>) -> String {
    let ok_case = if let Some(ok) = ok {
        format!("{{ tag: \"ok\"; value: {ok} }}")
    } else {
        "{ tag: \"ok\" }".to_string()
    };
    let err_case = if let Some(err) = err {
        format!("{{ tag: \"err\"; value: {err} }}")
    } else {
        "{ tag: \"err\" }".to_string()
    };
    format!("{ok_case} | {err_case}")
}

fn function_type_parameter_name(field_name: &str) -> String {
    format!(
        "{}Fn",
        function_type_parameter_stem(field_name).to_upper_camel_case()
    )
}

fn with_arguments_type_parameter_name(field_name: &str) -> String {
    format!(
        "{}Value",
        function_type_parameter_stem(field_name).to_upper_camel_case()
    )
}

fn with_arguments_args_type_parameter_name(field_name: &str) -> String {
    format!(
        "{}Args",
        function_type_parameter_stem(field_name).to_upper_camel_case()
    )
}

fn function_type_parameter_stem(field_name: &str) -> &str {
    field_name
        .strip_suffix("Type")
        .or_else(|| field_name.strip_suffix("Name"))
        .unwrap_or(field_name)
}

fn function_constraint(function: &FunctionFieldSpec) -> String {
    typescript_function_constraint(
        &typescript_function_args(&function.args),
        &typescript_function_result_annotation(&function.result),
    )
}

fn typescript_function_constraint(args: &RenderedFunctionArgs, result_annotation: &str) -> String {
    match args {
        RenderedFunctionArgs::Varargs {
            prefix,
            typescript_drop_prefix,
        } => {
            if *typescript_drop_prefix {
                return format!("(...args: any[]) => {result_annotation}");
            }
            let mut parameters = prefix
                .iter()
                .map(|parameter| format!("{}: {}", parameter.name, parameter.annotation))
                .collect::<Vec<_>>();
            parameters.push("...args: any[]".to_string());
            format!("({}) => {result_annotation}", parameters.join(", "))
        }
        RenderedFunctionArgs::Typed { parameters } => format!(
            "({}) => {result_annotation}",
            parameters
                .iter()
                .map(|parameter| format!("{}: {}", parameter.name, parameter.annotation))
                .collect::<Vec<_>>()
                .join(", ")
        ),
    }
}

fn typescript_function_args(args: &FunctionArgsSpec) -> RenderedFunctionArgs {
    match args {
        FunctionArgsSpec::Varargs {
            prefix,
            typescript_drop_prefix,
        } => RenderedFunctionArgs::Varargs {
            prefix: prefix
                .iter()
                .map(|arg| RenderedFunctionArg {
                    name: typescript_ident(&arg.name.to_lower_camel_case()),
                    annotation: typescript_authored_type_annotation(&arg.field_type),
                })
                .collect(),
            typescript_drop_prefix: *typescript_drop_prefix,
        },
        FunctionArgsSpec::Fixed(args) => RenderedFunctionArgs::Typed {
            parameters: args
                .iter()
                .map(|arg| RenderedFunctionArg {
                    name: typescript_ident(&arg.name.to_lower_camel_case()),
                    annotation: typescript_authored_type_annotation(&arg.field_type),
                })
                .collect(),
        },
    }
}

fn invocation_model_replace_type_name(model_name: &str) -> String {
    format!("Replace{model_name}")
}

fn model_to_proto_function_name(model_name: &str) -> String {
    format!("{}ToProto", model_name.to_lower_camel_case())
}

fn model_from_proto_function_name(model_name: &str) -> String {
    format!("{}FromProto", model_name.to_lower_camel_case())
}

fn generic_model_annotation(model_name: &str, type_parameters: &[RenderedTypeParameter]) -> String {
    if type_parameters.is_empty() {
        model_name.to_string()
    } else {
        format!(
            "{}<{}>",
            model_name,
            type_parameters
                .iter()
                .map(|type_parameter| type_parameter.name.as_str())
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

#[derive(Debug)]
struct RenderedService<'a> {
    name: &'a str,
    wire_name: &'a str,
    endpoint: String,
    experimental: bool,
    operations: Vec<RenderedOperation<'a>>,
    resources: Vec<PlannedResource>,
}

#[derive(Debug)]
struct RenderedOperation<'a> {
    name: &'a str,
    wire_name: &'a str,
    attr_name: String,
    experimental: bool,
    doc: Option<String>,
    return_doc: Option<String>,
    input_proto_ref: String,
    output_proto_ref: String,
    input_type_parameters: Vec<RenderedTypeParameter>,
    input_annotation: String,
    input_to_proto_expr: String,
    input_nexus_type_id: Option<String>,
    output_annotation: String,
    output_transform_expr: Option<String>,
    output_resource_return: Option<PlannedOperationResourceReturn>,
    output_direct_result: bool,
}

#[derive(Debug)]
struct RenderedEnum {
    name: String,
    values: Vec<RenderedEnumValue>,
}

#[derive(Debug)]
struct RenderedEnumValue {
    name: String,
    number: i32,
}

#[derive(Debug)]
struct RenderedFlags {
    name: String,
    flags: Vec<RenderedFlag>,
}

#[derive(Debug)]
struct RenderedFlag {
    name: String,
    bit: usize,
}

#[derive(Debug)]
struct RenderedVariant {
    name: String,
    cases: Vec<RenderedVariantCase>,
}

#[derive(Debug)]
struct RenderedVariantCase {
    name: String,
    payload_annotation: Option<String>,
    requirements: TypeScriptRequirements,
}

#[derive(Debug)]
struct RenderedModel {
    name: String,
    proto_ref: String,
    capabilities: ModelCapabilities,
    experimental: bool,
    type_parameters: Vec<RenderedTypeParameter>,
    fields: Vec<RenderedField>,
    sourced_fields: Vec<RenderedSourcedField>,
    functions: Vec<RenderedFunctionField>,
    with_arguments: Vec<RenderedWithArgumentsField>,
}

#[derive(Debug)]
struct RenderedField {
    name: String,
    proto_name: String,
    annotation: String,
    doc: Option<String>,
    optional: bool,
    from_proto_expr: String,
    to_proto_expr: String,
    flattened_fields: Vec<RenderedFlattenedField>,
    requirements: TypeScriptRequirements,
}

#[derive(Debug)]
struct RenderedFlattenedField {
    name: String,
    proto_name: String,
    annotation: String,
    doc: Option<String>,
    optional: bool,
    from_proto_expr: String,
    to_proto_expr: String,
}

#[derive(Debug)]
struct RenderedSourcedField {
    name: String,
    to_proto_expr: String,
}

#[derive(Debug, Clone)]
struct RenderedTypeParameter {
    name: String,
    constraint: String,
    default: String,
    infer_from_operation_request: bool,
}

#[derive(Debug, Clone)]
struct RenderedFunctionField {
    callable_field_name: String,
    args_field_name: String,
    primary: bool,
    args: RenderedFunctionArgs,
    type_parameter_name: String,
    alternate_annotation: Option<String>,
}

#[derive(Debug, Clone)]
enum RenderedFunctionArgs {
    Varargs {
        prefix: Vec<RenderedFunctionArg>,
        typescript_drop_prefix: bool,
    },
    Typed {
        parameters: Vec<RenderedFunctionArg>,
    },
}

#[derive(Debug, Clone)]
struct RenderedFunctionArg {
    name: String,
    annotation: String,
}

#[derive(Debug, Clone)]
struct RenderedWithArgumentsField {
    value_field_name: String,
    args_field_name: String,
    type_parameter_name: String,
    args_type_parameter_name: String,
    alternate_annotation: Option<String>,
}

#[derive(Debug, Clone, Default)]
struct TypeScriptRequirements {
    long: bool,
}

impl TypeScriptRequirements {
    fn merge(&mut self, other: &TypeScriptRequirements) {
        self.long |= other.long;
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ResolvedFieldKind {
    Scalar,
    Message,
    Enum,
    Unknown,
}

#[derive(Debug, Clone)]
struct ResolvedFieldType {
    annotation: String,
    kind: ResolvedFieldKind,
    requirements: TypeScriptRequirements,
    message_conversion: Option<MessageValueConversion>,
    enum_conversion: Option<EnumValueConversion>,
}

#[derive(Debug, Clone)]
enum MessageValueConversionKind {
    GeneratedModel {
        model_name: String,
    },
    NativeModel,
    Override {
        from_proto: String,
        to_proto: String,
    },
}

#[derive(Debug, Clone)]
struct MessageValueConversion {
    annotation: String,
    kind: MessageValueConversionKind,
}

impl MessageValueConversion {
    fn from_proto_expr(&self, proto_expr: &str) -> String {
        match &self.kind {
            MessageValueConversionKind::GeneratedModel { model_name } => {
                format!(
                    "{}({proto_expr})",
                    model_from_proto_function_name(model_name)
                )
            }
            MessageValueConversionKind::NativeModel => proto_expr.to_string(),
            MessageValueConversionKind::Override { from_proto, .. } => {
                format!("{from_proto}({proto_expr})")
            }
        }
    }

    fn to_proto_expr(&self, value_expr: &str) -> String {
        match &self.kind {
            MessageValueConversionKind::GeneratedModel { model_name } => {
                format!(
                    "{}({value_expr}) ?? {{}}",
                    model_to_proto_function_name(model_name)
                )
            }
            MessageValueConversionKind::NativeModel => value_expr.to_string(),
            MessageValueConversionKind::Override { to_proto, .. } => {
                format!("{to_proto}({value_expr})")
            }
        }
    }
}

#[derive(Debug, Clone)]
struct EnumValueConversion {
    from_proto: String,
    to_proto: String,
}

impl EnumValueConversion {
    fn from_proto_expr(&self, proto_expr: &str) -> String {
        format!("{}({proto_expr})", self.from_proto)
    }

    fn to_proto_expr(&self, value_expr: &str) -> String {
        format!("{}({value_expr})", self.to_proto)
    }
}

fn typescript_default_from_proto_name(name: &str) -> String {
    format!(
        "{}FromProto",
        name.rsplit('.')
            .next()
            .expect("converter name source should have a final segment")
            .to_lower_camel_case()
    )
}

fn typescript_default_to_proto_name(name: &str) -> String {
    format!(
        "{}ToProto",
        name.rsplit('.')
            .next()
            .expect("converter name source should have a final segment")
            .to_lower_camel_case()
    )
}

fn typescript_from_proto_converter(name: &str, replacement: &TypeReplacementSpec) -> String {
    replacement
        .from_proto
        .for_language(Language::TypeScript)
        .map(str::to_string)
        .unwrap_or_else(|| typescript_default_from_proto_name(name))
}

fn typescript_to_proto_converter(name: &str, replacement: &TypeReplacementSpec) -> String {
    replacement
        .to_proto
        .for_language(Language::TypeScript)
        .map(str::to_string)
        .unwrap_or_else(|| typescript_default_to_proto_name(name))
}

fn resolve_operation<'a>(
    operation: &'a PlannedOperation,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> Result<RenderedOperation<'a>> {
    let input_conversion = resolve_message_value_conversion(
        &operation.input,
        api_plan,
        enums,
        flags,
        variants,
        models,
    );
    let output_transform = operation.output_transform.as_ref();
    let output_resource_return = operation.output_resource_return.clone();
    let (output_proto_ref, output_annotation_default) = match &operation.output {
        PlannedOperationOutput::Message(output) => {
            if output_transform.is_none()
                && output_resource_return.is_none()
                && !operation.output_direct_result
            {
                let _output_conversion = resolve_message_value_conversion(
                    output, api_plan, enums, flags, variants, models,
                );
            }
            (
                operation_type_ref(output),
                resolve_output_annotation(output, api_plan, enums, flags, variants, models),
            )
        }
        PlannedOperationOutput::Resource { type_name } => (type_name.clone(), type_name.clone()),
        PlannedOperationOutput::None => ("void".to_string(), "void".to_string()),
    };
    let input_type_parameters = models
        .get(&operation.input.info.full_name)
        .map(|model| {
            model
                .type_parameters
                .iter()
                .filter(|type_parameter| type_parameter.infer_from_operation_request)
                .cloned()
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    let input_annotation = if matches!(
        input_conversion.kind,
        MessageValueConversionKind::GeneratedModel { .. } | MessageValueConversionKind::NativeModel
    ) {
        generic_model_annotation(&input_conversion.annotation, &input_type_parameters)
    } else {
        input_conversion.annotation.clone()
    };

    Ok(RenderedOperation {
        name: operation.name.as_str(),
        wire_name: operation.wire_name.as_str(),
        attr_name: typescript_ident(&operation.name.to_lower_camel_case()),
        experimental: operation.experimental,
        doc: operation
            .doc
            .for_language(Language::TypeScript)
            .map(str::to_string),
        return_doc: operation
            .return_doc
            .for_language(Language::TypeScript)
            .map(str::to_string),
        input_proto_ref: operation_type_ref(&operation.input),
        output_proto_ref,
        input_type_parameters,
        input_annotation,
        input_to_proto_expr: input_conversion.to_proto_expr("request"),
        input_nexus_type_id: if operation.input.source == PlannedMessageSource::Wit {
            Some(operation.input.info.full_name.clone())
        } else {
            None
        },
        output_annotation: output_transform
            .and_then(|transform| {
                transform
                    .type_name
                    .for_language(Language::TypeScript)
                    .map(str::to_string)
            })
            .or_else(|| {
                output_resource_return
                    .as_ref()
                    .map(|resource| resource.resource_type_name.clone())
            })
            .unwrap_or(output_annotation_default),
        output_transform_expr: output_transform.and_then(|transform| {
            transform
                .transform
                .for_language(Language::TypeScript)
                .map(str::to_string)
        }),
        output_resource_return,
        output_direct_result: operation.output_direct_result,
    })
}

fn operation_type_ref(message: &PlannedMessageType) -> String {
    match message.source {
        PlannedMessageSource::Proto => message_typescript_interface_ref(&message.info),
        PlannedMessageSource::Wit => message.model_name.clone(),
    }
}

fn resolve_output_annotation(
    message: &PlannedMessageType,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> String {
    match message.source {
        PlannedMessageSource::Proto => message_typescript_interface_ref(&message.info),
        PlannedMessageSource::Wit => {
            let conversion =
                resolve_message_value_conversion(message, api_plan, enums, flags, variants, models);
            conversion.annotation
        }
    }
}

fn resolve_message_value_conversion(
    message: &PlannedMessageType,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> MessageValueConversion {
    if let Some(language_override) = &message.replacement
        && let Some(type_name) = typescript_replacement_type_name(language_override)
    {
        return MessageValueConversion {
            annotation: type_name,
            kind: MessageValueConversionKind::Override {
                from_proto: typescript_from_proto_converter(
                    &message.info.full_name,
                    language_override,
                ),
                to_proto: typescript_to_proto_converter(&message.info.full_name, language_override),
            },
        };
    }
    if let Some(authored_type) = &message.authored_type {
        return MessageValueConversion {
            annotation: typescript_authored_type_annotation(authored_type),
            kind: MessageValueConversionKind::Override {
                from_proto: typescript_default_from_proto_name(&message.info.full_name),
                to_proto: typescript_default_to_proto_name(&message.info.full_name),
            },
        };
    }

    ensure_rendered_model(message, api_plan, enums, flags, variants, models);
    let rendered_model = models
        .get(&message.info.full_name)
        .expect("planned model should be rendered");
    let kind = if message.source == PlannedMessageSource::Wit {
        MessageValueConversionKind::NativeModel
    } else {
        MessageValueConversionKind::GeneratedModel {
            model_name: message.model_name.clone(),
        }
    };
    MessageValueConversion {
        annotation: rendered_model.name.clone(),
        kind,
    }
}

fn ensure_rendered_model(
    message: &PlannedMessageType,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) {
    let planned_model = api_plan
        .models
        .get(&message.info.full_name)
        .unwrap_or_else(|| panic!("planned model should exist for {}", message.info.full_name));

    if models.contains_key(&message.info.full_name) {
        return;
    }

    models.insert(
        message.info.full_name.clone(),
        RenderedModel {
            name: planned_model.name.clone(),
            proto_ref: if message.source == PlannedMessageSource::Proto {
                message_typescript_interface_ref(&planned_model.info)
            } else {
                planned_model.name.clone()
            },
            capabilities: planned_model.capabilities,
            experimental: planned_model.experimental,
            type_parameters: generated_model_type_parameters(&planned_model.generated_model),
            fields: Vec::new(),
            sourced_fields: Vec::new(),
            functions: Vec::new(),
            with_arguments: Vec::new(),
        },
    );

    let fields = planned_model
        .fields
        .iter()
        .map(|field| build_field(field, api_plan, enums, flags, variants, models))
        .collect();

    let sourced_fields = planned_model
        .sourced_fields
        .iter()
        .map(|field| build_sourced_field(field, api_plan, enums, flags, variants, models))
        .collect();

    models
        .get_mut(&message.info.full_name)
        .expect("model should be inserted before recursive field resolution")
        .fields = fields;
    models
        .get_mut(&message.info.full_name)
        .expect("model should be inserted before recursive field resolution")
        .sourced_fields = sourced_fields;
    models
        .get_mut(&message.info.full_name)
        .expect("model should be inserted before recursive field resolution")
        .functions = Some(&planned_model.generated_model)
        .map(|generated_model| {
            generated_model
                .functions
                .iter()
                .filter(|(field_name, _)| !generated_model.with_arguments.contains_key(*field_name))
                .map(|(field_name, function)| RenderedFunctionField {
                    callable_field_name: generated_model
                        .field_name_override(field_name)
                        .map(typescript_generated_field_name)
                        .unwrap_or_else(|| typescript_ident(&field_name.to_lower_camel_case())),
                    args_field_name: generated_model
                        .field_name_override(&function.args_field)
                        .map(typescript_generated_field_name)
                        .unwrap_or_else(|| {
                            typescript_ident(&function.args_field.to_lower_camel_case())
                        }),
                    primary: function.primary,
                    args: typescript_function_args(&function.args),
                    type_parameter_name: function_type_parameter_name(
                        generated_model
                            .field_name_override(field_name)
                            .unwrap_or(field_name),
                    ),
                    alternate_annotation: function
                        .alternate_type
                        .as_ref()
                        .map(typescript_authored_type_annotation),
                })
                .collect()
        })
        .unwrap_or_default();
    models
        .get_mut(&message.info.full_name)
        .expect("model should be inserted before recursive field resolution")
        .with_arguments = Some(&planned_model.generated_model)
        .map(|generated_model| {
            generated_model
                .with_arguments
                .iter()
                .map(|(field_name, with_arguments)| RenderedWithArgumentsField {
                    value_field_name: generated_model
                        .field_name_override(field_name)
                        .map(typescript_generated_field_name)
                        .unwrap_or_else(|| typescript_ident(&field_name.to_lower_camel_case())),
                    args_field_name: generated_model
                        .field_name_override(&with_arguments.args_field)
                        .map(typescript_generated_field_name)
                        .unwrap_or_else(|| {
                            typescript_ident(&with_arguments.args_field.to_lower_camel_case())
                        }),
                    type_parameter_name: {
                        let generated_field_name = generated_model
                            .field_name_override(field_name)
                            .unwrap_or(field_name);
                        with_arguments_type_parameter_name(generated_field_name)
                    },
                    args_type_parameter_name: {
                        let generated_field_name = generated_model
                            .field_name_override(field_name)
                            .unwrap_or(field_name);
                        with_arguments_args_type_parameter_name(generated_field_name)
                    },
                    alternate_annotation: generated_model
                        .with_arguments
                        .get(field_name)
                        .and_then(|with_arguments| with_arguments.alternate_type.as_ref())
                        .map(typescript_authored_type_annotation),
                })
                .collect()
        })
        .unwrap_or_default();
}

fn ensure_rendered_enum(planned_enum: &PlannedEnum, enums: &mut IndexMap<String, RenderedEnum>) {
    enums
        .entry(planned_enum.info.full_name.clone())
        .or_insert_with(|| RenderedEnum {
            name: planned_enum.name.clone(),
            values: planned_enum
                .values
                .iter()
                .map(|value| RenderedEnumValue {
                    name: value.name.clone(),
                    number: value.number,
                })
                .collect(),
        });
}

fn ensure_rendered_flags(
    planned_flags: &PlannedFlags,
    flags: &mut IndexMap<String, RenderedFlags>,
) {
    flags
        .entry(planned_flags.info.full_name.clone())
        .or_insert_with(|| RenderedFlags {
            name: planned_flags.name.clone(),
            flags: planned_flags
                .flags
                .iter()
                .map(|flag| RenderedFlag {
                    name: flag.name.clone(),
                    bit: flag.bit,
                })
                .collect(),
        });
}

fn ensure_rendered_variant(
    planned_variant: &PlannedVariant,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) {
    if variants.contains_key(&planned_variant.info.full_name) {
        return;
    }

    let cases = planned_variant
        .cases
        .iter()
        .map(|case| {
            let payload = case.payload.as_ref().map(|payload| {
                resolve_planned_value_type(payload, api_plan, enums, flags, variants, models)
            });
            RenderedVariantCase {
                name: case.name.clone(),
                payload_annotation: payload.as_ref().map(|payload| payload.annotation.clone()),
                requirements: payload
                    .as_ref()
                    .map(|payload| payload.requirements.clone())
                    .unwrap_or_default(),
            }
        })
        .collect();
    variants.insert(
        planned_variant.info.full_name.clone(),
        RenderedVariant {
            name: planned_variant.name.clone(),
            cases,
        },
    );
}

fn build_field(
    field: &PlannedField,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> RenderedField {
    let generated_field_name = typescript_generated_field_name(&field.authored_name);
    let proto_field_name = typescript_generated_field_name(&field.proto_name);
    let doc = field
        .doc
        .as_ref()
        .and_then(|doc| doc.for_language(Language::TypeScript))
        .map(str::to_string);

    if let PlannedFieldKind::Map { value, .. } = &field.kind {
        let value_type =
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models);
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: typescript_field_annotation(
                field,
                format!("Record<string, {}>", value_type.annotation),
            ),
            doc,
            optional: true,
            from_proto_expr: map_value_from_proto_expr(&value_type, &proto_field_name),
            to_proto_expr: map_value_to_proto_expr(&value_type, &generated_field_name),
            flattened_fields: Vec::new(),
            requirements: value_type.requirements,
        };
    }

    let (resolved_type, repeated) = match &field.kind {
        PlannedFieldKind::Singular(value) => (
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models),
            false,
        ),
        PlannedFieldKind::Repeated(value) => (
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models),
            true,
        ),
        PlannedFieldKind::Map { .. } => unreachable!("handled above"),
    };
    let owner_name = field.owner_name.clone();

    if !repeated
        && field.function.is_none()
        && !field.function_args
        && field.with_arguments.is_none()
        && !field.with_arguments_args
        && let Some(flattened) = build_flattened_message_field(
            field,
            &generated_field_name,
            &proto_field_name,
            api_plan,
            enums,
            flags,
            variants,
            models,
        )
    {
        return flattened;
    }

    if let Some(with_arguments) = &field.with_arguments {
        let function_converter = field
            .function
            .as_ref()
            .and_then(|function| function.converter.as_deref());
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: typescript_field_annotation(field, resolved_type.annotation.clone()),
            doc,
            optional: !field.required,
            from_proto_expr: if field.required {
                required_from_proto_expr(
                    &resolved_type,
                    &owner_name,
                    &proto_field_name,
                    &generated_field_name,
                    field,
                )
            } else {
                optional_from_proto_expr(&resolved_type, &proto_field_name)
            },
            to_proto_expr: match (field.required, function_converter) {
                (true, Some(converter)) => {
                    required_function_to_proto_expr(&owner_name, &generated_field_name, converter)
                }
                (false, Some(converter)) => {
                    optional_function_to_proto_expr(&generated_field_name, converter)
                }
                (true, None) => required_with_arguments_to_proto_expr(
                    &resolved_type,
                    &owner_name,
                    &generated_field_name,
                    &with_arguments.name_expr,
                ),
                (false, None) => optional_with_arguments_to_proto_expr(
                    &resolved_type,
                    &generated_field_name,
                    &with_arguments.name_expr,
                ),
            },
            requirements: resolved_type.requirements,
            flattened_fields: Vec::new(),
        };
    }

    if let Some(function_field) = &field.function
        && let Some(converter) = function_field.converter.as_deref()
    {
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: typescript_field_annotation(field, resolved_type.annotation.clone()),
            doc,
            optional: !field.required,
            from_proto_expr: if field.required {
                required_from_proto_expr(
                    &resolved_type,
                    &owner_name,
                    &proto_field_name,
                    &generated_field_name,
                    field,
                )
            } else {
                optional_from_proto_expr(&resolved_type, &proto_field_name)
            },
            to_proto_expr: if field.required {
                required_function_to_proto_expr(&owner_name, &generated_field_name, converter)
            } else {
                optional_function_to_proto_expr(&generated_field_name, converter)
            },
            requirements: resolved_type.requirements,
            flattened_fields: Vec::new(),
        };
    }

    if field.function_args || field.with_arguments_args {
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: resolved_type.annotation.clone(),
            doc,
            optional: !field.required,
            from_proto_expr: if field.required {
                required_from_proto_expr(
                    &resolved_type,
                    &owner_name,
                    &proto_field_name,
                    &generated_field_name,
                    field,
                )
            } else {
                optional_from_proto_expr(&resolved_type, &proto_field_name)
            },
            to_proto_expr: if field.required {
                required_to_proto_expr(&resolved_type, &owner_name, &generated_field_name)
            } else {
                optional_function_args_to_proto_expr(&resolved_type, &generated_field_name)
            },
            flattened_fields: Vec::new(),
            requirements: resolved_type.requirements,
        };
    }

    if repeated {
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: typescript_field_annotation(
                field,
                format!("{}[]", resolved_type.annotation),
            ),
            doc,
            optional: true,
            from_proto_expr: repeated_from_proto_expr(&resolved_type, &proto_field_name),
            to_proto_expr: repeated_to_proto_expr(&resolved_type, &generated_field_name),
            flattened_fields: Vec::new(),
            requirements: resolved_type.requirements,
        };
    }

    if let Some(default_value) = &field.default_value {
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: typescript_field_annotation(field, resolved_type.annotation.clone()),
            doc,
            optional: true,
            from_proto_expr: defaulted_enum_from_proto_expr(
                &resolved_type,
                &proto_field_name,
                &default_value.enum_case,
            ),
            to_proto_expr: defaulted_enum_to_proto_expr(
                &resolved_type,
                &generated_field_name,
                &default_value.enum_case,
            ),
            flattened_fields: Vec::new(),
            requirements: resolved_type.requirements,
        };
    }

    if field.required {
        return RenderedField {
            name: generated_field_name.clone(),
            proto_name: proto_field_name.clone(),
            annotation: typescript_field_annotation(field, resolved_type.annotation.clone()),
            doc,
            optional: false,
            from_proto_expr: required_from_proto_expr(
                &resolved_type,
                &owner_name,
                &proto_field_name,
                &generated_field_name,
                field,
            ),
            to_proto_expr: required_to_proto_expr(
                &resolved_type,
                &owner_name,
                &generated_field_name,
            ),
            flattened_fields: Vec::new(),
            requirements: resolved_type.requirements,
        };
    }

    RenderedField {
        name: generated_field_name.clone(),
        proto_name: proto_field_name.clone(),
        annotation: typescript_field_annotation(field, resolved_type.annotation.clone()),
        doc,
        optional: true,
        from_proto_expr: optional_from_proto_expr(&resolved_type, &proto_field_name),
        to_proto_expr: optional_to_proto_expr(&resolved_type, &generated_field_name),
        flattened_fields: Vec::new(),
        requirements: resolved_type.requirements,
    }
}

fn build_flattened_message_field(
    field: &PlannedField,
    generated_field_name: &str,
    proto_field_name: &str,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> Option<RenderedField> {
    let PlannedFieldKind::Singular(PlannedValueType::Message(message_type)) = &field.kind else {
        return None;
    };
    let nested_planned_model = api_plan.models.get(&message_type.info.full_name)?;
    if !nested_planned_model.flatten_in_api {
        return None;
    }

    let nested_fields = nested_planned_model.fields.clone();
    let nested_generated_model = nested_planned_model.generated_model.clone();
    let nested_model_name = nested_planned_model.name.clone();
    let mut flattened_fields = Vec::new();
    let mut requirements = TypeScriptRequirements::default();

    for nested_planned_field in nested_fields {
        let nested_rendered_field = build_field(
            &nested_planned_field,
            api_plan,
            enums,
            flags,
            variants,
            models,
        );
        let annotation = nested_generated_model
            .field_flattened_annotation(&nested_planned_field.proto_name)
            .and_then(|annotation| annotation.for_language(Language::TypeScript))
            .map(str::to_string)
            .unwrap_or_else(|| nested_rendered_field.annotation.clone());
        let to_proto_expr = flattened_field_to_proto_expr(&nested_rendered_field, &annotation);
        requirements.merge(&nested_rendered_field.requirements);
        flattened_fields.push(RenderedFlattenedField {
            name: nested_rendered_field.name,
            proto_name: nested_rendered_field.proto_name,
            annotation,
            doc: nested_rendered_field.doc,
            optional: nested_rendered_field.optional,
            from_proto_expr: format!(
                "proto.{proto_field_name} == null ? undefined : {}",
                nested_rendered_field
                    .from_proto_expr
                    .replace("proto.", &format!("proto.{proto_field_name}."))
            ),
            to_proto_expr,
        });
    }

    let doc = field
        .doc
        .as_ref()
        .and_then(|doc| doc.for_language(Language::TypeScript))
        .map(str::to_string);
    Some(RenderedField {
        name: generated_field_name.to_string(),
        proto_name: proto_field_name.to_string(),
        annotation: nested_model_name,
        doc,
        optional: !field.required,
        from_proto_expr: String::new(),
        to_proto_expr: flattened_message_to_proto_expr(field.required, &flattened_fields),
        flattened_fields,
        requirements,
    })
}

fn flattened_field_to_proto_expr(field: &RenderedField, annotation: &str) -> String {
    if field.annotation == "common.Payload" && annotation != field.annotation {
        let value_expr = format!("model.{}", field.name);
        if field.optional {
            return format!(
                "{value_expr} == null ? undefined : configuredPayloadConverter().toPayload({value_expr})"
            );
        }
        return format!("configuredPayloadConverter().toPayload({value_expr})");
    }
    field.to_proto_expr.clone()
}

fn flattened_message_to_proto_expr(required: bool, fields: &[RenderedFlattenedField]) -> String {
    let proto_fields = fields
        .iter()
        .map(|field| format!("{}: {}", field.proto_name, field.to_proto_expr))
        .collect::<Vec<_>>()
        .join(", ");
    if required || fields.iter().any(|field| !field.optional) {
        return format!("{{ {proto_fields} }}");
    }
    let all_empty = fields
        .iter()
        .map(|field| format!("model.{} == null", field.name))
        .collect::<Vec<_>>()
        .join(" && ");
    format!("{all_empty} ? undefined : {{ {proto_fields} }}")
}

fn build_sourced_field(
    field: &PlannedSourcedField,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> RenderedSourcedField {
    let field_name = typescript_generated_field_name(&field.proto_name);

    if let PlannedFieldKind::Map { value, .. } = &field.kind {
        let value_type =
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models);
        return RenderedSourcedField {
            name: field_name.clone(),
            to_proto_expr: map_value_to_proto_expr_from_expr(&value_type, &field.source_expr),
        };
    }

    let (resolved_type, repeated) = match &field.kind {
        PlannedFieldKind::Singular(value) => (
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models),
            false,
        ),
        PlannedFieldKind::Repeated(value) => (
            resolve_planned_value_type(value, api_plan, enums, flags, variants, models),
            true,
        ),
        PlannedFieldKind::Map { .. } => unreachable!("handled above"),
    };

    if repeated {
        return RenderedSourcedField {
            name: field_name.clone(),
            to_proto_expr: repeated_to_proto_expr_from_expr(&resolved_type, &field.source_expr),
        };
    }

    RenderedSourcedField {
        name: field_name,
        to_proto_expr: optional_to_proto_expr_from_expr(&resolved_type, &field.source_expr),
    }
}

fn typescript_field_annotation(field: &PlannedField, default_annotation: String) -> String {
    let annotation = field
        .annotation_override
        .as_ref()
        .and_then(|annotation| annotation.for_language(Language::TypeScript))
        .map(str::to_string)
        .unwrap_or(default_annotation);
    if !field.required && annotation.ends_with(" | undefined") {
        annotation
            .strip_suffix(" | undefined")
            .expect("suffix check should guarantee success")
            .to_string()
    } else {
        annotation
    }
}

fn resolve_planned_value_type(
    value_type: &PlannedValueType,
    api_plan: &ApiPlan,
    enums: &mut IndexMap<String, RenderedEnum>,
    flags: &mut IndexMap<String, RenderedFlags>,
    variants: &mut IndexMap<String, RenderedVariant>,
    models: &mut IndexMap<String, RenderedModel>,
) -> ResolvedFieldType {
    match value_type {
        PlannedValueType::Scalar(PlannedScalarType::Float | PlannedScalarType::Int32) => {
            ResolvedFieldType {
                annotation: "number".to_string(),
                kind: ResolvedFieldKind::Scalar,
                requirements: TypeScriptRequirements::default(),
                message_conversion: None,
                enum_conversion: None,
            }
        }
        PlannedValueType::Scalar(PlannedScalarType::Int64) => ResolvedFieldType {
            annotation: "Long".to_string(),
            kind: ResolvedFieldKind::Scalar,
            requirements: TypeScriptRequirements { long: true },
            message_conversion: None,
            enum_conversion: None,
        },
        PlannedValueType::Scalar(PlannedScalarType::Bool) => ResolvedFieldType {
            annotation: "boolean".to_string(),
            kind: ResolvedFieldKind::Scalar,
            requirements: TypeScriptRequirements::default(),
            message_conversion: None,
            enum_conversion: None,
        },
        PlannedValueType::Scalar(PlannedScalarType::String) => ResolvedFieldType {
            annotation: "string".to_string(),
            kind: ResolvedFieldKind::Scalar,
            requirements: TypeScriptRequirements::default(),
            message_conversion: None,
            enum_conversion: None,
        },
        PlannedValueType::Scalar(PlannedScalarType::Bytes) => ResolvedFieldType {
            annotation: "Uint8Array".to_string(),
            kind: ResolvedFieldKind::Scalar,
            requirements: TypeScriptRequirements::default(),
            message_conversion: None,
            enum_conversion: None,
        },
        PlannedValueType::Enum(enum_type) => {
            if let Some(replacement) = &enum_type.replacement
                && let Some(type_name) = typescript_replacement_type_name(replacement)
            {
                ResolvedFieldType {
                    annotation: type_name,
                    kind: ResolvedFieldKind::Enum,
                    requirements: TypeScriptRequirements::default(),
                    message_conversion: None,
                    enum_conversion: enum_type.info.as_ref().map(|info| EnumValueConversion {
                        from_proto: typescript_from_proto_converter(&info.full_name, replacement),
                        to_proto: typescript_to_proto_converter(&info.full_name, replacement),
                    }),
                }
            } else if let (Some(info), Some(name)) = (&enum_type.info, &enum_type.name) {
                if let Some(planned_enum) = api_plan.enums.get(&info.full_name) {
                    ensure_rendered_enum(planned_enum, enums);
                }
                ResolvedFieldType {
                    annotation: name.clone(),
                    kind: ResolvedFieldKind::Enum,
                    requirements: TypeScriptRequirements::default(),
                    message_conversion: None,
                    enum_conversion: None,
                }
            } else {
                ResolvedFieldType {
                    annotation: "number".to_string(),
                    kind: ResolvedFieldKind::Enum,
                    requirements: TypeScriptRequirements::default(),
                    message_conversion: None,
                    enum_conversion: None,
                }
            }
        }
        PlannedValueType::Flags(flags_type) => {
            if let Some(planned_flags) = api_plan.flags.get(&flags_type.info.full_name) {
                ensure_rendered_flags(planned_flags, flags);
            }
            ResolvedFieldType {
                annotation: flags_type.name.clone(),
                kind: ResolvedFieldKind::Scalar,
                requirements: TypeScriptRequirements::default(),
                message_conversion: None,
                enum_conversion: None,
            }
        }
        PlannedValueType::Variant(variant_type) => {
            if let Some(planned_variant) = api_plan.variants.get(&variant_type.info.full_name) {
                ensure_rendered_variant(planned_variant, api_plan, enums, flags, variants, models);
            }
            ResolvedFieldType {
                annotation: variant_type.name.clone(),
                kind: ResolvedFieldKind::Scalar,
                requirements: TypeScriptRequirements::default(),
                message_conversion: None,
                enum_conversion: None,
            }
        }
        PlannedValueType::Message(message_type) => {
            let conversion = if let Some(replacement) = &message_type.replacement
                && let Some(type_name) = typescript_replacement_type_name(replacement)
            {
                MessageValueConversion {
                    annotation: type_name,
                    kind: MessageValueConversionKind::Override {
                        from_proto: typescript_from_proto_converter(
                            &message_type.info.full_name,
                            replacement,
                        ),
                        to_proto: typescript_to_proto_converter(
                            &message_type.info.full_name,
                            replacement,
                        ),
                    },
                }
            } else if let Some(authored_type) = &message_type.authored_type {
                MessageValueConversion {
                    annotation: typescript_authored_type_annotation(authored_type),
                    kind: MessageValueConversionKind::Override {
                        from_proto: typescript_default_from_proto_name(
                            &message_type.info.full_name,
                        ),
                        to_proto: typescript_default_to_proto_name(&message_type.info.full_name),
                    },
                }
            } else {
                ensure_rendered_model(message_type, api_plan, enums, flags, variants, models);
                let model = models
                    .get(&message_type.info.full_name)
                    .expect("planned model should be rendered");
                MessageValueConversion {
                    annotation: model.name.clone(),
                    kind: MessageValueConversionKind::GeneratedModel {
                        model_name: message_type.model_name.clone(),
                    },
                }
            };
            ResolvedFieldType {
                annotation: conversion.annotation.clone(),
                kind: ResolvedFieldKind::Message,
                requirements: TypeScriptRequirements::default(),
                message_conversion: Some(conversion),
                enum_conversion: None,
            }
        }
        PlannedValueType::Tuple(items) => {
            let item_types = items
                .iter()
                .map(|item| {
                    resolve_planned_value_type(item, api_plan, enums, flags, variants, models)
                })
                .collect::<Vec<_>>();
            let mut requirements = TypeScriptRequirements::default();
            for item_type in &item_types {
                requirements.merge(&item_type.requirements);
            }
            ResolvedFieldType {
                annotation: format!(
                    "[{}]",
                    item_types
                        .iter()
                        .map(|item| item.annotation.as_str())
                        .collect::<Vec<_>>()
                        .join(", ")
                ),
                kind: ResolvedFieldKind::Scalar,
                requirements,
                message_conversion: None,
                enum_conversion: None,
            }
        }
        PlannedValueType::Result { ok, err } => {
            let ok = ok
                .as_ref()
                .map(|ok| resolve_planned_value_type(ok, api_plan, enums, flags, variants, models));
            let err = err.as_ref().map(|err| {
                resolve_planned_value_type(err, api_plan, enums, flags, variants, models)
            });
            let mut requirements = TypeScriptRequirements::default();
            if let Some(ok) = &ok {
                requirements.merge(&ok.requirements);
            }
            if let Some(err) = &err {
                requirements.merge(&err.requirements);
            }
            ResolvedFieldType {
                annotation: typescript_result_annotation(
                    ok.as_ref().map(|ok| ok.annotation.clone()),
                    err.as_ref().map(|err| err.annotation.clone()),
                ),
                kind: ResolvedFieldKind::Scalar,
                requirements,
                message_conversion: None,
                enum_conversion: None,
            }
        }
        PlannedValueType::External {
            type_name,
            fallback,
        } => {
            let mut resolved =
                resolve_planned_value_type(fallback, api_plan, enums, flags, variants, models);
            if let Some(annotation) = type_name.for_language(Language::TypeScript) {
                resolved.annotation = annotation.to_string();
                if annotation.contains("Long") {
                    resolved.requirements.long = true;
                }
            }
            resolved
        }
        PlannedValueType::Unknown => ResolvedFieldType {
            annotation: "unknown".to_string(),
            kind: ResolvedFieldKind::Unknown,
            requirements: TypeScriptRequirements::default(),
            message_conversion: None,
            enum_conversion: None,
        },
    }
}

fn repeated_from_proto_expr(resolved_type: &ResolvedFieldType, field_name: &str) -> String {
    match resolved_type.kind {
        ResolvedFieldKind::Message => format!(
            "proto.{field_name}?.map((value: any) => {}!)",
            resolved_type
                .message_conversion
                .as_ref()
                .expect("message conversion should be present")
                .from_proto_expr("value")
        ),
        ResolvedFieldKind::Enum => format!(
            "proto.{field_name}?.map((value: any) => {})",
            enum_from_proto_expr(resolved_type, "value")
        ),
        _ => format!("proto.{field_name}?.slice()"),
    }
}

fn repeated_to_proto_expr(resolved_type: &ResolvedFieldType, field_name: &str) -> String {
    repeated_to_proto_expr_from_expr(resolved_type, &format!("model.{field_name}"))
}

fn repeated_to_proto_expr_from_expr(resolved_type: &ResolvedFieldType, value_expr: &str) -> String {
    match resolved_type.kind {
        ResolvedFieldKind::Message => format!(
            "{value_expr}?.map((value) => {})",
            resolved_type
                .message_conversion
                .as_ref()
                .expect("message conversion should be present")
                .to_proto_expr("value")
        ),
        ResolvedFieldKind::Enum => format!(
            "{value_expr}?.map((value) => {})",
            enum_to_proto_expr(resolved_type, "value")
        ),
        _ => format!("{value_expr}?.slice()"),
    }
}

fn map_value_from_proto_expr(map_value_type: &ResolvedFieldType, field_name: &str) -> String {
    let value_expr = match map_value_type.kind {
        ResolvedFieldKind::Message => map_value_type
            .message_conversion
            .as_ref()
            .expect("message conversion should be present")
            .from_proto_expr("value"),
        ResolvedFieldKind::Enum => enum_from_proto_expr(map_value_type, "value"),
        _ => format!("value as {}", map_value_type.annotation),
    };
    format!(
        "proto.{field_name} == null ? undefined : (Object.fromEntries(Object.entries(proto.{field_name} as Record<string, any>).map(([key, value]: [string, any]) => [key, {value_expr}])) as Record<string, {}>)",
        map_value_type.annotation
    )
}

fn map_value_to_proto_expr(map_value_type: &ResolvedFieldType, field_name: &str) -> String {
    map_value_to_proto_expr_from_expr(map_value_type, &format!("model.{field_name}"))
}

fn map_value_to_proto_expr_from_expr(
    map_value_type: &ResolvedFieldType,
    value_expr: &str,
) -> String {
    let mapped_value_expr = match map_value_type.kind {
        ResolvedFieldKind::Message => map_value_type
            .message_conversion
            .as_ref()
            .expect("message conversion should be present")
            .to_proto_expr("value"),
        ResolvedFieldKind::Enum => enum_to_proto_expr(map_value_type, "value"),
        _ => "value".to_string(),
    };
    format!(
        "{value_expr} == null ? undefined : Object.fromEntries(Object.entries({value_expr} as Record<string, any>).map(([key, value]: [string, any]) => [key, {mapped_value_expr}]))"
    )
}

fn required_from_proto_expr(
    resolved_type: &ResolvedFieldType,
    owner_name: &str,
    proto_field_name: &str,
    generated_field_name: &str,
    field: &PlannedField,
) -> String {
    let proto_value_expr = if field.has_presence {
        format!("proto.{proto_field_name}")
    } else if matches!(
        &field.kind,
        PlannedFieldKind::Singular(PlannedValueType::Scalar(PlannedScalarType::String))
    ) {
        format!("proto.{proto_field_name} === '' ? undefined : proto.{proto_field_name}")
    } else if matches!(
        &field.kind,
        PlannedFieldKind::Singular(PlannedValueType::Scalar(PlannedScalarType::Bytes))
    ) {
        format!(
            "proto.{proto_field_name} == null || proto.{proto_field_name}.length === 0 ? undefined : proto.{proto_field_name}"
        )
    } else {
        format!("proto.{proto_field_name} ?? undefined")
    };
    let required_proto_expr =
        required_field_expr(&proto_value_expr, owner_name, generated_field_name);

    match resolved_type.kind {
        ResolvedFieldKind::Message => required_field_expr(
            &resolved_type
                .message_conversion
                .as_ref()
                .expect("message conversion should be present")
                .from_proto_expr(&required_proto_expr),
            owner_name,
            generated_field_name,
        ),
        ResolvedFieldKind::Enum => required_field_expr(
            &enum_from_proto_expr(resolved_type, &required_proto_expr),
            owner_name,
            generated_field_name,
        ),
        _ => required_proto_expr,
    }
}

fn required_to_proto_expr(
    resolved_type: &ResolvedFieldType,
    owner_name: &str,
    field_name: &str,
) -> String {
    let required_value =
        required_field_expr(&format!("model.{field_name}"), owner_name, field_name);
    match resolved_type.kind {
        ResolvedFieldKind::Message => resolved_type
            .message_conversion
            .as_ref()
            .expect("message conversion should be present")
            .to_proto_expr(&required_value),
        ResolvedFieldKind::Enum => enum_to_proto_expr(resolved_type, &required_value),
        _ => required_value,
    }
}

fn optional_from_proto_expr(resolved_type: &ResolvedFieldType, field_name: &str) -> String {
    match resolved_type.kind {
        ResolvedFieldKind::Message => format!(
            "proto.{field_name} == null ? undefined : {}",
            resolved_type
                .message_conversion
                .as_ref()
                .expect("message conversion should be present")
                .from_proto_expr(&format!("proto.{field_name}"))
        ),
        ResolvedFieldKind::Enum => format!(
            "proto.{field_name} == null ? undefined : {}",
            enum_from_proto_expr(resolved_type, &format!("proto.{field_name}"))
        ),
        _ => format!("proto.{field_name} ?? undefined"),
    }
}

fn defaulted_enum_from_proto_expr(
    resolved_type: &ResolvedFieldType,
    field_name: &str,
    enum_case: &str,
) -> String {
    let default_expr = enum_default_expr(resolved_type, enum_case);
    format!(
        "proto.{field_name} == null ? {default_expr} : {}",
        enum_from_proto_expr(resolved_type, &format!("proto.{field_name}"))
    )
}

fn optional_to_proto_expr(resolved_type: &ResolvedFieldType, field_name: &str) -> String {
    optional_to_proto_expr_from_expr(resolved_type, &format!("model.{field_name}"))
}

fn optional_to_proto_expr_from_expr(resolved_type: &ResolvedFieldType, value_expr: &str) -> String {
    match resolved_type.kind {
        ResolvedFieldKind::Message => format!(
            "{value_expr} == null ? undefined : {}",
            resolved_type
                .message_conversion
                .as_ref()
                .expect("message conversion should be present")
                .to_proto_expr(value_expr)
        ),
        ResolvedFieldKind::Enum => format!(
            "{value_expr} == null ? undefined : {}",
            enum_to_proto_expr(resolved_type, value_expr)
        ),
        _ => value_expr.to_string(),
    }
}

fn defaulted_enum_to_proto_expr(
    resolved_type: &ResolvedFieldType,
    field_name: &str,
    enum_case: &str,
) -> String {
    let value_expr = format!("model.{field_name}");
    let default_expr = enum_default_expr(resolved_type, enum_case);
    enum_to_proto_expr(
        resolved_type,
        &format!("({value_expr} == null ? {default_expr} : {value_expr})"),
    )
}

fn required_function_to_proto_expr(owner_name: &str, field_name: &str, converter: &str) -> String {
    let required_value =
        required_field_expr(&format!("model.{field_name}"), owner_name, field_name);
    format!("{converter}({required_value})")
}

fn optional_function_to_proto_expr(field_name: &str, converter: &str) -> String {
    let value_expr = format!("model.{field_name}");
    format!(
        "{value_expr} == null ? undefined : {}",
        format!("{converter}({value_expr})")
    )
}

fn required_with_arguments_to_proto_expr(
    resolved_type: &ResolvedFieldType,
    owner_name: &str,
    field_name: &str,
    name_expr_template: &str,
) -> String {
    let required_value =
        required_field_expr(&format!("model.{field_name}"), owner_name, field_name);
    function_value_to_proto_expr(
        resolved_type,
        &with_arguments_name_expr(name_expr_template, &required_value),
    )
}

fn optional_with_arguments_to_proto_expr(
    resolved_type: &ResolvedFieldType,
    field_name: &str,
    name_expr_template: &str,
) -> String {
    let value_expr = format!("model.{field_name}");
    format!(
        "{value_expr} == null ? undefined : {}",
        function_value_to_proto_expr(
            resolved_type,
            &with_arguments_name_expr(name_expr_template, &value_expr),
        )
    )
}

fn function_value_to_proto_expr(resolved_type: &ResolvedFieldType, name_expr: &str) -> String {
    match resolved_type.kind {
        ResolvedFieldKind::Message => match &resolved_type
            .message_conversion
            .as_ref()
            .expect("message conversion should be present")
            .kind
        {
            MessageValueConversionKind::GeneratedModel { model_name } => {
                format!(
                    "{}({{ name: {name_expr} }}) ?? {{}}",
                    model_to_proto_function_name(model_name)
                )
            }
            MessageValueConversionKind::NativeModel => name_expr.to_string(),
            MessageValueConversionKind::Override { to_proto, .. } => {
                format!("{to_proto}({name_expr})")
            }
        },
        _ => name_expr.to_string(),
    }
}

fn with_arguments_name_expr(name_expr_template: &str, value_expr: &str) -> String {
    format!(
        "((value) => typeof value === 'string' ? value : ({}))({value_expr})",
        name_expr_template,
    )
}

fn optional_function_args_to_proto_expr(
    _resolved_type: &ResolvedFieldType,
    field_name: &str,
) -> String {
    format!("requestArgsToPayloads(model.{field_name})")
}

fn enum_from_proto_expr(resolved_type: &ResolvedFieldType, expr: &str) -> String {
    if let Some(enum_conversion) = &resolved_type.enum_conversion {
        enum_conversion.from_proto_expr(expr)
    } else {
        format!("{expr} as {}", resolved_type.annotation)
    }
}

fn enum_default_expr(resolved_type: &ResolvedFieldType, enum_case: &str) -> String {
    let value_name = if resolved_type.enum_conversion.is_some() {
        enum_case.to_shouty_snake_case()
    } else {
        enum_case.to_upper_camel_case()
    };
    format!("{}.{}", resolved_type.annotation, value_name)
}

fn enum_to_proto_expr(resolved_type: &ResolvedFieldType, expr: &str) -> String {
    if let Some(enum_conversion) = &resolved_type.enum_conversion {
        enum_conversion.to_proto_expr(expr)
    } else {
        format!("{expr} as number")
    }
}

fn message_typescript_interface_ref(type_info: &PlannedTypeInfo) -> String {
    let relative_name = relative_descriptor_name(&type_info.full_name, &type_info.package);
    let mut parts = relative_name.split('.').collect::<Vec<_>>();
    let leaf = parts
        .pop()
        .expect("descriptor names should not be empty")
        .to_string();
    if parts.is_empty() {
        format!("{}.I{leaf}", type_info.package)
    } else {
        format!("{}.{}.I{leaf}", type_info.package, parts.join("."))
    }
}

#[derive(Debug, Default)]
struct SupportExports {
    value_names: Vec<String>,
    type_names: Vec<String>,
}

fn collect_typescript_requirements(
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    services: &[RenderedService<'_>],
) -> TypeScriptRequirements {
    let mut requirements = TypeScriptRequirements::default();
    for variant in variants {
        for case in &variant.cases {
            requirements.merge(&case.requirements);
        }
    }
    for model in models {
        for field in &model.fields {
            requirements.merge(&field.requirements);
        }
    }
    for service in services {
        for resource in &service.resources {
            for field in &resource.fields {
                requirements.merge(&typescript_resource_field_requirements(&field.kind));
            }
            for method in &resource.methods {
                for param in &method.params {
                    requirements.merge(&typescript_resource_field_requirements(&param.kind));
                }
                if let Some(result) = &method.result
                    && let PlannedResourceMethodResultKind::Value(kind) = &result.kind
                {
                    requirements.merge(&typescript_resource_field_requirements(kind));
                }
            }
        }
    }
    requirements
}

fn render_module_files(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    services: &[RenderedService<'_>],
    requirements: &TypeScriptRequirements,
    language_imports: &[LanguageImportSpec],
    support_source: Option<&str>,
) -> GeneratedFiles {
    let support_source = support_source.filter(|source| !source.trim().is_empty());
    let support_exports = support_source.map(support_exports);
    let mut files = BTreeMap::<PathBuf, String>::new();
    files.insert(
        "index.ts".into(),
        render_index_module(enums, flags, variants, models, services),
    );
    files.insert(
        "models.ts".into(),
        render_models_module(
            enums,
            flags,
            variants,
            models,
            language_imports,
            support_exports.as_ref(),
        ),
    );
    files.insert(
        "service.ts".into(),
        render_service_module(
            enums,
            flags,
            variants,
            models,
            services,
            requirements,
            language_imports,
        ),
    );
    if services.iter().any(|service| !service.resources.is_empty()) {
        files.insert(
            "resources.ts".into(),
            render_resources_module(
                enums,
                flags,
                variants,
                models,
                services,
                requirements,
                language_imports,
                support_exports.as_ref(),
            ),
        );
    }
    for service in services {
        for operation in &service.operations {
            files.insert(
                format!("operations/{}.ts", operation_file_name(operation)).into(),
                render_operation_module(
                    enums,
                    flags,
                    variants,
                    models,
                    services,
                    service,
                    operation,
                    requirements,
                    language_imports,
                    support_exports.as_ref(),
                ),
            );
        }
    }
    if let Some(support_source) = support_source {
        files.insert("support.ts".into(), render_support_module(support_source));
    }
    GeneratedFiles::directory(files)
}

fn operation_file_name(operation: &RenderedOperation<'_>) -> String {
    operation.attr_name.to_kebab_case()
}

fn support_exports(source: &str) -> SupportExports {
    let mut exports = SupportExports::default();
    for line in source.lines().map(str::trim_start) {
        if let Some(name) = export_name(line, "export function ") {
            exports.value_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export const ") {
            exports.value_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export let ") {
            exports.value_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export var ") {
            exports.value_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export class ") {
            exports.value_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export enum ") {
            exports.value_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export type ") {
            exports.type_names.push(name.to_string());
        } else if let Some(name) = export_name(line, "export interface ") {
            exports.type_names.push(name.to_string());
        }
    }
    exports
}

fn export_name<'a>(line: &'a str, prefix: &str) -> Option<&'a str> {
    let remainder = line.strip_prefix(prefix)?;
    let end = remainder
        .find(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '_' || ch == '$'))
        .unwrap_or(remainder.len());
    (end > 0).then_some(&remainder[..end])
}

fn render_typescript_namespace_imports(
    output: &mut String,
    source: &str,
    language_imports: &[LanguageImportSpec],
    generated_value_imports: &[(&str, &str)],
) {
    let mut namespace_imports = BTreeMap::<(String, String), bool>::new();
    let mut named_type_imports = BTreeMap::<String, BTreeSet<String>>::new();
    for (namespace, package) in generated_value_imports {
        if contains_qualified_identifier(source, namespace) {
            namespace_imports.insert(((*namespace).to_string(), (*package).to_string()), true);
        }
    }
    for import in language_imports {
        if !contains_qualified_identifier(source, &import.reference) {
            continue;
        }
        match import.import_style {
            LanguageImportStyle::Namespace => {
                let key = (import.reference.clone(), import.module.clone());
                let needs_value_import = !import.type_only;
                namespace_imports
                    .entry(key)
                    .and_modify(|existing| *existing |= needs_value_import)
                    .or_insert(needs_value_import);
            }
            LanguageImportStyle::Named => {
                named_type_imports
                    .entry(import.module.clone())
                    .or_default()
                    .insert(
                        import
                            .name
                            .clone()
                            .unwrap_or_else(|| import.reference.clone()),
                    );
            }
            LanguageImportStyle::Module => {}
        }
    }

    for ((namespace, package), needs_value_import) in namespace_imports {
        if needs_value_import {
            output.push_str("import * as ");
        } else {
            output.push_str("import type * as ");
        }
        output.push_str(&namespace);
        output.push_str(" from '");
        output.push_str(&package);
        output.push_str("';\n");
    }
    for (package, imports) in named_type_imports {
        output.push_str("import type { ");
        output.push_str(&imports.into_iter().collect::<Vec<_>>().join(", "));
        output.push_str(" } from '");
        output.push_str(&package);
        output.push_str("';\n");
    }
}

fn render_typescript_default_type_import_if_used(
    output: &mut String,
    source: &str,
    name: &str,
    package: &str,
) {
    if !contains_identifier(source, name) {
        return;
    }
    output.push_str("import type ");
    output.push_str(name);
    output.push_str(" from '");
    output.push_str(package);
    output.push_str("';\n");
}

fn contains_identifier(source: &str, identifier: &str) -> bool {
    source.match_indices(identifier).any(|(index, _)| {
        let before = source[..index].chars().next_back();
        let after = source[index + identifier.len()..].chars().next();
        !before.is_some_and(is_typescript_identifier_char)
            && !after.is_some_and(is_typescript_identifier_char)
    })
}

fn contains_qualified_identifier(source: &str, identifier: &str) -> bool {
    let chars = source.char_indices().collect::<Vec<_>>();
    let mut index = 0;
    while index < chars.len() {
        let (start_byte, ch) = chars[index];
        match ch {
            '\'' | '"' | '`' => {
                index = skip_typescript_string(&chars, index, ch);
                continue;
            }
            '/' if chars.get(index + 1).is_some_and(|(_, next)| *next == '/') => {
                index += 2;
                while index < chars.len() && chars[index].1 != '\n' {
                    index += 1;
                }
                continue;
            }
            '/' if chars.get(index + 1).is_some_and(|(_, next)| *next == '*') => {
                index += 2;
                while index + 1 < chars.len() {
                    if chars[index].1 == '*' && chars[index + 1].1 == '/' {
                        index += 2;
                        break;
                    }
                    index += 1;
                }
                continue;
            }
            _ => {}
        }

        if !is_typescript_identifier_start(ch) {
            index += 1;
            continue;
        }

        let before = source[..start_byte].chars().next_back();
        if before.is_some_and(|before| is_typescript_identifier_char(before) || before == '.') {
            index += 1;
            continue;
        }

        let mut end = index + 1;
        while end < chars.len() && is_typescript_identifier_char(chars[end].1) {
            end += 1;
        }
        let end_byte = chars
            .get(end)
            .map(|(byte, _)| *byte)
            .unwrap_or(source.len());
        if &source[start_byte..end_byte] == identifier {
            let mut after = end;
            while after < chars.len() && chars[after].1.is_whitespace() {
                after += 1;
            }
            if after < chars.len() && chars[after].1 == '.' {
                return true;
            }
        }
        index = end;
    }
    false
}

fn skip_typescript_string(chars: &[(usize, char)], start: usize, quote: char) -> usize {
    let mut index = start + 1;
    let mut escaped = false;
    while index < chars.len() {
        let ch = chars[index].1;
        if escaped {
            escaped = false;
        } else if ch == '\\' {
            escaped = true;
        } else if ch == quote {
            return index + 1;
        }
        index += 1;
    }
    index
}

fn is_typescript_identifier_start(ch: char) -> bool {
    ch.is_ascii_alphabetic() || ch == '_' || ch == '$'
}

fn is_typescript_identifier_char(ch: char) -> bool {
    ch.is_ascii_alphanumeric() || ch == '_' || ch == '$'
}

fn used_import_names(source: &str, names: &[String]) -> Vec<String> {
    names
        .iter()
        .filter(|name| contains_identifier(source, name))
        .cloned()
        .collect()
}

fn render_generated_module(imports: String, body: String) -> String {
    let mut output = String::new();
    output.push_str(GENERATED_HEADER);
    output.push_str("\n\n");
    if !imports.is_empty() {
        output.push_str(&imports);
        output.push('\n');
    }
    output.push_str(&body);
    if !body.ends_with('\n') {
        output.push('\n');
    }
    output
}

fn render_support_module(support_source: &str) -> String {
    render_generated_module(String::new(), support_source.to_string())
}

fn render_index_module(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    services: &[RenderedService<'_>],
) -> String {
    let mut output = String::new();
    output.push_str(GENERATED_HEADER);
    output.push_str("\n\n");
    for service in services {
        output.push_str("export { ");
        output.push_str(service.name);
        output.push_str(" } from './service.ts';\n");
    }
    for service in services {
        for operation in &service.operations {
            output.push_str("export { ");
            output.push_str(&operation.attr_name);
            output.push_str(" } from './operations/");
            output.push_str(&operation_file_name(operation));
            output.push_str(".ts';\n");
        }
    }

    let value_model_names = enums
        .iter()
        .map(|enumeration| enumeration.name.as_str())
        .chain(flags.iter().map(|flags| flags.name.as_str()))
        .collect::<Vec<_>>();
    if !value_model_names.is_empty() {
        output.push_str("export { ");
        output.push_str(&value_model_names.join(", "));
        output.push_str(" } from './models.ts';\n");
    }

    let type_model_names = variants
        .iter()
        .map(|variant| variant.name.as_str())
        .chain(models.iter().map(|model| model.name.as_str()))
        .collect::<Vec<_>>();
    if !type_model_names.is_empty() {
        output.push_str("export type { ");
        output.push_str(&type_model_names.join(", "));
        output.push_str(" } from './models.ts';\n");
    }

    let resource_names = services
        .iter()
        .flat_map(|service| {
            service
                .resources
                .iter()
                .map(|resource| resource.type_name.as_str())
        })
        .collect::<Vec<_>>();
    if !resource_names.is_empty() {
        output.push_str("export { ");
        output.push_str(&resource_names.join(", "));
        output.push_str(" } from './resources.ts';\n");
    }
    output
}

fn render_models_module(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    language_imports: &[LanguageImportSpec],
    support_exports: Option<&SupportExports>,
) -> String {
    let mut body = String::new();
    render_required_field(&mut body, true);

    let uses_invocation_models = models
        .iter()
        .any(|model| !model.functions.is_empty() || !model.with_arguments.is_empty());
    let uses_configured_payload_converter = uses_invocation_models
        || models
            .iter()
            .any(|model| model_uses_configured_payload_converter(model));
    if uses_configured_payload_converter {
        body.push('\n');
        render_configured_payload_converter(&mut body);
    }
    if uses_invocation_models {
        body.push('\n');
        render_function_runtime_helpers(&mut body);
    }
    if !enums.is_empty() {
        body.push('\n');
        for enumeration in enums {
            render_enum(&mut body, enumeration);
            body.push('\n');
        }
    }
    if !flags.is_empty() {
        body.push('\n');
        for flag_set in flags {
            render_flags(&mut body, flag_set);
            body.push('\n');
        }
    }
    if !variants.is_empty() {
        body.push('\n');
        for variant in variants {
            render_variant(&mut body, variant);
            body.push('\n');
        }
    }
    if !models.is_empty() {
        body.push('\n');
        for model in models {
            render_model(&mut body, model);
            body.push('\n');
        }
    }
    let mut imports = String::new();
    let generated_value_imports = if uses_configured_payload_converter {
        vec![("common", "@temporalio/common")]
    } else {
        Vec::new()
    };
    render_typescript_namespace_imports(
        &mut imports,
        &body,
        language_imports,
        &generated_value_imports,
    );
    render_typescript_default_type_import_if_used(&mut imports, &body, "Long", "long");
    render_support_imports(&mut imports, support_exports, "./support.ts", &body);
    render_generated_module(imports, body)
}

fn render_service_module(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    services: &[RenderedService<'_>],
    _requirements: &TypeScriptRequirements,
    language_imports: &[LanguageImportSpec],
) -> String {
    let mut body = String::new();
    for service in services {
        render_service_definition(&mut body, service);
        body.push('\n');
    }
    let mut imports = String::new();
    render_typescript_namespace_imports(
        &mut imports,
        &body,
        language_imports,
        &[("nexus", "nexus-rpc")],
    );
    render_typescript_default_type_import_if_used(&mut imports, &body, "Long", "long");
    render_type_imports(
        &mut imports,
        "./models.ts",
        &used_import_names(&body, &model_type_names(enums, flags, variants, models)),
    );
    render_type_imports(
        &mut imports,
        "./resources.ts",
        &used_import_names(&body, &resource_type_names(services)),
    );
    render_generated_module(imports, body)
}

fn render_resources_module(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    services: &[RenderedService<'_>],
    _requirements: &TypeScriptRequirements,
    language_imports: &[LanguageImportSpec],
    support_exports: Option<&SupportExports>,
) -> String {
    let mut body = String::new();
    for service in services {
        for resource in &service.resources {
            render_resource(&mut body, resource, service);
            body.push('\n');
        }
    }
    render_nexus_type_registrations(&mut body, services);
    let mut imports = String::new();
    render_typescript_namespace_imports(&mut imports, &body, language_imports, &[]);
    render_typescript_default_type_import_if_used(&mut imports, &body, "Long", "long");
    render_value_imports(
        &mut imports,
        "../nex-gen-runtime.ts",
        &used_import_names(
            &body,
            &[
                "markNexusResource".to_string(),
                "registerNexusResource".to_string(),
            ],
        ),
    );
    render_type_imports(
        &mut imports,
        "./models.ts",
        &used_import_names(&body, &model_type_names(enums, flags, variants, models)),
    );
    render_support_imports(&mut imports, support_exports, "./support.ts", &body);
    for service in services {
        for operation in &service.operations {
            if contains_identifier(&body, &operation.attr_name) {
                render_value_imports(
                    &mut imports,
                    &format!("./operations/{}.ts", operation_file_name(operation)),
                    std::slice::from_ref(&operation.attr_name),
                );
            }
        }
    }
    render_generated_module(imports, body)
}

fn render_operation_module(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
    services: &[RenderedService<'_>],
    service: &RenderedService<'_>,
    operation: &RenderedOperation<'_>,
    _requirements: &TypeScriptRequirements,
    language_imports: &[LanguageImportSpec],
    support_exports: Option<&SupportExports>,
) -> String {
    let mut body = String::new();
    render_operation_function(&mut body, service, operation);
    let mut imports = String::new();
    render_typescript_namespace_imports(
        &mut imports,
        &body,
        language_imports,
        &[("workflow", "@temporalio/workflow")],
    );
    render_typescript_default_type_import_if_used(&mut imports, &body, "Long", "long");
    render_value_imports(&mut imports, "../service.ts", &[service.name.to_string()]);
    let mut model_values = model_to_proto_function_names(models);
    model_values.push("requiredField".to_string());
    model_values.sort();
    model_values.dedup();
    render_value_imports(
        &mut imports,
        "../models.ts",
        &used_import_names(&body, &model_values),
    );
    render_type_imports(
        &mut imports,
        "../models.ts",
        &used_import_names(&body, &model_type_names(enums, flags, variants, models)),
    );
    render_support_imports(&mut imports, support_exports, "../support.ts", &body);
    let resources = resource_type_names(services);
    render_value_imports(
        &mut imports,
        "../resources.ts",
        &used_import_names(&body, &resources),
    );
    if operation.input_nexus_type_id.is_some() {
        render_value_imports(
            &mut imports,
            "../../nex-gen-runtime.ts",
            &["nexusValue".to_string()],
        );
    }
    render_generated_module(imports, body)
}

fn render_support_imports(
    output: &mut String,
    support_exports: Option<&SupportExports>,
    path: &str,
    source: &str,
) {
    if let Some(support_exports) = support_exports {
        render_value_imports(
            output,
            path,
            &used_import_names(source, &support_exports.value_names),
        );
        render_type_imports(
            output,
            path,
            &used_import_names(source, &support_exports.type_names),
        );
    }
}

fn render_value_imports(output: &mut String, path: &str, names: &[String]) {
    if names.is_empty() {
        return;
    }
    output.push_str("import { ");
    output.push_str(&names.join(", "));
    output.push_str(" } from '");
    output.push_str(path);
    output.push_str("';\n");
}

fn render_type_imports(output: &mut String, path: &str, names: &[String]) {
    if names.is_empty() {
        return;
    }
    output.push_str("import type { ");
    output.push_str(&names.join(", "));
    output.push_str(" } from '");
    output.push_str(path);
    output.push_str("';\n");
}

fn model_type_names(
    enums: &[&RenderedEnum],
    flags: &[&RenderedFlags],
    variants: &[&RenderedVariant],
    models: &[&RenderedModel],
) -> Vec<String> {
    enums
        .iter()
        .map(|enumeration| enumeration.name.clone())
        .chain(flags.iter().map(|flags| flags.name.clone()))
        .chain(variants.iter().map(|variant| variant.name.clone()))
        .chain(models.iter().map(|model| model.name.clone()))
        .collect()
}

fn resource_type_names(services: &[RenderedService<'_>]) -> Vec<String> {
    services
        .iter()
        .flat_map(|service| {
            service
                .resources
                .iter()
                .map(|resource| resource.type_name.clone())
        })
        .collect()
}

fn model_to_proto_function_names(models: &[&RenderedModel]) -> Vec<String> {
    models
        .iter()
        .filter(|model| model.capabilities.to_proto)
        .map(|model| model_to_proto_function_name(&model.name))
        .collect()
}

fn render_required_field(output: &mut String, exported: bool) {
    if exported {
        output.push_str("export ");
    }
    output.push_str("function requiredField<T>(\n");
    output.push_str("  value: T | null | undefined,\n");
    output.push_str("  owner: string,\n");
    output.push_str("  field: string,\n");
    output.push_str("): T {\n");
    output.push_str("  if (value == null) {\n");
    output.push_str("    throw new Error(`missing required field ${owner}.${field}`);\n");
    output.push_str("  }\n");
    output.push_str("  return value;\n");
    output.push_str("}\n");
}

fn render_nexus_type_registrations(output: &mut String, services: &[RenderedService<'_>]) {
    let has_resources = services.iter().any(|service| !service.resources.is_empty());
    if !has_resources {
        return;
    }
    output.push('\n');
    for service in services {
        for resource in &service.resources {
            let type_id = typescript_resource_type_id(service, resource);
            output.push_str("registerNexusResource(");
            output.push_str(&typescript_string_literal(&type_id));
            output.push_str(", (fields): ");
            output.push_str(&resource.type_name);
            output.push_str(" => {\n");
            output.push_str("  return new ");
            output.push_str(&resource.type_name);
            output.push_str("(\n");
            for field in &resource.fields {
                output.push_str("    fields.");
                output.push_str(&typescript_generated_field_name(&field.name));
                output.push_str(" as ");
                output.push_str(&typescript_resource_field_annotation(
                    &field.kind,
                    field.optional,
                    field.function.as_ref(),
                ));
                output.push_str(",\n");
            }
            output.push_str("  );\n");
            output.push_str("});\n");
            output.push_str("markNexusResource(");
            output.push_str(&resource.type_name);
            output.push_str(", ");
            output.push_str(&typescript_string_literal(&type_id));
            output.push_str(");\n\n");
        }
    }
}

fn render_enum(output: &mut String, enumeration: &RenderedEnum) {
    output.push_str("export enum ");
    output.push_str(&enumeration.name);
    output.push_str(" {\n");
    if enumeration.values.is_empty() {
        output.push_str("}\n");
        return;
    }
    for value in &enumeration.values {
        output.push_str("  ");
        output.push_str(&value.name);
        output.push_str(" = ");
        output.push_str(&value.number.to_string());
        output.push_str(",\n");
    }
    output.push_str("}\n");
}

fn render_flags(output: &mut String, flags: &RenderedFlags) {
    output.push_str("export enum ");
    output.push_str(&flags.name);
    output.push_str(" {\n");
    if flags.flags.is_empty() {
        output.push_str("}\n");
        return;
    }
    for flag in &flags.flags {
        output.push_str("  ");
        output.push_str(&flag.name);
        output.push_str(" = 2 ** ");
        output.push_str(&flag.bit.to_string());
        output.push_str(",\n");
    }
    output.push_str("}\n");
}

fn render_variant(output: &mut String, variant: &RenderedVariant) {
    output.push_str("export type ");
    output.push_str(&variant.name);
    output.push_str(" =\n");
    if variant.cases.is_empty() {
        output.push_str("  never;\n");
        return;
    }
    for (index, case) in variant.cases.iter().enumerate() {
        output.push_str("  | { tag: ");
        output.push_str(&typescript_string_literal(&case.name));
        if let Some(payload_annotation) = &case.payload_annotation {
            output.push_str("; value: ");
            output.push_str(payload_annotation);
        }
        output.push_str(" }");
        if index + 1 == variant.cases.len() {
            output.push_str(";\n");
        } else {
            output.push('\n');
        }
    }
}

fn render_function_runtime_helpers(output: &mut String) {
    output.push_str("function requestArgsToPayloads(\n");
    output.push_str("  args: ReadonlyArray<unknown> | undefined,\n");
    output.push_str("): temporal.api.common.v1.IPayloads | undefined {\n");
    output.push_str("  if (args == null) {\n");
    output.push_str("    return undefined;\n");
    output.push_str("  }\n");
    output
        .push_str("  const payloads = common.toPayloads(configuredPayloadConverter(), ...args);\n");
    output.push_str("  return payloads == null ? undefined : { payloads };\n");
    output.push_str("}\n");
}

fn model_uses_configured_payload_converter(model: &RenderedModel) -> bool {
    model.fields.iter().any(|field| {
        field.to_proto_expr.contains("configuredPayloadConverter()")
            || field
                .flattened_fields
                .iter()
                .any(|field| field.to_proto_expr.contains("configuredPayloadConverter()"))
    }) || model
        .sourced_fields
        .iter()
        .any(|field| field.to_proto_expr.contains("configuredPayloadConverter()"))
}

fn render_configured_payload_converter(output: &mut String) {
    output.push_str("function configuredPayloadConverter(): common.PayloadConverter {\n");
    output.push_str("  const activator = (\n");
    output.push_str("    globalThis as typeof globalThis & {\n");
    output.push_str("      __TEMPORAL_ACTIVATOR__?: {\n");
    output.push_str("        payloadConverter?: common.PayloadConverter;\n");
    output.push_str("      };\n");
    output.push_str("    }\n");
    output.push_str("  ).__TEMPORAL_ACTIVATOR__;\n");
    output.push_str("  if (activator?.payloadConverter == null) {\n");
    output.push_str(
        "    throw new Error('payload converter is unavailable outside workflow context');\n",
    );
    output.push_str("  }\n");
    output.push_str("  return activator.payloadConverter;\n");
    output.push_str("}\n");
}

fn render_type_parameter_list(type_parameters: &[RenderedTypeParameter]) -> String {
    if type_parameters.is_empty() {
        return String::new();
    }

    let params = type_parameters
        .iter()
        .map(|type_parameter| {
            format!(
                "{} extends {} = {}",
                type_parameter.name, type_parameter.constraint, type_parameter.default
            )
        })
        .collect::<Vec<_>>()
        .join(", ");
    format!("<{params}>")
}

fn render_named_generic_function_start(
    output: &mut String,
    name: &str,
    type_parameters: &[RenderedTypeParameter],
    indent: usize,
) {
    let indent_str = " ".repeat(indent);
    output.push_str(name);
    output.push_str("<\n");
    for type_parameter in type_parameters {
        output.push_str(&indent_str);
        output.push_str("  ");
        output.push_str(&type_parameter.name);
        output.push_str(" extends ");
        output.push_str(&type_parameter.constraint);
        output.push_str(" = ");
        output.push_str(&type_parameter.default);
        output.push_str(",\n");
    }
    output.push_str(&indent_str);
    output.push_str(">(\n");
}

fn render_function_model_invocations(output: &mut String, model: &RenderedModel) {
    let mut invocations = model
        .functions
        .iter()
        .filter(|function| matches!(function.args, RenderedFunctionArgs::Varargs { .. }))
        .map(RenderedInvocationField::Function)
        .chain(
            model
                .with_arguments
                .iter()
                .map(RenderedInvocationField::WithArguments),
        )
        .collect::<Vec<_>>();
    invocations.sort_by(|left, right| {
        left.sort_key()
            .cmp(&right.sort_key())
            .then_with(|| left.field_name().cmp(right.field_name()))
    });

    for (index, invocation) in invocations.into_iter().enumerate() {
        if index > 0 {
            output.push_str(" &\n");
        }
        render_invocation_union(output, model, invocation);
    }
}

fn render_invocation_model_replace_helper(output: &mut String, model: &RenderedModel) {
    let field_names = invocation_model_replaced_field_names(model);
    if field_names.is_empty() {
        return;
    }
    output.push_str("export type ");
    output.push_str(&invocation_model_replace_type_name(&model.name));
    output.push_str("<Base, New> = Omit<Base, ");
    output.push_str(
        &field_names
            .iter()
            .map(|field_name| typescript_string_literal(field_name))
            .collect::<Vec<_>>()
            .join(" | "),
    );
    output.push_str("> & New;\n\n");
}

fn invocation_model_replaced_field_names(model: &RenderedModel) -> Vec<String> {
    let mut names = model
        .functions
        .iter()
        .filter(|function| matches!(function.args, RenderedFunctionArgs::Varargs { .. }))
        .flat_map(|function| {
            [
                function.callable_field_name.clone(),
                function.args_field_name.clone(),
            ]
        })
        .chain(model.with_arguments.iter().flat_map(|with_arguments| {
            [
                with_arguments.value_field_name.clone(),
                with_arguments.args_field_name.clone(),
            ]
        }))
        .collect::<Vec<_>>();
    names.sort();
    names.dedup();
    names
}

fn model_uses_invocation_composition(model: &RenderedModel) -> bool {
    model
        .functions
        .iter()
        .any(|function| matches!(function.args, RenderedFunctionArgs::Varargs { .. }))
        || !model.with_arguments.is_empty()
}

fn render_invocation_model_base_type(output: &mut String, model: &RenderedModel) {
    output.push_str("{\n");
    for field in &model.fields {
        render_invocation_model_base_property(output, model, field);
    }
    output.push('}');
}

fn render_invocation_model_base_property(
    output: &mut String,
    model: &RenderedModel,
    field: &RenderedField,
) {
    if !field.flattened_fields.is_empty() {
        render_flattened_type_properties(output, "  ", &field.flattened_fields);
        return;
    }
    if let Some(function) = model
        .functions
        .iter()
        .find(|function| function.callable_field_name == field.name)
    {
        render_typescript_type_property(
            output,
            "  ",
            &field.name,
            field.optional,
            &typescript_function_value_annotation(
                function.alternate_annotation.as_deref(),
                &function.type_parameter_name,
            ),
            typescript_model_field_doc(model, field).as_deref(),
        );
        return;
    }
    if let Some(function) = model
        .functions
        .iter()
        .find(|function| function.args_field_name == field.name)
    {
        render_typescript_type_property(
            output,
            "  ",
            &field.name,
            field.optional,
            &typescript_function_args_base_annotation(function),
            typescript_model_field_doc(model, field).as_deref(),
        );
        return;
    }
    if let Some(with_arguments) = model
        .with_arguments
        .iter()
        .find(|with_arguments| with_arguments.value_field_name == field.name)
    {
        render_typescript_type_property(
            output,
            "  ",
            &field.name,
            field.optional,
            &typescript_function_value_annotation(
                with_arguments.alternate_annotation.as_deref(),
                &with_arguments.type_parameter_name,
            ),
            typescript_model_field_doc(model, field).as_deref(),
        );
        return;
    }
    if let Some(with_arguments) = model
        .with_arguments
        .iter()
        .find(|with_arguments| with_arguments.args_field_name == field.name)
    {
        render_typescript_type_property(
            output,
            "  ",
            &field.name,
            field.optional,
            &format!(
                "ReadonlyArray<unknown> | Readonly<{}>",
                with_arguments.args_type_parameter_name
            ),
            typescript_model_field_doc(model, field).as_deref(),
        );
        return;
    }
    render_typescript_type_property(
        output,
        "  ",
        &field.name,
        field.optional,
        &field.annotation,
        typescript_model_field_doc(model, field).as_deref(),
    );
}

fn typescript_function_value_annotation(
    alternate_annotation: Option<&str>,
    typed_annotation: &str,
) -> String {
    if let Some(alternate_annotation) = alternate_annotation {
        format!(
            "{alternate_annotation} | {}",
            parenthesized_typescript_union_member(typed_annotation)
        )
    } else {
        typed_annotation.to_string()
    }
}

fn typescript_function_args_base_annotation(function: &RenderedFunctionField) -> String {
    let function_args = typescript_function_request_args_expression(function);
    let parameters = format!("Readonly<{function_args}>");
    match function.args {
        RenderedFunctionArgs::Varargs { .. } => {
            format!("ReadonlyArray<unknown> | {parameters}")
        }
        _ => parameters,
    }
}

fn typescript_function_request_args_expression(function: &RenderedFunctionField) -> String {
    match &function.args {
        RenderedFunctionArgs::Varargs {
            prefix,
            typescript_drop_prefix: false,
        } if !prefix.is_empty() => {
            let prefix_pattern = vec!["any"; prefix.len()].join(", ");
            format!(
                "Parameters<{}> extends [{prefix_pattern}, ...infer Args] ? Args : never",
                function.type_parameter_name
            )
        }
        _ => format!("Parameters<{}>", function.type_parameter_name),
    }
}

fn parenthesized_typescript_union_member(annotation: &str) -> String {
    if annotation.starts_with('(') {
        format!("({annotation})")
    } else {
        annotation.to_string()
    }
}

fn render_invocation_union(
    output: &mut String,
    model: &RenderedModel,
    invocation: RenderedInvocationField<'_>,
) {
    match invocation {
        RenderedInvocationField::Function(function) => {
            let request_args = typescript_function_request_args_expression(function);
            let request_args_union_member = parenthesized_typescript_type_expr(&request_args);
            let args_annotation = format!("{request_args_union_member} | Readonly<{request_args}>");
            let typed_args_condition = format!("({request_args})");
            render_request_invocation_union(
                output,
                model,
                &function.callable_field_name,
                &function.args_field_name,
                function.alternate_annotation.as_deref(),
                "ReadonlyArray<unknown>",
                &function.type_parameter_name,
                &typed_args_condition,
                &args_annotation,
            );
        }
        RenderedInvocationField::WithArguments(with_arguments) => {
            let args_annotation = format!(
                "{} | Readonly<{}>",
                with_arguments.args_type_parameter_name, with_arguments.args_type_parameter_name
            );
            render_request_invocation_union(
                output,
                model,
                &with_arguments.value_field_name,
                &with_arguments.args_field_name,
                with_arguments.alternate_annotation.as_deref(),
                "ReadonlyArray<unknown>",
                &with_arguments.type_parameter_name,
                &with_arguments.args_type_parameter_name,
                &args_annotation,
            );
        }
    }
}

fn render_request_invocation_union(
    output: &mut String,
    model: &RenderedModel,
    value_field_name: &str,
    args_field_name: &str,
    alternate_value_annotation: Option<&str>,
    alternate_args_annotation: &str,
    typed_value_annotation: &str,
    typed_args_condition: &str,
    typed_args_annotation: &str,
) {
    let value_doc = typescript_model_field_doc_by_name(model, value_field_name);
    let args_doc = typescript_model_field_doc_by_name(model, args_field_name);
    if let Some(alternate_value_annotation) = alternate_value_annotation {
        output.push_str("  (\n");
        output.push_str("    | {\n");
        render_typescript_type_property(
            output,
            "      ",
            value_field_name,
            false,
            alternate_value_annotation,
            value_doc.as_deref(),
        );
        render_typescript_type_property(
            output,
            "      ",
            args_field_name,
            true,
            alternate_args_annotation,
            args_doc.as_deref(),
        );
        output.push_str("    }\n");
        output.push_str("    | ({\n");
    } else {
        output.push_str("  {\n");
    }
    render_typescript_type_property(
        output,
        "        ",
        value_field_name,
        false,
        typed_value_annotation,
        value_doc.as_deref(),
    );
    output.push_str("      } & (");
    output.push_str(typed_args_condition);
    output.push_str(" extends [any, ...any[]]\n");
    output.push_str("        ? {\n");
    render_typescript_type_property(
        output,
        "            ",
        args_field_name,
        false,
        typed_args_annotation,
        args_doc.as_deref(),
    );
    output.push_str("          }\n");
    output.push_str("        : {\n");
    render_typescript_type_property(
        output,
        "            ",
        args_field_name,
        true,
        typed_args_annotation,
        args_doc.as_deref(),
    );
    if alternate_value_annotation.is_some() {
        output.push_str("          }))\n");
        output.push_str("  )");
    } else {
        output.push_str("          })\n");
    }
}

fn parenthesized_typescript_type_expr(expr: &str) -> String {
    if expr.contains(" extends ") && expr.contains(" ? ") {
        format!("({expr})")
    } else {
        expr.to_string()
    }
}

#[derive(Clone, Copy)]
enum RenderedInvocationField<'a> {
    Function(&'a RenderedFunctionField),
    WithArguments(&'a RenderedWithArgumentsField),
}

impl<'a> RenderedInvocationField<'a> {
    fn sort_key(self) -> (u8, bool) {
        match self {
            Self::Function(function) => (0, !function.primary),
            Self::WithArguments(_) => (1, true),
        }
    }

    fn field_name(self) -> &'a str {
        match self {
            Self::Function(function) => &function.callable_field_name,
            Self::WithArguments(with_arguments) => &with_arguments.value_field_name,
        }
    }
}

fn with_arguments_args_type_expression(
    args_type_template: &str,
    type_parameter_name: &str,
) -> String {
    args_type_template.replace("Value", type_parameter_name)
}

fn render_typescript_doc_comment(
    output: &mut String,
    indent: &str,
    summary: Option<&str>,
    tags: &[(String, String)],
) {
    let has_summary = summary.is_some_and(|summary| !summary.trim().is_empty());
    let has_tags = tags.iter().any(|(_, doc)| !doc.trim().is_empty());
    if !has_summary && !has_tags {
        return;
    }

    output.push_str(indent);
    output.push_str("/**\n");
    if let Some(summary) = summary.map(str::trim).filter(|summary| !summary.is_empty()) {
        for line in summary.lines() {
            push_wrapped_typescript_doc_line(output, indent, "", "", line.trim());
        }
    }
    if has_summary && has_tags {
        output.push_str(indent);
        output.push_str(" *\n");
    }
    for (tag, doc) in tags {
        let doc = doc.trim();
        if doc.is_empty() {
            continue;
        }
        push_wrapped_typescript_doc_line(output, indent, &format!("{tag} "), "  ", doc);
    }
    output.push_str(indent);
    output.push_str(" */\n");
}

fn experimental_doc_tag(experimental: bool) -> Vec<(String, String)> {
    if experimental {
        vec![(
            "@experimental".to_string(),
            EXPERIMENTAL_WARNING.to_string(),
        )]
    } else {
        Vec::new()
    }
}

fn push_wrapped_typescript_doc_line(
    output: &mut String,
    indent: &str,
    first_prefix: &str,
    continuation_prefix: &str,
    text: &str,
) {
    let max_width = TYPESCRIPT_FORMAT_LINE_LENGTH.saturating_sub(indent.chars().count() + 3);
    let text = text.replace("*/", "* /");
    if text.trim().is_empty() {
        output.push_str(indent);
        output.push_str(" *\n");
        return;
    }

    let mut prefix = first_prefix;
    let mut current = String::new();
    for word in text.split_whitespace() {
        let prefix_width = prefix.chars().count();
        let current_width = current.chars().count();
        let word_width = word.chars().count();
        let separator_width = usize::from(!current.is_empty());
        if current_width > 0
            && prefix_width + current_width + separator_width + word_width > max_width
        {
            output.push_str(indent);
            output.push_str(" * ");
            output.push_str(prefix);
            output.push_str(&current);
            output.push('\n');
            prefix = continuation_prefix;
            current.clear();
        }
        if !current.is_empty() {
            current.push(' ');
        }
        current.push_str(word);
    }

    output.push_str(indent);
    output.push_str(" * ");
    output.push_str(prefix);
    output.push_str(&current);
    output.push('\n');
}

fn typescript_model_field_doc(model: &RenderedModel, field: &RenderedField) -> Option<String> {
    if let Some(doc) = &field.doc {
        return Some(doc.clone());
    }
    if let Some(function) = model
        .functions
        .iter()
        .find(|function| function.args_field_name == field.name)
    {
        return Some(format!("Arguments for {}.", function.callable_field_name));
    }
    if let Some(with_arguments) = model
        .with_arguments
        .iter()
        .find(|with_arguments| with_arguments.args_field_name == field.name)
    {
        return Some(format!(
            "Arguments for {}.",
            with_arguments.value_field_name
        ));
    }
    None
}

fn typescript_model_field_doc_by_name(model: &RenderedModel, name: &str) -> Option<String> {
    model
        .fields
        .iter()
        .find(|field| field.name == name)
        .and_then(|field| typescript_model_field_doc(model, field))
}

fn render_typescript_type_property(
    output: &mut String,
    indent: &str,
    name: &str,
    optional: bool,
    annotation: &str,
    doc: Option<&str>,
) {
    render_typescript_doc_comment(output, indent, doc, &[]);
    output.push_str(indent);
    output.push_str(name);
    if optional {
        output.push('?');
    }
    output.push_str(": ");
    output.push_str(annotation);
    output.push_str(";\n");
}

fn render_flattened_type_properties(
    output: &mut String,
    indent: &str,
    fields: &[RenderedFlattenedField],
) {
    for field in fields {
        render_typescript_type_property(
            output,
            indent,
            &field.name,
            field.optional,
            &field.annotation,
            field.doc.as_deref(),
        );
    }
}

fn render_model_to_proto_function(output: &mut String, model: &RenderedModel, function_name: &str) {
    if model.type_parameters.is_empty() {
        output.push_str("export function ");
        output.push_str(function_name);
        output.push_str("(\n");
        output.push_str("  model: ");
        output.push_str(&model.name);
        output.push_str(" | null | undefined,\n");
    } else {
        render_named_generic_function_start(
            output,
            &format!("export function {function_name}"),
            &model.type_parameters,
            0,
        );
        output.push_str("  model: ");
        output.push_str(&generic_model_annotation(
            &model.name,
            &model.type_parameters,
        ));
        output.push_str(" | null | undefined,\n");
    }
    output.push_str("): ");
    output.push_str(&model.proto_ref);
    output.push_str(" | undefined {\n");
    output.push_str("  if (model == null) {\n");
    output.push_str("    return undefined;\n");
    output.push_str("  }\n");
    if model.fields.is_empty() && model.sourced_fields.is_empty() {
        output.push_str("  return {};\n");
    } else {
        output.push_str("  return {\n");
        for field in &model.fields {
            output.push_str("    ");
            output.push_str(&field.proto_name);
            output.push_str(": ");
            output.push_str(&field.to_proto_expr);
            output.push_str(",\n");
        }
        for field in &model.sourced_fields {
            output.push_str("    ");
            output.push_str(&field.name);
            output.push_str(": ");
            output.push_str(&field.to_proto_expr);
            output.push_str(",\n");
        }
        output.push_str("  };\n");
    }
    output.push_str("}\n");
}

fn render_model_from_proto_function(
    output: &mut String,
    model: &RenderedModel,
    function_name: &str,
) {
    if model.type_parameters.is_empty() {
        output.push_str("export function ");
        output.push_str(function_name);
        output.push_str("(\n");
        output.push_str("  proto: ");
        output.push_str(&model.proto_ref);
        output.push_str(" | null | undefined,\n");
    } else {
        render_named_generic_function_start(
            output,
            &format!("export function {function_name}"),
            &model.type_parameters,
            0,
        );
        output.push_str("  proto: ");
        output.push_str(&model.proto_ref);
        output.push_str(" | null | undefined,\n");
    }
    output.push_str("): ");
    output.push_str(&generic_model_annotation(
        &model.name,
        &model.type_parameters,
    ));
    output.push_str(" | undefined {\n");
    output.push_str("  if (proto == null) {\n");
    output.push_str("    return undefined;\n");
    output.push_str("  }\n");
    if model.fields.is_empty() {
        output.push_str("  return {};\n");
    } else {
        output.push_str("  return {\n");
        for field in &model.fields {
            if field.flattened_fields.is_empty() {
                output.push_str("    ");
                output.push_str(&field.name);
                output.push_str(": ");
                output.push_str(&field.from_proto_expr);
                output.push_str(",\n");
            } else {
                for flattened_field in &field.flattened_fields {
                    output.push_str("    ");
                    output.push_str(&flattened_field.name);
                    output.push_str(": ");
                    output.push_str(&flattened_field.from_proto_expr);
                    output.push_str(",\n");
                }
            }
        }
        output.push_str("  };\n");
    }
    output.push_str("}\n");
}

fn render_model(output: &mut String, model: &RenderedModel) {
    let doc_tags = experimental_doc_tag(model.experimental);
    if !model_uses_invocation_composition(model) {
        if model.fields.is_empty() {
            render_typescript_doc_comment(output, "", None, &doc_tags);
            output.push_str("export type ");
            output.push_str(&model.name);
            output.push_str(" = Record<string, never>;\n\n");
        } else {
            render_typescript_doc_comment(output, "", None, &doc_tags);
            output.push_str("export interface ");
            output.push_str(&model.name);
            output.push_str(&render_type_parameter_list(&model.type_parameters));
            output.push_str(" {\n");
            for field in &model.fields {
                if field.flattened_fields.is_empty() {
                    render_invocation_model_base_property(output, model, field);
                } else {
                    render_flattened_type_properties(output, "  ", &field.flattened_fields);
                }
            }
            output.push_str("}\n\n");
        }
    } else {
        render_invocation_model_replace_helper(output, model);
        render_typescript_doc_comment(output, "", None, &doc_tags);
        output.push_str("export type ");
        output.push_str(&model.name);
        output.push_str(&render_type_parameter_list(&model.type_parameters));
        output.push_str(" = ");
        output.push_str(&invocation_model_replace_type_name(&model.name));
        output.push('<');
        render_invocation_model_base_type(output, model);
        output.push_str(", ");
        render_function_model_invocations(output, model);
        output.push_str(">;\n\n");
    }
    let mut wrote_conversion = false;
    if model.capabilities.from_proto {
        render_model_from_proto_function(
            output,
            model,
            &model_from_proto_function_name(&model.name),
        );
        wrote_conversion = true;
    }
    if model.capabilities.to_proto {
        if wrote_conversion {
            output.push('\n');
        }
        render_model_to_proto_function(output, model, &model_to_proto_function_name(&model.name));
    }
}

fn render_service_definition(output: &mut String, service: &RenderedService<'_>) {
    let service_doc_tags = experimental_doc_tag(service.experimental);
    render_typescript_doc_comment(output, "", None, &service_doc_tags);
    output.push_str("export const ");
    output.push_str(service.name);
    output.push_str(" = nexus.service('");
    output.push_str(service.wire_name);
    output.push_str("', {\n");
    for operation in &service.operations {
        let operation_doc_tags = experimental_doc_tag(operation.experimental);
        render_typescript_doc_comment(output, "  ", None, &operation_doc_tags);
        output.push_str("  ");
        output.push_str(&operation.attr_name);
        output.push_str(": nexus.operation<\n");
        output.push_str("    ");
        output.push_str(&operation.input_proto_ref);
        output.push_str(",\n");
        output.push_str("    ");
        output.push_str(&operation.output_proto_ref);
        output.push('\n');
        output.push_str("  >({ name: ");
        output.push_str(&typescript_string_literal(operation.wire_name));
        output.push_str(" }),\n");
    }
    output.push_str("});\n\n");
}

fn typescript_resource_type_id(
    service: &RenderedService<'_>,
    resource: &PlannedResource,
) -> String {
    format!("{}::resource::{}", service.name, resource.name)
}

fn render_resource(output: &mut String, resource: &PlannedResource, service: &RenderedService<'_>) {
    output.push_str("export class ");
    output.push_str(&resource.type_name);
    output.push_str(" {\n");
    output.push_str("  public constructor(\n");
    for field in &resource.fields {
        output.push_str("    public readonly ");
        output.push_str(&typescript_generated_field_name(&field.name));
        output.push_str(": ");
        output.push_str(&typescript_resource_field_annotation(
            &field.kind,
            field.optional,
            field.function.as_ref(),
        ));
        output.push_str(",\n");
    }
    output.push_str("  ) {}\n");

    if resource.methods.is_empty() {
        output.push_str("}\n");
        return;
    }

    for method in &resource.methods {
        let result_annotation = typescript_resource_method_result_annotation(method);
        output.push('\n');
        output.push_str("  public async ");
        output.push_str(&typescript_generated_field_name(&method.name));
        output.push_str("(\n");
        for param in &method.params {
            output.push_str("    ");
            output.push_str(&typescript_generated_field_name(&param.name));
            if param.optional {
                output.push('?');
            }
            output.push_str(": ");
            output.push_str(&typescript_resource_field_annotation(
                &param.kind,
                false,
                param.function.as_ref(),
            ));
            output.push_str(",\n");
        }
        output.push_str("  ): Promise<");
        output.push_str(&result_annotation);
        output.push_str("> {\n");
        match &method.binding {
            PlannedResourceMethodBindingSpec::Operation {
                operation_name,
                request_plan,
                direct_return,
            } => {
                let operation = service
                    .operations
                    .iter()
                    .find(|operation| operation.name == operation_name)
                    .expect("bound operation should exist on the resource service");
                let branchable_params =
                    branchable_request_method_params(method, request_plan, operation);
                if *direct_return {
                    output.push_str(&render_resource_method_operation_body(
                        4,
                        &operation.attr_name,
                        &result_annotation,
                        request_plan,
                        &branchable_params,
                        true,
                    ));
                } else {
                    output.push_str(&render_resource_method_operation_body(
                        4,
                        &operation.attr_name,
                        &result_annotation,
                        request_plan,
                        &branchable_params,
                        false,
                    ));
                }
            }
            PlannedResourceMethodBindingSpec::Stub => {
                output.push_str("    throw new Error(");
                output.push_str(&typescript_string_literal(&format!(
                    "{}.{} is not yet implemented",
                    resource.name,
                    typescript_generated_field_name(&method.name)
                )));
                output.push_str(");\n");
            }
        }
        output.push_str("  }\n");
    }

    output.push_str("}\n");
}

fn typescript_resource_method_result_annotation(method: &PlannedResourceMethod) -> String {
    let Some(result) = &method.result else {
        return "void".to_string();
    };
    let annotation = match &result.kind {
        PlannedResourceMethodResultKind::Resource { type_name } => type_name.clone(),
        PlannedResourceMethodResultKind::Value(kind) => {
            typescript_resource_field_annotation(kind, result.optional, None)
        }
    };
    if result.optional && !annotation.contains(" | undefined") {
        format!("{annotation} | undefined")
    } else {
        annotation
    }
}

fn typescript_resource_field_annotation(
    kind: &PlannedFieldKind,
    optional: bool,
    function: Option<&FunctionFieldSpec>,
) -> String {
    let base = if let Some(function) = function {
        typescript_function_value_annotation(
            function
                .alternate_type
                .as_ref()
                .map(typescript_authored_type_annotation)
                .as_deref(),
            &function_constraint(function),
        )
    } else {
        match kind {
            PlannedFieldKind::Singular(value) => typescript_resource_value_annotation(value),
            PlannedFieldKind::Repeated(value) => {
                format!("{}[]", typescript_resource_value_annotation(value))
            }
            PlannedFieldKind::Map { key, value } => format!(
                "Record<{}, {}>",
                typescript_resource_value_annotation(key),
                typescript_resource_value_annotation(value)
            ),
        }
    };
    if optional {
        format!("{base} | undefined")
    } else {
        base
    }
}

fn typescript_resource_value_annotation(value: &PlannedValueType) -> String {
    match value {
        PlannedValueType::Scalar(PlannedScalarType::Float | PlannedScalarType::Int32) => {
            "number".to_string()
        }
        PlannedValueType::Scalar(PlannedScalarType::Int64) => "Long".to_string(),
        PlannedValueType::Scalar(PlannedScalarType::Bool) => "boolean".to_string(),
        PlannedValueType::Scalar(PlannedScalarType::String) => "string".to_string(),
        PlannedValueType::Scalar(PlannedScalarType::Bytes) => "Uint8Array".to_string(),
        PlannedValueType::Enum(enum_type) => enum_type
            .replacement
            .as_ref()
            .and_then(typescript_replacement_type_name)
            .or_else(|| enum_type.name.clone())
            .unwrap_or_else(|| "number".to_string()),
        PlannedValueType::Flags(flags_type) => flags_type.name.clone(),
        PlannedValueType::Variant(variant_type) => variant_type.name.clone(),
        PlannedValueType::Message(message_type) => message_type
            .replacement
            .as_ref()
            .and_then(typescript_replacement_type_name)
            .unwrap_or_else(|| message_type.model_name.clone()),
        PlannedValueType::Tuple(items) => format!(
            "[{}]",
            items
                .iter()
                .map(typescript_resource_value_annotation)
                .collect::<Vec<_>>()
                .join(", ")
        ),
        PlannedValueType::Result { ok, err } => typescript_result_annotation(
            ok.as_ref()
                .map(|ok| typescript_resource_value_annotation(ok).to_string()),
            err.as_ref()
                .map(|err| typescript_resource_value_annotation(err).to_string()),
        ),
        PlannedValueType::External {
            type_name,
            fallback,
        } => type_name
            .for_language(Language::TypeScript)
            .map(str::to_string)
            .unwrap_or_else(|| typescript_resource_value_annotation(fallback)),
        PlannedValueType::Unknown => "unknown".to_string(),
    }
}

fn typescript_resource_field_requirements(kind: &PlannedFieldKind) -> TypeScriptRequirements {
    let mut requirements = TypeScriptRequirements::default();
    match kind {
        PlannedFieldKind::Singular(value) | PlannedFieldKind::Repeated(value) => {
            collect_typescript_value_requirements(value, &mut requirements);
        }
        PlannedFieldKind::Map { key, value } => {
            collect_typescript_value_requirements(key, &mut requirements);
            collect_typescript_value_requirements(value, &mut requirements);
        }
    }
    requirements
}

fn collect_typescript_value_requirements(
    value: &PlannedValueType,
    requirements: &mut TypeScriptRequirements,
) {
    match value {
        PlannedValueType::Scalar(PlannedScalarType::Int64) => {
            requirements.long = true;
        }
        PlannedValueType::Tuple(items) => {
            for item in items {
                collect_typescript_value_requirements(item, requirements);
            }
        }
        PlannedValueType::Result { ok, err } => {
            if let Some(ok) = ok {
                collect_typescript_value_requirements(ok, requirements);
            }
            if let Some(err) = err {
                collect_typescript_value_requirements(err, requirements);
            }
        }
        PlannedValueType::External {
            type_name,
            fallback,
        } => {
            if type_name
                .for_language(Language::TypeScript)
                .is_some_and(|type_name| type_name.contains("Long"))
            {
                requirements.long = true;
            }
            collect_typescript_value_requirements(fallback, requirements);
        }
        _ => {}
    }
}

fn branchable_request_method_params(
    method: &PlannedResourceMethod,
    request_plan: &RequestPlan,
    operation: &RenderedOperation<'_>,
) -> Vec<String> {
    if operation.input_type_parameters.is_empty() {
        return Vec::new();
    }
    let mut used_params = BTreeSet::new();
    collect_request_plan_method_params(request_plan, &mut used_params);
    method
        .params
        .iter()
        .filter(|param| {
            used_params.contains(&param.name)
                && typescript_resource_field_annotation(
                    &param.kind,
                    param.optional,
                    param.function.as_ref(),
                )
                .trim_start()
                .starts_with("string | ")
        })
        .map(|param| param.name.clone())
        .collect()
}

fn collect_request_plan_method_params(plan: &RequestPlan, params: &mut BTreeSet<String>) {
    match plan {
        RequestPlan::Source(RequestPlanSource::MethodParam(name)) => {
            params.insert(name.clone());
        }
        RequestPlan::Source(RequestPlanSource::ResourceField(_)) => {}
        RequestPlan::Construct { fields, .. } => {
            for field in fields {
                collect_request_plan_method_params(&field.value, params);
            }
        }
    }
}

fn render_resource_method_operation_body(
    indent: usize,
    operation_attr_name: &str,
    result_annotation: &str,
    request_plan: &RequestPlan,
    branchable_params: &[String],
    direct_return: bool,
) -> String {
    let indent_str = " ".repeat(indent);
    if let Some((param_name, remaining)) = branchable_params.split_first() {
        let param_ident = typescript_generated_field_name(param_name);
        let mut output = String::new();
        output.push_str(&indent_str);
        output.push_str("if (typeof ");
        output.push_str(&param_ident);
        output.push_str(" === \"string\") {\n");
        output.push_str(&render_resource_method_operation_body(
            indent + 2,
            operation_attr_name,
            result_annotation,
            request_plan,
            remaining,
            direct_return,
        ));
        output.push_str(&indent_str);
        output.push_str("} else {\n");
        output.push_str(&render_resource_method_operation_body(
            indent + 2,
            operation_attr_name,
            result_annotation,
            request_plan,
            remaining,
            direct_return,
        ));
        output.push_str(&indent_str);
        output.push_str("}\n");
        return output;
    }

    let request_expr = render_request_plan_typescript(request_plan);
    let mut output = String::new();
    if direct_return {
        output.push_str(&indent_str);
        if result_annotation == "void" {
            output.push_str("await ");
        } else {
            output.push_str("return await ");
        }
        output.push_str(operation_attr_name);
        output.push('(');
        output.push_str(&request_expr);
        output.push_str(");\n");
    } else {
        output.push_str(&indent_str);
        output.push_str("const handle = await ");
        output.push_str(operation_attr_name);
        output.push('(');
        output.push_str(&request_expr);
        output.push_str(");\n");
        output.push_str(&indent_str);
        if result_annotation == "void" {
            output.push_str("await handle.result();\n");
        } else {
            output.push_str("return await handle.result();\n");
        }
    }
    output
}

fn render_request_plan_typescript(plan: &RequestPlan) -> String {
    render_request_plan(
        plan,
        typescript_generated_field_name,
        |name, value| format!("{name}: {value}"),
        |_message_name, fields| {
            if fields.is_empty() {
                "{}".to_string()
            } else {
                format!("{{ {} }}", fields.join(", "))
            }
        },
        |name| format!("this.{}", typescript_generated_field_name(name)),
        typescript_generated_field_name,
    )
}

fn resource_return_binding_expr_typescript(
    binding: &PlannedOperationResourceFieldBinding,
) -> String {
    match &binding.source {
        ResolvedResourceBindingSource::RequestField {
            field_name,
            proto_field_name,
            hidden,
        } => {
            if *hidden {
                let expr = format!(
                    "requestProto.{}",
                    typescript_generated_field_name(proto_field_name)
                );
                if binding.optional {
                    format!("{expr} ?? undefined")
                } else {
                    format!(
                        "requiredField({expr}, {}, {})",
                        typescript_string_literal("resource"),
                        typescript_string_literal(&typescript_generated_field_name(
                            &binding.field_name
                        )),
                    )
                }
            } else {
                format!("request.{}", typescript_generated_field_name(field_name))
            }
        }
        ResolvedResourceBindingSource::ResultField {
            proto_field_name, ..
        } => {
            let expr = format!(
                "result.{}",
                typescript_generated_field_name(proto_field_name)
            );
            if binding.optional {
                format!("{expr} ?? undefined")
            } else {
                format!(
                    "requiredField({expr}, {}, {})",
                    typescript_string_literal("resource"),
                    typescript_string_literal(&typescript_generated_field_name(
                        &binding.field_name
                    )),
                )
            }
        }
    }
}

fn render_operation_function(
    output: &mut String,
    service: &RenderedService<'_>,
    operation: &RenderedOperation<'_>,
) {
    let returns_direct = operation.output_transform_expr.is_some()
        || operation.output_resource_return.is_some()
        || operation.output_direct_result;
    let mut doc_tags = vec![(
        "@param request -".to_string(),
        "Request for the operation.".to_string(),
    )];
    if let Some(return_doc) = &operation.return_doc {
        doc_tags.push(("@returns".to_string(), return_doc.clone()));
    }
    if operation.experimental {
        doc_tags.push((
            "@experimental".to_string(),
            EXPERIMENTAL_WARNING.to_string(),
        ));
    }
    render_typescript_doc_comment(output, "", operation.doc.as_deref(), &doc_tags);
    if operation.input_type_parameters.is_empty() {
        output.push_str("export async function ");
        output.push_str(&operation.attr_name);
        output.push_str("(\n");
    } else {
        render_named_generic_function_start(
            output,
            &format!("export async function {}", operation.attr_name),
            &operation.input_type_parameters,
            0,
        );
    }
    output.push_str("  request: ");
    output.push_str(&operation.input_annotation);
    output.push_str(",\n");
    if returns_direct {
        output.push_str("): Promise<");
        output.push_str(&operation.output_annotation);
        output.push_str("> {\n");
    } else {
        output.push_str("): Promise<workflow.NexusOperationHandle<");
        output.push_str(&operation.output_annotation);
        output.push_str(">> {\n");
        output.push_str("  const client = workflow.createNexusServiceClient({\n");
        output.push_str("    service: ");
        output.push_str(service.name);
        output.push_str(",\n");
        output.push_str("    endpoint: ");
        output.push_str(&typescript_string_literal(&service.endpoint));
        output.push_str(",\n");
        output.push_str("  });\n");
        output.push_str("  return await client.startOperation(\n");
        output.push_str("    ");
        output.push_str(service.name);
        output.push_str(".operations.");
        output.push_str(&operation.attr_name);
        output.push_str(",\n");
        output.push_str("    ");
        output.push_str(&typescript_operation_input_expr(operation));
        output.push_str(",\n");
        output.push_str("  );\n");
        output.push_str("}\n");
        return;
    }
    output.push_str("  const client = workflow.createNexusServiceClient({\n");
    output.push_str("    service: ");
    output.push_str(service.name);
    output.push_str(",\n");
    output.push_str("    endpoint: ");
    output.push_str(&typescript_string_literal(&service.endpoint));
    output.push_str(",\n");
    output.push_str("  });\n");
    output.push_str("  const requestProto = ");
    output.push_str(&typescript_operation_input_expr(operation));
    output.push_str(";\n");
    output.push_str("  const handle = await client.startOperation(\n");
    output.push_str("    ");
    output.push_str(service.name);
    output.push_str(".operations.");
    output.push_str(&operation.attr_name);
    output.push_str(",\n");
    output.push_str("    requestProto,\n");
    output.push_str("  );\n");
    if let Some(transform_expr) = &operation.output_transform_expr {
        output.push_str("  const result = await handle.result();\n");
        output.push_str("  return ");
        output.push_str(transform_expr);
        output.push_str(";\n");
    } else if let Some(resource_return) = &operation.output_resource_return {
        output.push_str("  const result = await handle.result();\n");
        output.push_str("  return new ");
        output.push_str(&resource_return.resource_type_name);
        output.push_str("(\n");
        for binding in &resource_return.bindings {
            output.push_str("    ");
            output.push_str(&resource_return_binding_expr_typescript(binding));
            output.push_str(",\n");
        }
        output.push_str("  );\n");
    } else if operation.output_direct_result {
        output.push_str("  return await handle.result();\n");
    }
    output.push_str("}\n");
}

fn typescript_operation_input_expr(operation: &RenderedOperation<'_>) -> String {
    if let Some(type_id) = &operation.input_nexus_type_id {
        format!(
            "nexusValue({}, {})",
            typescript_string_literal(type_id),
            operation.input_to_proto_expr
        )
    } else {
        operation.input_to_proto_expr.clone()
    }
}

fn required_field_expr(value_expr: &str, owner_name: &str, field_name: &str) -> String {
    format!(
        "requiredField({value_expr}, {}, {})",
        typescript_string_literal(owner_name),
        typescript_string_literal(field_name)
    )
}

pub(crate) fn field_name(field: &FieldDescriptorProto, explicit_name: Option<&str>) -> String {
    match explicit_name {
        Some(name) => typescript_generated_field_name(name),
        None => {
            let name = field
                .json_name
                .as_deref()
                .or_else(|| field.name.as_deref())
                .expect("descriptor fields should be named");
            typescript_ident(name)
        }
    }
}

fn typescript_ident(name: &str) -> String {
    if is_typescript_keyword(name) {
        format!("{name}_")
    } else {
        name.to_string()
    }
}

fn typescript_generated_field_name(name: &str) -> String {
    typescript_ident(&name.to_lower_camel_case())
}

fn typescript_string_literal(value: &str) -> String {
    format!("{value:?}")
}

fn is_typescript_keyword(name: &str) -> bool {
    matches!(
        name,
        "break"
            | "case"
            | "catch"
            | "class"
            | "const"
            | "continue"
            | "debugger"
            | "default"
            | "delete"
            | "do"
            | "else"
            | "enum"
            | "export"
            | "extends"
            | "false"
            | "finally"
            | "for"
            | "function"
            | "if"
            | "import"
            | "in"
            | "instanceof"
            | "new"
            | "null"
            | "return"
            | "super"
            | "switch"
            | "this"
            | "throw"
            | "true"
            | "try"
            | "typeof"
            | "var"
            | "void"
            | "while"
            | "with"
            | "yield"
            | "as"
            | "implements"
            | "interface"
            | "let"
            | "package"
            | "private"
            | "protected"
            | "public"
            | "static"
    )
}

#[cfg(test)]
mod tests {
    use std::collections::BTreeMap;
    use std::fs;
    use std::path::Path;
    use std::path::PathBuf;
    use std::process::Command;
    use std::time::{SystemTime, UNIX_EPOCH};

    use crate::SupportFiles;
    use crate::descriptors::DescriptorIndex;
    use crate::generator::{GeneratedOutputLayout, generate_files, generate_source};
    use crate::language::Language;
    use crate::spec::ApiSpec;

    fn sample_input_path(root: &std::path::Path) -> PathBuf {
        root.join("examples/inputs/workflow-service.wit")
    }

    fn type_roundtrip_input_path(root: &std::path::Path) -> PathBuf {
        root.join("examples/inputs/type-roundtrip.wit")
    }

    fn linked_inputs_path(root: &std::path::Path) -> PathBuf {
        root.join("examples/inputs/deps")
    }

    fn example_input_paths(root: &std::path::Path, input_path: PathBuf) -> Vec<PathBuf> {
        vec![input_path, linked_inputs_path(root)]
    }

    fn sample_typescript_output_path(root: &std::path::Path) -> PathBuf {
        root.join("examples/typescript/workflow-service")
    }

    fn sample_support_files(root: &std::path::Path) -> SupportFiles {
        SupportFiles {
            typescript: Some(
                fs::read_to_string(root.join(
                    "examples/inputs/deps/nexus-temporal-types/typescript/model_overrides.ts",
                ))
                .unwrap(),
            ),
            ..SupportFiles::default()
        }
    }

    fn ensure_typescript_dependencies(root: &std::path::Path) {
        let example_dir = root.join("examples/typescript");
        if example_dir.join("node_modules").exists() {
            return;
        }

        let status = Command::new("npm")
            .current_dir(&example_dir)
            .args(["install", "--no-fund", "--no-audit"])
            .status()
            .unwrap();
        assert!(status.success());
    }

    fn read_typescript_output_files(dir: &Path) -> BTreeMap<PathBuf, String> {
        fn visit(root: &Path, dir: &Path, files: &mut BTreeMap<PathBuf, String>) {
            let mut entries = fs::read_dir(dir)
                .unwrap()
                .map(|entry| entry.unwrap().path())
                .collect::<Vec<_>>();
            entries.sort();
            for path in entries {
                if path.is_dir() {
                    visit(root, &path, files);
                } else if path.extension().and_then(|extension| extension.to_str()) == Some("ts") {
                    files.insert(
                        path.strip_prefix(root).unwrap().to_path_buf(),
                        fs::read_to_string(&path).unwrap(),
                    );
                }
            }
        }

        let mut files = BTreeMap::new();
        visit(dir, dir, &mut files);
        files
    }

    #[test]
    fn renders_sample_output() {
        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let spec = ApiSpec::load_for_language_with_inputs(
            Language::TypeScript,
            &example_input_paths(&root, sample_input_path(&root)),
        )
        .unwrap();
        let descriptors =
            DescriptorIndex::load(&root.join("examples/descriptors/temporal_api.bin")).unwrap();
        let support = sample_support_files(&root);
        let generated =
            generate_files(Language::TypeScript, &spec, &descriptors, &support).unwrap();
        assert_eq!(generated.layout, GeneratedOutputLayout::Directory);

        ensure_typescript_dependencies(&root);
        let temp_dir = std::env::temp_dir().join(format!(
            "nex-gen-typescript-snapshot-{}",
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_nanos()
        ));
        fs::create_dir_all(&temp_dir).unwrap();
        for (relative_path, contents) in &generated.files {
            let path = temp_dir.join(relative_path);
            if let Some(parent) = path.parent() {
                fs::create_dir_all(parent).unwrap();
            }
            fs::write(path, contents).unwrap();
        }
        let status = Command::new("npm")
            .current_dir(root.join("examples/typescript"))
            .args([
                "exec",
                "--",
                "prettier",
                "--write",
                "--print-width",
                "88",
                temp_dir.to_str().unwrap(),
            ])
            .status()
            .unwrap();
        assert!(status.success());

        let output = read_typescript_output_files(&temp_dir);
        let expected = read_typescript_output_files(&sample_typescript_output_path(&root));
        assert_eq!(output, expected);
        let _ = fs::remove_dir_all(temp_dir);
    }

    #[test]
    fn renders_required_fields_and_custom_message_types() {
        let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let spec = ApiSpec::load_for_language_with_inputs(
            Language::TypeScript,
            &example_input_paths(&root, sample_input_path(&root)),
        )
        .unwrap();
        let descriptors =
            DescriptorIndex::load(&root.join("examples/descriptors/temporal_api.bin")).unwrap();
        let support = sample_support_files(&root);
        let output = generate_source(Language::TypeScript, &spec, &descriptors, &support).unwrap();

        assert!(output.contains("### support.ts"));
        assert!(output.contains("### index.ts"));
        let index_output = output
            .split("### index.ts")
            .nth(1)
            .expect("rendered output should include index.ts");
        assert!(!index_output.contains("export * from './support.ts';"));
        assert!(output.contains("export function retryPolicyFromProto("));
        assert!(!index_output.contains("export const SignalWithStartWorkflowRequest = {"));
        assert!(!index_output.contains("const UserMetadata = {"));
        assert!(index_output.contains("function userMetadataFromProto("));
        assert!(index_output.contains("function signalWithStartWorkflowRequestToProto<"));
        assert!(!output.contains("type _RequestWithFunctionField<"));
        assert!(!output.contains("type _RequestWithArgumentsField<"));
        assert!(!output.contains("type SignalWithStartWorkflowRequestBase = {"));
        assert!(output.contains("export type SignalWithStartWorkflowRequest<"));
        assert!(output.contains("export type ReplaceSignalWithStartWorkflowRequest<Base, New>"));
        assert!(output.contains(
            "WorkflowFn extends (...args: any[]) => Promise<any> = (...args: any[]) => Promise<any>,"
        ));
        assert!(output.contains(
            "SignalValue extends workflow.SignalDefinition<any[]> = workflow.SignalDefinition<any[]>"
        ));
        assert!(output.contains("> = ReplaceSignalWithStartWorkflowRequest<"));
        assert!(output.contains(
            "SignalValue extends workflow.SignalDefinition<infer Args, any> ? Args : never"
        ));
        assert!(output.contains("SignalArgs extends any[] = SignalValue extends"));
        assert!(output.contains("signalArgs: SignalArgs | Readonly<SignalArgs>;"));
        assert!(output.contains("signalArgs?: SignalArgs | Readonly<SignalArgs>;"));
        assert!(output.contains(
            "Workflow type name or workflow function identifying the workflow to start."
        ));
        assert!(output.contains("workflow: string;"));
        assert!(output.contains("Arguments for workflow."));
        assert!(output.contains("args?: ReadonlyArray<unknown>;"));
        assert!(output.contains("Arguments for signal."));
        assert!(output.contains("signalArgs?: ReadonlyArray<unknown>;"));
        assert!(output.contains("id: string;"));
        assert!(output.contains("taskQueue: string;"));
        assert!(output.contains("runTimeout?: common.Duration;"));
        assert!(output.contains("idReusePolicy?: common.WorkflowIdReusePolicy;"));
        assert!(output.contains("common.WorkflowIdReusePolicy.ALLOW_DUPLICATE"));
        assert!(output.contains("idConflictPolicy?: common.WorkflowIdConflictPolicy;"));
        assert!(output.contains("workflowIdConflictPolicy:"));
        assert!(output.contains("model.idConflictPolicy == null"));
        assert!(output.contains("workflowIdConflictPolicyToProto(model.idConflictPolicy)"));
        assert!(!output.contains("identity?: string;"));
        assert!(output.contains("memo?: Record<string, unknown>;"));
        assert!(output.contains("searchAttributes?: common.TypedSearchAttributes;"));
        assert!(!output.contains("common.TypedSearchAttributes | common.SearchAttributes"));
        assert!(output.contains("versioningOverride?: common.VersioningOverride;"));
        assert!(output.contains("priority?: common.Priority;"));
        assert!(output.contains("signal: string;"));
        assert!(output.contains("staticSummary?: string;"));
        assert!(output.contains("staticDetails?: string;"));
        assert!(!output.contains("userMetadata?: UserMetadata;"));
        assert!(!output.contains("namespace?: string;"));
        assert!(output.contains("namespace: workflowNamespace(),"));
        assert!(output.contains("workflowType: workflowTypeToProto("));
        assert!(output.contains("workflowFunctionName("));
        assert!(output.contains("input: requestArgsToPayloads(model.args),"));
        assert!(output.contains("signalInput: requestArgsToPayloads(model.signalArgs),"));
        assert!(!output.contains("_RequestArgsToPayloads"));
        assert!(output.contains("signalName: signalFunctionToProto("));
        assert!(!output.contains("signalName: ((value) =>"));
        assert!(output.contains("workflowType: workflowTypeToProto("));
        assert!(output.contains("taskQueue: taskQueueToProto("));
        assert!(output.contains(
            "workflowRunTimeout: model.runTimeout == null ? undefined : durationToProto(model.runTimeout),"
        ));
        assert!(output.contains("memo: model.memo == null ? undefined : memoToProto(model.memo),"));
        assert!(output.contains(
            "searchAttributes: model.searchAttributes == null ? undefined : searchAttributesToProto(model.searchAttributes),"
        ));
        assert!(output.contains(
            "priority: model.priority == null ? undefined : priorityToProto(model.priority),"
        ));
        assert!(output.contains("model.staticSummary == null && model.staticDetails == null"));
        assert!(output.contains("summary: model.staticSummary == null"));
        assert!(output.contains("configuredPayloadConverter().toPayload(model.staticSummary)"));
        assert!(output.contains("common.toPayloads(configuredPayloadConverter(), ...args)"));
        assert!(!output.contains("common.defaultPayloadConverter"));
        assert!(!output.contains("payloadToProto(payload: unknown"));
        assert!(!output.contains("function isPayload("));
        assert!(output.contains(
            "versioningOverride: model.versioningOverride == null ? undefined : versioningOverrideToProto(model.versioningOverride),"
        ));
        assert!(output.contains("export function taskQueueFromProto("));
        assert!(output.contains("export function taskQueueToProto("));
        assert!(output.contains(
            "): temporal.api.workflowservice.v1.ISignalWithStartWorkflowExecutionRequest | undefined {"
        ));
        assert!(!output.contains("header?:"));
        assert!(!output.contains("export interface Header"));
        assert!(!output.contains("export interface WorkflowType"));
        assert!(!output.contains("export interface TaskQueue"));
        assert!(!output.contains("export interface Duration"));
        assert!(!output.contains("export interface Memo"));
        assert!(!output.contains("export interface SearchAttributes"));
        assert!(!output.contains("export interface Priority"));
        assert!(!output.contains("export interface VersioningOverride"));
        assert!(!output.contains("SignalWithStartWorkflowRequest = {\n  fromProto("));
        assert!(!output.contains("SignalWithStartWorkflowRequest.fromProto"));
        assert!(!output.contains("export interface SignalWithStartWorkflowRequest {"));
        assert!(!output.contains("export interface RetryPolicy"));
        assert!(!output.contains("export enum WorkflowIdReusePolicy"));
        assert!(!output.contains("export enum WorkflowIdConflictPolicy"));
        assert!(!output.contains("signalWithStartWorkflowExecution("));
        assert!(output.contains("export async function signalWithStartWorkflow<"));
        assert!(
            output.contains("request: SignalWithStartWorkflowRequest<WorkflowFn, SignalValue>,")
        );
        assert!(output.contains("const client = workflow.createNexusServiceClient({"));
        assert!(!output.contains("export class WorkflowServiceClient"));
        assert!(!output.contains("from './model_overrides.ts'"));
        assert!(!output.contains("// Included from support.$typescript"));

        let type_roundtrip_spec = ApiSpec::load_for_language_with_inputs(
            Language::TypeScript,
            &example_input_paths(&root, type_roundtrip_input_path(&root)),
        )
        .unwrap();
        let type_roundtrip_output = generate_source(
            Language::TypeScript,
            &type_roundtrip_spec,
            &descriptors,
            &support,
        )
        .unwrap();
        assert!(type_roundtrip_output.contains("retryPolicy: common.RetryPolicy;"));
        assert!(type_roundtrip_output.contains("requiredField(model.retryPolicy"));
        assert!(type_roundtrip_output.contains("request: common.RetryPolicy"));
        assert!(type_roundtrip_output.contains("retryPolicyToProto(request)"));
    }

    #[test]
    fn renders_typescript_enum_overrides() {
        let wit = r#"
package temporal:nexus@1.0.0;

world system {
  export workflow-service;
}

/// @nexus.endpoint "__temporal_system"
interface workflow-service {
  use nexus:temporal-types/model@1.0.0.{duration, placeholder, workflow-id-reuse-policy};

  /// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionRequest"
  record signal-with-start-workflow-request {
    workflow-id: string,
    workflow-id-reuse-policy: option<workflow-id-reuse-policy>,
    /// @nexus.omit
    namespace: placeholder,
    /// @nexus.omit
    workflow-type: placeholder,
    /// @nexus.omit
    task-queue: placeholder,
    /// @nexus.omit
    input: placeholder,
    /// @nexus.omit
    workflow-execution-timeout: placeholder,
    /// @nexus.omit
    workflow-run-timeout: placeholder,
    /// @nexus.omit
    workflow-task-timeout: placeholder,
    /// @nexus.omit
    identity: placeholder,
    /// @nexus.omit
    request-id: placeholder,
    /// @nexus.omit
    workflow-id-conflict-policy: placeholder,
    /// @nexus.omit
    signal-name: placeholder,
    /// @nexus.omit
    signal-input: placeholder,
    /// @nexus.omit
    control: placeholder,
    /// @nexus.omit
    retry-policy: placeholder,
    /// @nexus.omit
    cron-schedule: placeholder,
    /// @nexus.omit
    memo: placeholder,
    /// @nexus.omit
    search-attributes: placeholder,
    /// @nexus.omit
    header: placeholder,
    workflow-start-delay: option<duration>,
    /// @nexus.omit
    user-metadata: placeholder,
    /// @nexus.omit
    links: placeholder,
    /// @nexus.omit
    versioning-override: placeholder,
    /// @nexus.omit
    priority: placeholder,
    /// @nexus.omit
    time-skipping-config: placeholder,
  }

  /// @nexus.proto "temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse"
  record signal-with-start-workflow-response {
    run-id: option<string>,
    started: option<bool>,
    /// @nexus.omit
    signal-link: placeholder,
  }

  signal-with-start-workflow-execution: func(
    request: signal-with-start-workflow-request,
  ) -> signal-with-start-workflow-response;
}
"#;
        let spec = ApiSpec::parse_for_language_with_inputs(
            Language::TypeScript,
            wit,
            PathBuf::from("inline.wit"),
            &[linked_inputs_path(&PathBuf::from(env!(
                "CARGO_MANIFEST_DIR"
            )))],
        )
        .unwrap();
        let descriptors = DescriptorIndex::load(
            &PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .join("examples/descriptors/temporal_api.bin"),
        )
        .unwrap();
        let output = generate_source(
            Language::TypeScript,
            &spec,
            &descriptors,
            &SupportFiles::default(),
        )
        .unwrap();

        assert!(output.contains("workflowIdReusePolicy?: common.WorkflowIdReusePolicy;"));
        assert!(output.contains(
            "workflowIdReusePolicy: model.workflowIdReusePolicy == null ? undefined : workflowIdReusePolicyToProto(model.workflowIdReusePolicy)"
        ));
        assert!(!output.contains("export enum WorkflowIdReusePolicy"));
    }

    #[test]
    fn renders_experimental_annotations() {
        let wit = r#"
package temporal:nexus@1.0.0;

world system {
  export example-service;
}

/// @nexus.endpoint "example"
/// @nexus.experimental
interface example-service {
  /// @nexus.experimental
  record request {
    id: string,
  }

  /// @nexus.experimental
  record response {
    ok: bool,
  }

  /// @nexus.experimental
  /// @nexus.doc "Runs example."
  request-op: func(request: request) -> response;
}
"#;
        let spec =
            ApiSpec::parse_for_language(Language::TypeScript, wit, PathBuf::from("inline.wit"))
                .unwrap();
        let descriptors = DescriptorIndex::load(
            &PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .join("examples/descriptors/temporal_api.bin"),
        )
        .unwrap();
        let output = generate_source(
            Language::TypeScript,
            &spec,
            &descriptors,
            &SupportFiles::default(),
        )
        .unwrap();

        assert!(output.contains(
            "/**\n * @experimental This API is experimental and subject to change.\n */\nexport interface Request"
        ));
        assert!(output.contains(
            "/**\n * @experimental This API is experimental and subject to change.\n */\nexport const ExampleService"
        ));
        assert!(output.contains(
            "  /**\n   * @experimental This API is experimental and subject to change.\n   */\n  requestOp:"
        ));
        assert!(output.contains(
            " * Runs example.\n *\n * @param request - Request for the operation.\n * @experimental This API is experimental and subject to change."
        ));
    }

    #[test]
    fn rejects_missing_service_endpoint() {
        let wit = r#"
package temporal:nexus@1.0.0;

world system {
  export example-service;
}

interface example-service {
  use nexus:temporal-types/model@1.0.0.{retry-policy};

  example-operation: func(request: retry-policy) -> retry-policy;
}
"#;
        let spec = ApiSpec::parse_for_language_with_inputs(
            Language::TypeScript,
            wit,
            PathBuf::from("inline.wit"),
            &[linked_inputs_path(&PathBuf::from(env!(
                "CARGO_MANIFEST_DIR"
            )))],
        )
        .unwrap();
        let descriptors = DescriptorIndex::load(
            &PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .join("examples/descriptors/temporal_api.bin"),
        )
        .unwrap();

        let error = generate_source(
            Language::TypeScript,
            &spec,
            &descriptors,
            &SupportFiles::default(),
        )
        .unwrap_err();
        assert_eq!(
            error.to_string(),
            "service `ExampleService` is missing an endpoint"
        );
    }
}
