createMorphy function

String createMorphy(
  1. bool isAbstract,
  2. List<NameTypeClassComment> allFields,
  3. String className,
  4. String classComment,
  5. List<Interface> interfacesFromImplements,
  6. List<Interface> interfacesAllInclSubInterfaces,
  7. List<NameTypeClassComment> classGenerics,
  8. bool hasConstContructor,
  9. bool generateJson,
  10. bool hidePublicConstructor,
  11. List<Interface> explicitForJson,
  12. bool nonSealed,
  13. bool explicitToJson,
  14. bool generateCompareTo,
  15. bool generateCopyWithFn,
  16. List<FactoryMethodInfo> factoryMethods,
  17. Map<String, dynamic> allAnnotatedClasses,
)

Implementation

String createMorphy(
  bool isAbstract,
  List<NameTypeClassComment> allFields,
  String className,
  String classComment,
  List<Interface> interfacesFromImplements,
  List<Interface> interfacesAllInclSubInterfaces,
  List<NameTypeClassComment> classGenerics,
  bool hasConstContructor,
  bool generateJson,
  bool hidePublicConstructor,
  List<Interface> explicitForJson,
  bool nonSealed,
  bool explicitToJson,
  bool generateCompareTo,
  bool generateCopyWithFn,
  List<FactoryMethodInfo> factoryMethods,
  Map<String, dynamic> allAnnotatedClasses,
) {
  //recursively go through otherClasses and get my fieldnames &

  var sb = StringBuffer();
  var classNameTrimmed = className.replaceAll("\$", "");

  sb.write(getClassComment(interfacesFromImplements, classComment));

  if (generateJson) {
    sb.writeln(createJsonSingleton(classNameTrimmed, classGenerics));
    sb.writeln(
      createJsonHeader(
        className,
        classGenerics,
        hidePublicConstructor,
        explicitToJson,
        generateCompareTo,
        isAbstract,
      ),
    );
  }

  sb.write(
    getClassDefinition(
      isAbstract: isAbstract,
      nonSealed: nonSealed,
      className: className,
    ),
  );

  if (classGenerics.isNotEmpty) {
    sb.write(getClassGenerics(classGenerics));
  }

  // Handle extends and implements
  if (!isAbstract || (isAbstract && nonSealed)) {
    // For concrete classes or non-sealed abstract classes ($-prefixed)
    // If factory methods exist, implement instead of extend
    if (factoryMethods.isNotEmpty) {
      // Consolidate the main class and interfaces into single implements clause
      var mainClassName = className;
      if (classGenerics.isNotEmpty) {
        // For implements clause, use generic type parameters without bounds
        var typeParams = classGenerics.map((g) => g.name).join(', ');
        mainClassName += '<$typeParams>';
      }
      var allImplements = <String>[
        mainClassName,
      ]; // Keep the original $ClassName with generics
      if (interfacesFromImplements.isNotEmpty) {
        allImplements.addAll(
          interfacesFromImplements.map((e) {
            var type = e.interfaceName.replaceAll("\$", "");
            if (e.typeParams.isEmpty) {
              return type;
            }
            return "${type}<${e.typeParams.map((e) => e.type).join(", ")}>";
          }),
        );
      }
      sb.write(" implements ${allImplements.join(', ')}");
    } else {
      sb.write(" extends ${className}");
      if (classGenerics.isNotEmpty) {
        sb.write(getExtendsGenerics(classGenerics));
      }
      if (interfacesFromImplements.isNotEmpty) {
        sb.write(getImplements(interfacesFromImplements, className));
      }
    }
  } else {
    // For sealed abstract classes, only add implements for interfaces
    if (interfacesFromImplements.isNotEmpty) {
      sb.write(getImplements(interfacesFromImplements, className));
    }
  }

  sb.writeln(" {");
  if (isAbstract) {
    sb.writeln(getPropertiesAbstract(allFields));

    // Add abstract copyWith methods for sealed classes
    var selfInterface = Interface.fromGenerics(
      className,
      classGenerics.map((e) => NameType(e.name, e.type)).toList(),
      allFields,
    );
    sb.writeln(
      MethodGenerator.generateAbstractCopyWithMethods(
        interfaceFields: selfInterface.fields,
        interfaceName: selfInterface.interfaceName,
        interfaceGenerics: selfInterface.typeParams,
        generateCopyWithFn: generateCopyWithFn,
      ),
    );
  } else {
    sb.writeln(getProperties(allFields));
    sb.write(getClassComment(interfacesFromImplements, classComment));

    //constructor
    // var constructorName = getConstructorName(classNameTrimmed, hidePublicConstructor);
    if (allFields.isEmpty) {
      if (!hidePublicConstructor) {
        sb.writeln("${classNameTrimmed}();");
        sb.writeln('\n');
      } else {
        sb.writeln("${classNameTrimmed}._();");
      }
    } else {
      //public constructor
      if (!hidePublicConstructor) {
        sb.writeln("${classNameTrimmed}({");
        sb.writeln(getConstructorRows(allFields));
        sb.writeln("}) ${getInitializer(allFields)};");
      }

      //the json needs a public constructor, we add this if public constructor is hidden
      if (hidePublicConstructor && generateJson) {
        sb.writeln("${classNameTrimmed}.forJsonDoNotUse({");
        sb.writeln(getConstructorRows(allFields));
        sb.writeln("}) ${getInitializer(allFields)};");
      }

      //only write a private constructor when hidePublicConstructor is true
      if (hidePublicConstructor) {
        sb.writeln("${classNameTrimmed}._({");
        sb.writeln(getConstructorRows(allFields));
        sb.writeln("}) ${getInitializer(allFields)};");
        sb.writeln('\n');
      }

      if (hasConstContructor) {
        sb.writeln("const ${classNameTrimmed}.constant({");
        sb.writeln(getConstructorRows(allFields));
        sb.writeln("}) ${getInitializer(allFields)};");
        sb.writeln('\n');
      }

      // Generate factory methods
      for (var factory in factoryMethods) {
        sb.writeln(generateFactoryMethod(factory, classNameTrimmed, allFields));
      }

      sb.writeln(getToString(allFields, classNameTrimmed));
    }

    sb.writeln('\n');
    sb.writeln(getHashCode(allFields));
    sb.writeln('\n');
    sb.writeln(getEquals(allFields, classNameTrimmed));
    sb.writeln('\n');
  }
  //

  var interfacesX = [
    ...interfacesAllInclSubInterfaces,
    Interface.fromGenerics(
      className,
      classGenerics.map((e) => NameType(e.name, e.type ?? e.name)).toList(),
      allFields,
    ),
  ];

  // Create list of known Morphy classes early so it can be used in method generators
  var knownClasses = [
    ...interfacesAllInclSubInterfaces.map(
      (i) => i.interfaceName.replaceAll("\$", ""),
    ),
    classNameTrimmed,
    ...allAnnotatedClasses.keys.map((name) => name.replaceAll("\$", "")),
  ].toSet().toList();

  if (!isAbstract || (isAbstract && nonSealed)) {
    interfacesX.where((element) => !element.isExplicitSubType).forEach((x) {
      sb.writeln(
        MethodGenerator.generateCopyWithMethods(
          classFields: allFields,
          interfaceFields: x.fields,
          interfaceName: x.interfaceName,
          className: className,
          isClassAbstract: isAbstract,
          interfaceGenerics: x.typeParams,
          generateCopyWithFn: generateCopyWithFn,
          knownClasses: knownClasses,
          classGenerics: classGenerics
              .map((e) => NameType(e.name, e.type))
              .toList(),
          nonSealed: nonSealed,
          hidePublicConstructor: hidePublicConstructor,
        ),
      );
      // Generate changeTo methods for inherited interfaces (upward conversion: child to parent)
      // Skip generation for abstract interfaces that can't be instantiated unless it's for non-sealed classes
      if ((x.interfaceName != classNameTrimmed &&
              !NameCleaner.isAbstract(x.interfaceName)) ||
          (nonSealed && NameCleaner.isAbstract(x.interfaceName))) {
        sb.writeln(
          MethodGenerator.generateChangeToMethods(
            classFields: allFields,
            interfaceFields: x.fields,
            interfaceName: x.interfaceName,
            className: className,
            isClassAbstract: isAbstract,
            interfaceGenerics: x.typeParams,
            knownClasses: knownClasses,
            isInterfaceSealed: x.isSealed,
            classGenerics: classGenerics
                .map((e) => NameType(e.name, e.type))
                .toList(),
            nonSealed: nonSealed,
            hidePublicConstructor: hidePublicConstructor,
            interfaceHidePublicConstructor: x.hidePublicConstructor,
          ),
        );
      }
    });
  }

  if (generateJson) {
    // sb.writeln("// $classGenerics");
    // sb.writeln("//interfacesX");
    // sb.writeln("//explicitForJson");
    sb.writeln(commentEveryLine(interfacesX.map((e) => e.toString()).join()));
    sb.writeln(commentEveryLine(explicitForJson.join("\n").toString()));
    sb.writeln(generateFromJsonHeader(className));
    sb.writeln(
      generateFromJsonBody(
        className,
        classGenerics,
        explicitForJson,
        isAbstract,
      ),
    );
    sb.writeln(generateToJson(className, classGenerics, isAbstract));
    sb.writeln(generateToJsonLean(className, isAbstract));
  }
  sb.writeln("}");
  if ((!isAbstract || (isAbstract && nonSealed)) &&
      (!className.startsWith('\$\$') ||
          (className.startsWith('\$\$') && !isAbstract)) &&
      generateCompareTo) {
    // Create a list of all known classes from the interfaces

    sb.writeln(
      generateCompareExtension(
        isAbstract,
        className,
        classNameTrimmed,
        allFields,
        interfacesAllInclSubInterfaces, // Pass all known interfaces
        knownClasses, // Pass all known classes
        generateCompareTo,
      ),
    );
  }
  // Generate changeTo methods for explicitSubTypes (downward conversion: parent to child)
  if (interfacesX.any((element) => element.isExplicitSubType)) {
    sb.writeln(
      "extension ${classNameTrimmed}ChangeToE on ${classNameTrimmed} {",
    );

    interfacesX.where((element) => element.isExplicitSubType).forEach((x) {
      sb.writeln(
        MethodGenerator.generateChangeToMethods(
          classFields: allFields,
          interfaceFields: x.fields,
          interfaceName: x.interfaceName,
          className: className,
          isClassAbstract: isAbstract,
          interfaceGenerics: x.typeParams,
          knownClasses: knownClasses,
          isInterfaceSealed: x.isSealed,
          classGenerics: classGenerics
              .map((e) => NameType(e.name, e.type))
              .toList(),
          hidePublicConstructor: hidePublicConstructor,
          interfaceHidePublicConstructor: x.hidePublicConstructor,
        ),
      );
    });
    sb.writeln("}");
  }

  sb.writeln(getEnumPropertyList(allFields, className));

  sb.writeln(
    getPatchClass(
      allFields,
      className,
      knownClasses,
      classGenerics.map((e) => e.name).toList(),
    ),
  );
  // return commentEveryLine(sb.toString());
  return sb.toString();
}