private String getEndpointParametersString(ServiceEndpointModel endpointModel) {
    List<String> parameterStrings = Lists.newArrayList();
    for (ServiceEndpointParameterModel parameterModel : endpointModel.parameters()) {
      if (parameterModel.headerParam() != null) {
        // continue, header params are implicit
        continue;
      }
      String optionalString = parameterModel.queryParam() != null ? "?" : "";
      parameterStrings.add(
          parameterModel.getParameterName()
              + optionalString
              + ": "
              + parameterModel.tsType().toString());
    }

    return Joiner.on(", ").join(parameterStrings);
  }
  public void emitTypescriptClass() {
    Set<String> endpointsToWarnAboutDuplicateNames = Sets.newHashSet();
    if (!this.settings.emitDuplicateJavaMethodNames()) {
      endpointsToWarnAboutDuplicateNames = getDuplicateEndpointNames();
    }
    writer.writeLine("");
    // Adding "Impl" ensures the class name is different from the impl name, which is a compilation
    // requirement.
    writer.writeLine(
        "export class "
            + model.name()
            + "Impl"
            + " implements "
            + settings.getSettings().addTypeNamePrefix
            + model.name()
            + " {");
    writer.increaseIndent();

    writer.writeLine("");
    writer.writeLine(
        String.format(
            "private httpApiBridge: %sHttpApiBridge;", settings.generatedInterfacePrefix()));
    writer.writeLine(
        String.format(
            "constructor(httpApiBridge: %sHttpApiBridge) {", settings.generatedInterfacePrefix()));
    writer.increaseIndent();
    writer.writeLine("this.httpApiBridge = httpApiBridge;");
    writer.decreaseIndent();
    writer.writeLine("}");

    for (InnerServiceModel innerServiceModel : model.innerServiceModels()) {
      if (model.innerServiceModels().size() > 1) {
        writer.writeLine("");
        writer.writeLine("// endpoints for service class: " + innerServiceModel.name());
      }
      for (ServiceEndpointModel endpointModel : innerServiceModel.endpointModels()) {
        if (endpointsToWarnAboutDuplicateNames.contains(endpointModel.endpointName())) {
          // don't output any duplicates
          continue;
        }
        writer.writeLine("");
        String line = "public " + endpointModel.endpointName() + "(";
        line += getEndpointParametersString(endpointModel);
        line += ") {";
        writer.writeLine(line);
        writer.increaseIndent();
        writer.writeLine(
            String.format(
                "var httpCallData = <%sHttpEndpointOptions> {",
                settings.generatedInterfacePrefix()));
        writer.increaseIndent();
        writer.writeLine(
            "serviceIdentifier: \""
                + Character.toLowerCase(model.name().charAt(0))
                + model.name().substring(1)
                + "\",");
        writer.writeLine(
            "endpointPath: \"" + getEndpointPathString(innerServiceModel, endpointModel) + "\",");
        writer.writeLine("endpointName: \"" + endpointModel.endpointName() + "\",");
        writer.writeLine("method: \"" + endpointModel.endpointMethodType() + "\",");
        writer.writeLine("requestMediaType: \"" + endpointModel.endpointRequestMediaType() + "\",");
        writer.writeLine(
            "responseMediaType: \""
                + optionalToString(endpointModel.endpointResponseMediaType())
                + "\",");
        List<String> requiredHeaders = Lists.newArrayList();
        List<String> pathArguments = Lists.newArrayList();
        List<String> queryArguments = Lists.newArrayList();
        String dataArgument = null;
        for (ServiceEndpointParameterModel parameterModel : endpointModel.parameters()) {
          if (parameterModel.headerParam() != null) {
            requiredHeaders.add("\"" + parameterModel.headerParam() + "\"");
          } else if (parameterModel.pathParam() != null) {
            pathArguments.add(parameterModel.getParameterName());
          } else if (parameterModel.queryParam() != null) {
            queryArguments.add(parameterModel.queryParam());
          } else {
            if (dataArgument != null) {
              throw new IllegalStateException(
                  "There should only be one data argument per endpoint. Found both"
                      + dataArgument
                      + " and "
                      + parameterModel.getParameterName());
            }
            dataArgument = parameterModel.getParameterName();
            boolean isEnum = false;
            if (parameterModel.javaType() instanceof Class<?>) {
              isEnum = ((Class<?>) parameterModel.javaType()).isEnum();
            }
            if (endpointModel.endpointRequestMediaType().equals(MediaType.APPLICATION_JSON)
                && (parameterModel.tsType().toString().equals("string") || isEnum)) {
              // strings (and enums, the wire format of an enum is a string) have to be wrapped in
              // quotes in order to be valid json
              dataArgument = "`\"${" + parameterModel.getParameterName() + "}\"`";
            }
          }
        }
        writer.writeLine("requiredHeaders: [" + Joiner.on(", ").join(requiredHeaders) + "],");
        writer.writeLine("pathArguments: [" + Joiner.on(", ").join(pathArguments) + "],");
        writer.writeLine("queryArguments: {");
        writer.increaseIndent();
        for (String queryArgument : queryArguments) {
          writer.writeLine(queryArgument + ": " + queryArgument + ",");
        }
        writer.decreaseIndent();
        writer.writeLine("},");
        writer.writeLine("data: " + dataArgument);
        writer.decreaseIndent();
        writer.writeLine("};");
        writer.writeLine(
            "return this.httpApiBridge.callEndpoint<"
                + endpointModel.tsReturnType().toString()
                + ">(httpCallData);");
        writer.decreaseIndent();
        writer.writeLine("}");
      }
    }
    writer.decreaseIndent();
    writer.writeLine("}");
  }