/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { AbsolutePath } from '../../core';
import {
  CodegenError,
  TypeAnnotationToTS,
  createEventNameFromEventHandlerName,
  getStringifiedTSDefaultValueFromPropTypeAnnotation,
} from '../core';
import type { SpecCodeGenerator } from '../core';
import { SpecSchema } from '../core';
import {
  ComponentDescriptorHTemplate,
  ComponentJSIBinderHTemplate,
  ComponentUtilsTSTemplate,
} from '../templates';
import { GlueCodeComponentDataV1 } from './AppBuildTimeGlueCodeGenerator';

export class ArkTSComponentCodeGeneratorArkTS implements SpecCodeGenerator {
  private glueCodeDataItems: GlueCodeComponentDataV1[] = [];

  constructor(
    private cppOutputPath: AbsolutePath,
    private tsOutputPath: AbsolutePath,
    private codegenNoticeLines: string[],
    private rnohImport: string
  ) {}

  getGlueCodeData(): GlueCodeComponentDataV1[] {
    return this.glueCodeDataItems;
  }

  generate(schema: SpecSchema): Map<AbsolutePath, string> {
    if (schema.type !== 'Component') {
      throw new CodegenError({
        whatHappened: `ComponentCodeGenerator can't generate code for a module type: ${schema.type}`,
        unexpected: true,
      });
    }
    const typeAnnotationToTS = new TypeAnnotationToTS();
    const result = new Map<AbsolutePath, string>();
    Object.entries(schema.components).map(([name, shape]) => {
      const componentDescriptorHTemplate = new ComponentDescriptorHTemplate(
        name
      );
      const componentJSIBinderHTemplate = new ComponentJSIBinderHTemplate(
        name,
        this.codegenNoticeLines
      );
      const componentUtilsTSTemplate = new ComponentUtilsTSTemplate(
        name,
        this.codegenNoticeLines,
        this.rnohImport
      );

      shape.props.forEach((prop) => {
        componentJSIBinderHTemplate.addProp({
          name: prop.name,
          isObject: prop.typeAnnotation.type === 'ObjectTypeAnnotation',
        });

        componentUtilsTSTemplate.addProp({
          name: prop.name,
          tsType: typeAnnotationToTS.convert(prop.typeAnnotation),
          defaultValue: getStringifiedTSDefaultValueFromPropTypeAnnotation(
            prop.typeAnnotation
          ),
          isOptional: prop.optional,
        });
      });
      shape.commands.forEach((command) => {
        componentJSIBinderHTemplate.addCommand({
          name: command.name,
        });
        componentUtilsTSTemplate.addCommand({
          name: command.name,
          argsTSType:
            '[' +
            command.typeAnnotation.params
              .map(
                (param) =>
                  typeAnnotationToTS.convert(param.typeAnnotation) +
                  (param.optional ? '?' : '')
              )
              .join(', ') +
            ']',
        });
      });

      shape.events.forEach((event) => {
        componentUtilsTSTemplate.addEvent({
          name: createEventNameFromEventHandlerName(event.name),
          payloadTSType: event.typeAnnotation.argument
            ? typeAnnotationToTS.convert(event.typeAnnotation.argument)
            : 'undefined',
        });
        componentJSIBinderHTemplate.addEvent({
          type: event.bubblingType,
          eventHandlerName: event.name,
        });
      });
      result.set(
        this.cppOutputPath.copyWithNewSegment(`${name}ComponentDescriptor.h`),
        componentDescriptorHTemplate.build()
      );
      result.set(
        this.cppOutputPath.copyWithNewSegment(`${name}JSIBinder.h`),
        componentJSIBinderHTemplate.build()
      );
      result.set(
        this.tsOutputPath.copyWithNewSegment(`${name}.ts`),
        componentUtilsTSTemplate.build()
      );
      this.glueCodeDataItems.push({
        name,
        eventNames: shape.events.map((e) =>
          createEventNameFromEventHandlerName(e.name)
        ),
      });
    });
    return result;
  }
}