// Copyright 2017-2025 @pezkuwi/types authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { AnyJson, Codec, CodecClass } from '@pezkuwi/types-codec/types';
import type { TypeDef } from '@pezkuwi/types-create/types';
import type { EventMetadataLatest } from '../interfaces/metadata/index.js';
import type { EventId } from '../interfaces/system/index.js';
import type { IEvent, IEventData, InterfaceTypes, Registry } from '../types/index.js';

import { Null, Struct, Tuple } from '@pezkuwi/types-codec';
import { objectProperties, objectSpread } from '@pezkuwi/util';

interface Decoded {
  DataType: CodecClass<Null> | CodecClass<GenericEventData>;
  value?: {
    index: Uint8Array;
    data: Uint8Array;
  }
}

/** @internal */
function decodeEvent (registry: Registry, value?: Uint8Array): Decoded {
  if (!value?.length) {
    return { DataType: Null };
  }

  const index = value.subarray(0, 2);

  return {
    DataType: registry.findMetaEvent(index),
    value: {
      data: value.subarray(2),
      index
    }
  };
}

/**
 * @name GenericEventData
 * @description
 * Wrapper for the actual data that forms part of an [[Event]]
 */
export class GenericEventData extends Tuple implements IEventData {
  readonly #meta: EventMetadataLatest;
  readonly #method: string;
  readonly #names: string[] | null = null;
  readonly #section: string;
  readonly #typeDef: TypeDef[];

  constructor (registry: Registry, value: Uint8Array, meta: EventMetadataLatest, section = '<unknown>', method = '<unknown>') {
    const fields = meta?.fields || [];

    super(registry, fields.map(({ type }) => registry.createLookupType(type) as keyof InterfaceTypes), value);

    this.#meta = meta;
    this.#method = method;
    this.#section = section;
    this.#typeDef = fields.map(({ type }) => registry.lookup.getTypeDef(type));

    const names = fields
      .map(({ name }) => registry.lookup.sanitizeField(name)[0])
      .filter((n): n is string => !!n);

    if (names.length === fields.length) {
      this.#names = names;

      objectProperties(this, names, (_, i) => this[i]);
    }
  }

  /**
   * @description The wrapped [[EventMetadata]]
   */
  public get meta (): EventMetadataLatest {
    return this.#meta;
  }

  /**
   * @description The method as a string
   */
  public get method (): string {
    return this.#method;
  }

  /**
   * @description The field names (as available)
   */
  public get names (): string[] | null {
    return this.#names;
  }

  /**
   * @description The section as a string
   */
  public get section (): string {
    return this.#section;
  }

  /**
   * @description The [[TypeDef]] for this event
   */
  public get typeDef (): TypeDef[] {
    return this.#typeDef;
  }

  /**
   * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
   */
  public override toHuman (isExtended?: boolean, disableAscii?: boolean): AnyJson {
    if (this.#names !== null) {
      const json: Record<string, AnyJson> = {};

      for (let i = 0, count = this.#names.length; i < count; i++) {
        json[this.#names[i]] = this[i].toHuman(isExtended, disableAscii);
      }

      return json;
    }

    return super.toHuman(isExtended);
  }
}

/**
 * @name GenericEvent
 * @description
 * A representation of a system event. These are generated via the [[Metadata]] interfaces and
 * specific to a specific Bizinikiwi runtime
 */
export class GenericEvent extends Struct implements IEvent<Codec[]> {
  // Currently we _only_ decode from Uint8Array, since we expect it to
  // be used via EventRecord
  constructor (registry: Registry, _value?: Uint8Array) {
    const { DataType, value } = decodeEvent(registry, _value);

    super(registry, {
      index: 'EventId',
      // eslint-disable-next-line sort-keys
      data: DataType
    }, value);
  }

  /**
   * @description The wrapped [[EventData]]
   */
  public get data (): IEvent<Codec[]>['data'] {
    return this.getT('data');
  }

  /**
   * @description The [[EventId]], identifying the raw event
   */
  public get index (): EventId {
    return this.getT('index');
  }

  /**
   * @description The [[EventMetadata]] with the documentation
   */
  public get meta (): EventMetadataLatest {
    return this.data.meta;
  }

  /**
   * @description The method string identifying the event
   */
  public get method (): string {
    return this.data.method;
  }

  /**
   * @description The section string identifying the event
   */
  public get section (): string {
    return this.data.section;
  }

  /**
   * @description The [[TypeDef]] for the event
   */
  public get typeDef (): TypeDef[] {
    return this.data.typeDef;
  }

  /**
   * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
   */
  public override toHuman (isExpanded?: boolean, disableAscii?: boolean): Record<string, AnyJson> {
    return objectSpread(
      {
        method: this.method,
        section: this.section
      },
      isExpanded
        ? { docs: this.meta.docs.map((d) => d.toString()) }
        : null,
      super.toHuman(isExpanded, disableAscii)
    );
  }
}
