// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'dart:async';

import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:wyatt_component_copy_with_extension/component_copy_with_extension.dart';

class ComponentCopyWithGenerator
    extends GeneratorForAnnotation<ComponentCopyWithExtension> {
  @override
  FutureOr<String> generateForAnnotatedElement(
    Element element,
    ConstantReader annotation,
    BuildStep buildStep,
  ) {
    /// Check element type
    if (element is! ClassElement) {
      throw InvalidGenerationSourceError(
        'Only classes can be annotated with "CopyWith". "$element" is '
        'not a ClassElement.',
        element: element,
      );
    }

    final classAnnotation = ComponentCopyWithExtension(
      skipFields: annotation.peek('skipFields')?.boolValue ?? true,
    );

    final generatedCode = StringBuffer()

      // Generate CopyWith Proxy implementation.
      ..write('class \$${element.displayName}CWProxyImpl implements '
          '\$${element.supertype?.getDisplayString(
        withNullability: false,
      )}CWProxy {  '
          'const \$${element.displayName}CWProxyImpl '
          '(this._value); '
          'final ${element.displayName} _value;');

    if (classAnnotation.skipFields ?? true) {
      for (final superField
          in element.supertype!.element.constructors.first.parameters) {
        final superFieldDisplayName = superField.displayName;
        generatedCode.write('@override ${element.displayName} '
            '$superFieldDisplayName(${superField.type.getDisplayString(
          withNullability: false,
        )}? '
            ' $superFieldDisplayName)'
            ' => this($superFieldDisplayName : $superFieldDisplayName); ');
      }
    }

    generatedCode.write('@override ${element.displayName} call ({');

    for (final superField
        in element.supertype!.element.constructors.first.parameters) {
      final superFieldDisplayName = superField.displayName;
      generatedCode.write('${superField.type.getDisplayString(
        withNullability: false,
      )}? $superFieldDisplayName,');
    }

    generatedCode.write('})=>${element.displayName}(');

    for (final superField in element.constructors.first.parameters) {
      final superFieldDisplayName = superField.displayName;
      generatedCode.write('$superFieldDisplayName:$superFieldDisplayName?? '
          '_value.$superFieldDisplayName,');
    }

    generatedCode
      ..write(');}')
      ..write(
        'mixin \$${element.displayName}CWMixin on Component { '
        '\$${element.supertype?.element.displayName}CWProxy get copyWith => '
        '\$${element.displayName}CWProxyImpl(this as ${element.displayName});}',
      );

    return generatedCode.toString();
  }
}
