import { DataEntity } from './DataEntity.js'
import { DataNamespace } from './DataNamespace.js'
import { type DataPropertyType } from '../DataFormat.js'
import { type IThing, Thing } from '../../models/Thing.js'
import { nanoid } from 'nanoid'

/**
 * A fluent builder for constructing `DataEntity` instances.
 *
 * @example
 * ```typescript
 * const ns = new DataNamespace();
 * const userEntity = DataEntityBuilder.withName(namespace, 'User')
 *   .addTypedProperty('string', 'firstName')
 *   .addTypedProperty('string', 'lastName')
 *   .addTypedProperty('integer', 'age')
 *   .addNamedAssociation('address')
 *   .addTag('user')
 *   .addTaxonomy(['user', 'person'])
 *   .build();
 *
 * const addressEntity = DataEntityBuilder.withName(namespace, 'Address')
 *   .addTypedProperty('string', 'street')
 *   .addTypedProperty('string', 'city')
 *   .addTag('address')
 *   .build();
 *
 * // Adding a target association after entities creation:
 * userEntity.associations[0].addTarget(addressEntity)
 * ```
 *
 * @example
 * ```typescript
 * const newEntity = DataEntityBuilder.newEntity(namespace)
 *   .withInfo({name: "test entity", description: "test desc"})
 *   .build();
 *
 * // with parents
 * const baseEntity = DataEntityBuilder.withName(namespace, 'Base')
 *   .build()
 *
 * const specializedEntity = DataEntityBuilder.withName(namespace, 'Specialized')
 *   .addParent(baseEntity.key)
 *   .build()
 * ```
 * @deprecated
 */
export class DataEntityBuilder {
  private entity: DataEntity
  private root: DataNamespace

  /**
   * Creates a new DataEntityBuilder.
   * @param root The namespace the entity will belong to.
   * @param name Optional name of the entity.
   */
  private constructor(root: DataNamespace, name?: string) {
    this.root = root
    this.entity = new DataEntity(root)
    if (name) {
      this.entity.info = Thing.fromName(name)
    }
  }

  /**
   * Creates a new DataEntityBuilder instance with a name.
   * @param root The namespace the entity will belong to.
   * @param name The name of the entity.
   * @returns A new DataEntityBuilder instance.
   */
  static withName(root: DataNamespace, name: string): DataEntityBuilder {
    return new DataEntityBuilder(root, name)
  }

  /**
   * Creates a new DataEntityBuilder instance without a name.
   * @param root The namespace the entity will belong to.
   * @returns A new DataEntityBuilder instance.
   */
  static newEntity(root: DataNamespace): DataEntityBuilder {
    return new DataEntityBuilder(root)
  }

  /**
   * Sets the key of the entity.
   * @param key The key of the entity.
   * @returns The current DataEntityBuilder instance.
   */
  withKey(key: string): DataEntityBuilder {
    this.entity.key = key
    return this
  }

  /**
   * Adds a description to the entity.
   * @param description The description of the entity.
   * @returns The current DataEntityBuilder instance.
   */
  withDescription(description: string): DataEntityBuilder {
    this.entity.info.description = description
    return this
  }
  /**
   * Adds info object to the entity
   * @param info The info object
   * @returns The current DataEntityBuilder instance.
   */
  withInfo(info: IThing): DataEntityBuilder {
    this.entity.info = new Thing(info)
    return this
  }

  /**
   * Adds a tag to the entity.
   * @param tag The tag to add.
   * @returns The current DataEntityBuilder instance.
   */
  addTag(tag: string): DataEntityBuilder {
    this.entity.addTag(tag)
    return this
  }

  /**
   * Adds multiple tags to the entity.
   * @param tags The tags to add.
   * @returns The current DataEntityBuilder instance.
   */
  addTags(tags: string[]): DataEntityBuilder {
    tags.forEach((tag) => this.entity.addTag(tag))
    return this
  }
  /**
   * Adds taxonomy to the entity.
   * @param taxonomy The taxonomy to add.
   * @returns The current DataEntityBuilder instance.
   */
  addTaxonomy(taxonomy: string[]): DataEntityBuilder {
    this.entity.taxonomy.push(...taxonomy)
    return this
  }
  /**
   * Adds a typed property to the entity.
   * @param type The type of the property.
   * @param name The name of the property.
   * @returns The current DataEntityBuilder instance.
   */
  addTypedProperty(type: DataPropertyType, name?: string): DataEntityBuilder {
    this.entity.addTypedProperty(type, name)
    return this
  }

  /**
   * Adds a named property to the entity.
   * @param name The name of the property.
   * @returns The current DataEntityBuilder instance.
   */
  addNamedProperty(name: string): DataEntityBuilder {
    this.entity.addNamedProperty(name)
    return this
  }

  /**
   * Adds a named association to the entity.
   * @param name The name of the association.
   * @returns The current DataEntityBuilder instance.
   */
  addNamedAssociation(name: string): DataEntityBuilder {
    this.entity.addNamedAssociation(name)
    return this
  }

  /**
   * Adds a target association to the entity.
   * @param target The target entity key of the association.
   * @param name Optional name of the association.
   * @returns The current DataEntityBuilder instance.
   */
  addTargetAssociation(target: string, name?: string): DataEntityBuilder {
    this.entity.addTargetAssociation(target, name)
    return this
  }

  /**
   * Adds a foreign association to the entity.
   * @param target The target entity key of the association.
   * @param namespace The target entity namespace.
   * @param name Optional name of the association.
   * @returns The current DataEntityBuilder instance.
   */
  addForeignAssociation(target: string, namespace: string, name?: string): DataEntityBuilder {
    this.entity.addForeignAssociation(target, namespace, name)
    return this
  }
  /**
   * Sets the entity as deprecated.
   * @param deprecated
   * @returns The current DataEntityBuilder instance.
   */
  setDeprecated(deprecated: boolean): DataEntityBuilder {
    this.entity.deprecated = deprecated
    return this
  }
  /**
   * Adds a parent to the entity.
   * @param parentKey The key of the parent entity.
   * @returns The current DataEntityBuilder instance.
   */
  addParent(parentKey: string): DataEntityBuilder {
    this.entity.addParent(parentKey)
    return this
  }

  /**
   * Adds multiple parents to the entity.
   * @param parentKeys The keys of the parent entities.
   * @returns The current DataEntityBuilder instance.
   */
  addParents(parentKeys: string[]): DataEntityBuilder {
    parentKeys.forEach((key) => {
      this.addParent(key)
    })
    return this
  }

  /**
   * Builds and returns the `DataEntity` instance.
   * @returns The constructed DataEntity instance.
   */
  build(): DataEntity {
    if (!this.entity.key) {
      this.entity.key = nanoid()
    }
    this.root.definitions.entities.push(this.entity)
    return this.entity
  }
}
