/**
 * 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 mustache from 'mustache';

const TEMPLATE = `
/**
{{#codegenNoticeLines}}
 * {{{line}}}
{{/codegenNoticeLines}}
 */

import {
  Descriptor as ComponentDescriptor,
  ViewBaseProps,
  ViewRawProps,
  ViewDescriptorWrapperBase,
  ColorValue,
  Color,
  RNInstance,
  Tag,
  RNComponentCommandReceiver,
  ViewPropsSelector,
} from '{{{rnohImport}}}';


export namespace {{name}} {
  export const NAME = "{{name}}" as const

  export interface DirectRawProps {
    {{#props}}
    {{name}}{{optionalityMarker}}: {{{type}}};
    {{/props}}
  }
  
  export interface Props extends ViewBaseProps {}
  
  export interface State {}
  
  export interface RawProps extends ViewRawProps, DirectRawProps {}
  
  export class PropsSelector extends ViewPropsSelector<Props, RawProps> {
    {{#simpleProps}}
    get {{name}}() {
      return this.rawProps.{{name}}{{{orDefaultValue}}};
    }
    
    {{/simpleProps}}
  
    {{#complexProps}}
    get {{name}}() {
{{{implementation}}}
    }
    
    {{/complexProps}}
  }

  export type Descriptor = ComponentDescriptor<
    typeof NAME,
    Props,
    State,
    RawProps
  >;
  
  export class DescriptorWrapper extends ViewDescriptorWrapperBase<
    typeof NAME,
    Props,
    State,
    RawProps,
    PropsSelector
  > {
    protected createPropsSelector() {
      return new PropsSelector(this.descriptor.props, this.descriptor.rawProps)
    }
  }
  
  export interface EventPayloadByName {
    {{#events}}
    "{{name}}": {{{payloadTSType}}}
    {{/events}}
  }
  
  export class EventEmitter {
    constructor(private rnInstance: RNInstance, private tag: Tag) {}
    
    emit<TEventName extends keyof EventPayloadByName>(eventName: TEventName, payload: EventPayloadByName[TEventName]) {
      this.rnInstance.emitComponentEvent(this.tag, eventName, payload)
    }
  }
  
  export interface CommandArgvByName {
    {{#commands}}
    "{{name}}": {{{argsTSType}}}
    {{/commands}}
  }
  
  export class CommandReceiver {
    private listenersByCommandName = new Map<string, Set<(...args: any[]) => void>>()
    private cleanUp: (() => void) | undefined = undefined
  
    constructor(private componentCommandReceiver: RNComponentCommandReceiver, private tag: Tag) {
    }
  
    subscribe<TCommandName extends keyof CommandArgvByName>(commandName: TCommandName, listener: (argv: CommandArgvByName[TCommandName]) => void) {
      if (!this.listenersByCommandName.has(commandName)) {
        this.listenersByCommandName.set(commandName, new Set())
      }
      this.listenersByCommandName.get(commandName)!.add(listener)
      const hasRegisteredCommandReceiver = !!this.cleanUp
      if (!hasRegisteredCommandReceiver) {
        this.cleanUp = this.componentCommandReceiver.registerCommandCallback(this.tag, (commandName: string, argv: any[]) => {
          if (this.listenersByCommandName.has(commandName)) {
            const listeners = this.listenersByCommandName.get(commandName)!
            listeners.forEach(listener => {
              listener(argv)
            })
          }
        })
      }
  
      return () => {
        this.listenersByCommandName.get(commandName)?.delete(listener)
        if (this.listenersByCommandName.get(commandName)?.size ?? 0 === 0) {
          this.listenersByCommandName.delete(commandName)
        }
        if (this.listenersByCommandName.size === 0) {
          this.cleanUp?.()
        }
      }
    }
  }

}
`;

const COLOR_VALUE_GETTER_TEMPLATE = `        if (this.rawProps.{{name}}) {
          return Color.fromColorValue(this.rawProps.{{name}})
        } else {
          return new Color({ r: 0, g: 0, b: 0, a: 255})
        }`;

type Prop = {
  name: string;
  tsType: string;
  defaultValue: string | null;
  isOptional: boolean;
};

type Event = {
  name: string;
  payloadTSType: string;
};

type Command = {
  name: string;
  argsTSType: string;
};

export class ComponentUtilsTSTemplate {
  private props: Prop[] = [];
  private events: Event[] = [];
  private commands: Command[] = [];
  private getterTemplateByTSType = new Map<string, string>();

  constructor(
    private name: string,
    private codegenNoticeLines: string[],
    private rnohImport: string
  ) {
    this.getterTemplateByTSType.set('ColorValue', COLOR_VALUE_GETTER_TEMPLATE);
  }

  addProp(prop: Prop) {
    this.props.push(prop);
  }

  addEvent(event: Event) {
    this.events.push(event);
  }

  addCommand(command: Command) {
    this.commands.push(command);
  }

  build(): string {
    return mustache.render(TEMPLATE.trimStart(), {
      codegenNoticeLines: this.codegenNoticeLines.map((line) => ({
        line,
      })),
      name: this.name,
      rnohImport: this.rnohImport,
      props: this.props.map((prop) => ({
        name: prop.name,
        type: prop.tsType,
        optionalityMarker: prop.isOptional ? '?' : '',
      })),
      events: this.events,
      commands: this.commands,
      simpleProps: this.props
        .filter((prop) => !this.getterTemplateByTSType.has(prop.tsType))
        .map((prop) => ({
          name: prop.name,
          type: prop.tsType,
          orDefaultValue: prop.defaultValue ? ` ?? ${prop.defaultValue}` : '',
        })),
      complexProps: this.props
        .filter((prop) => this.getterTemplateByTSType.has(prop.tsType))
        .map((prop) => ({
          name: prop.name,
          implementation: mustache.render(
            this.getterTemplateByTSType.get(prop.tsType)!,
            { name: prop.name },
            {}
          ),
        })),
    });
  }
}