import {
  DataNamespaceKind,
  DataEntityKind,
  DataModelKind,
  DataPropertyKind,
  DataAssociationKind,
} from '../../models/kinds.js'
import type { DataNamespace } from './DataNamespace.js'

/**
 * @deprecated
 */
export type DataImpactKinds =
  | typeof DataNamespaceKind
  | typeof DataEntityKind
  | typeof DataModelKind
  | typeof DataPropertyKind
  | typeof DataAssociationKind

/**
 * The impact analysis report
 * @deprecated
 */
export interface DataImpactReport {
  /**
   * The key of the impacted data object.
   * This is the key of the object that is being changed.
   */
  key: string
  /**
   * The kind of the impacted data object.
   * This is the kind of the object that is being changed.
   */
  kind: DataImpactKinds
  /**
   * The list of impacted data objects.
   */
  impact: DataImpactItem[]
  /**
   * Whether it is possible to proceed with the change.
   * If the change is not possible, the reason will be in the impact list.
   */
  canProceed: boolean
}

/**
 * @deprecated
 */
export interface DataImpactItem {
  /**
   * The key of the impacted data object.
   */
  key: string
  /**
   * The kind of the impacted data object.
   */
  kind: string
  /**
   * The type of the impact.
   *
   * - `delete` - The data object would be deleted.
   */
  type: 'delete'
  /**
   * The impact description.
   */
  impact: string
  /**
   * Whether the impact is blocking the operation.
   * If true, the operation cannot proceed.
   */
  blocking: boolean
  /**
   * The type of the relationship between two impacted objects.
   */
  relationship?: 'child'
  /**
   * The resolution of the conflict if the change will be forced.
   */
  resolution?: string
}

/**
 * # DomainImpactAnalysis
 *
 * The `DomainImpactAnalysis` class is a powerful tool for analyzing the consequences of deleting data domain objects
 * within a `DataNamespace`.
 * It helps developers understand the ripple effects of removing a namespace, data model, entity, property,
 * or association, ensuring data integrity and preventing unintended side effects.
 *
 * ## Core Concepts
 *
 * -   **Impact Report:** The central output of the `DomainImpactAnalysis` class is an `DataImpactReport`.
 *     This report details the potential consequences of a deletion operation, including:
 *     -   The object being deleted (`key`, `kind`).
 *     -   A list of `ImpactItem` objects, each describing a specific consequence.
 *     -   Whether the deletion can proceed safely (`canProceed`).
 *
 * -   **ImpactItem:** Each `ImpactItem` describes a specific consequence of the deletion. Key properties include:
 *     -   `key`: The key of the impacted object.
 *     -   `kind`: The kind of the impacted object.
 *     -   `type`: The type of impact (currently only `delete`).
 *     -   `impact`: A human-readable description of the impact.
 *     -   `blocking`: Whether this impact prevents the deletion from proceeding.
 *     -   `relationship`: The type of relationship between the deleted object and the impacted object (e.g., `child`).
 *     -   `resolution`: A description of how the impact will be resolved if the deletion is forced.
 *
 * -   **Blocking Impacts:** Some impacts are considered "blocking," meaning they prevent the deletion from proceeding
 *     without manual intervention. For example, deleting an entity that is a parent to other entities is
 *     a blocking impact.
 *
 * -   **Non-Blocking Impacts:** Some impacts are informational and do not prevent the deletion. For example, deleting a
 *     property is not a blocking impact.
 *
 * ## Usage
 *
 * 1.  **Instantiation:** Create an instance of `DomainImpactAnalysis`, passing the root `DataNamespace` as an argument.
 *
 *     ```typescript
 *     import { DataNamespace } from './DataNamespace';
 *     import { DomainImpactAnalysis } from './DomainImpactAnalysis';
 *
 *     const rootNamespace = new DataNamespace();
 *     // ... add some data to the namespace
 *     const analyzer = new DomainImpactAnalysis(rootNamespace);
 *     ```
 *
 * 2.  **Performing Analysis:** Use the `deleteAnalysis()` method to generate an `ImpactReport` for a specific deletion.
 *     Provide the `key` and `kind` of the object to be deleted.
 *
 *     ```typescript
 *     import { DataEntityKind } from '../models/kinds.js';
 *     // ...
 *     const entityKey = 'some-entity-key';
 *     const report = analyzer.deleteAnalysis(entityKey, DataEntityKind);
 *     ```
 *
 * 3.  **Interpreting the Report:** Examine the `ImpactReport` to understand the consequences of the deletion.
 *     -   Check `report.canProceed` to see if the deletion is safe.
 *     -   Iterate through `report.impact` to understand each consequence.
 *     -   Pay special attention to `impact.blocking` to identify impacts that require manual resolution.
 *
 *     ```typescript
 *     if (report.canProceed) {
 *       // Proceed with deletion
 *     } else {
 *       console.warn('Deletion cannot proceed due to the following impacts:');
 *       report.impact.forEach((item) => {
 *         console.warn(`- ${item.impact}`);
 *         if (item.blocking) {
 *           console.warn(`  - This impact is blocking.`);
 *           if (item.resolution) {
 *             console.warn(`  - Resolution: ${item.resolution}`);
 *           }
 *         }
 *       });
 *       // Handle blocking impacts (e.g., prompt the user, modify the data, etc.)
 *     }
 *     ```
 *
 * ## Supported Deletion Scenarios
 *
 * The `DomainImpactAnalysis` class supports analyzing the deletion of the following data domain object types:
 *
 * -   **Namespaces (`DataNamespaceKind`):** Deleting a namespace also impacts all its child namespaces,
 *     data models, entities, properties, and associations.
 * -   **Data Models (`DataModelKind`):** Deleting a data model impacts all its entities, properties, and associations.
 * -   **Entities (`DataEntityKind`):** Deleting an entity impacts its properties, associations, and any other
 *     entities that have it as a parent or target in an association.
 * -   **Properties (`DataPropertyKind`):** Deleting a property impacts the entity it belongs to.
 * -   **Associations (`DataAssociationKind`):** Deleting an association impacts the entity it belongs to.
 *
 * ## Example: Deleting an Entity
 *
 * Consider the following scenario:
 *
 * -   Namespace: `MyNamespace`
 *     -   Data Model: `ProductModel`
 *         -   Entity: `Product`
 *             -   Property: `name`
 *             -   Association: `category` (targets `Category` entity)
 *         -   Entity: `Category`
 *             - Property: `name`
 *         - Entity: `SpecialProduct` (parent: `Product`)
 *
 * If you attempt to delete the `Product` entity, the `DomainImpactAnalysis` will generate a report similar to this:
 *
 * ```json
 * {
 *   "key": "Product",
 *   "kind": "DataEntityKind",
 *   "impact": [
 *     {
 *       "key": "Product",
 *       "kind": "DataEntityKind",
 *       "type": "delete",
 *       "impact": "The entity with key Product will be deleted.",
 *       "blocking": false
 *     },
 *     {
 *       "key": "SpecialProduct",
 *       "kind": "DataEntityKind",
 *       "type": "delete",
 *       "impact": "The SpecialProduct entity will become an orphan because it is a child of Product.",
 *       "resolution": "The "Product" entity will be removed as the parent of the "SpecialProduct" entity.",
 *       "blocking": true,
 *       "relationship": "child"
 *     },
 *     {
 *       "key": "category",
 *       "kind": "DataAssociationKind",
 *       "type": "delete",
 *       "impact": "The association with key category will be broken because it has a target to Product.",
 *       "resolution": "The association with key category will be removed from Product.",
 *       "blocking": true
 *     },
 *     {
 *       "key": "name",
 *       "kind": "DataPropertyKind",
 *       "type": "delete",
 *       "impact": "The property with key name will be deleted.",
 *       "blocking": false
 *     }
 *   ],
 *   "canProceed": false
 * }
 * ```
 *
 * This report indicates that:
 *
 * -   The `Product` entity will be deleted.
 * -   The `SpecialProduct` entity will become an orphan (blocking).
 * -   The `category` association will be broken (blocking).
 * -   The `name` property will be deleted.
 * -   The deletion cannot proceed without addressing the blocking impacts.
 *
 * ## Types
 *
 * ### `ImpactKinds`
 *
 * -   **Description:** A type alias for the kinds of data domain objects that can be analyzed.
 * -   **Values:**
 *     -   `DataNamespaceKind`
 *     -   `DataEntityKind`
 *     -   `DataModelKind`
 *     -   `DataPropertyKind`
 *     -   `DataAssociationKind`
 *
 * ### `ImpactReport`
 *
 * -   **Description:** The structure of the impact analysis report.
 * -   **Properties:**
 *     -   `key` (`string`): The key of the object being deleted.
 *     -   `kind` (`ImpactKinds`): The kind of the object being deleted.
 *     -   `impact` (`ImpactItem[]`): The list of impacts.
 *     -   `canProceed` (`boolean`): Whether the deletion can proceed.
 *
 * ### `ImpactItem`
 *
 * -   **Description:** The structure of an individual impact item.
 * -   **Properties:**
 *     -   `key` (`string`): The key of the impacted object.
 *     -   `kind` (`string`): The kind of the impacted object.
 *     -   `type` (`'delete'`): The type of impact.
 *     -   `impact` (`string`): The impact description.
 *     -   `blocking` (`boolean`): Whether the impact is blocking.
 *     -   `relationship` (`'child'`, optional): The relationship type.
 *     -   `resolution` (`string`, optional): The resolution description.
 *
 * ## Error Handling
 *
 * The `DomainImpactAnalysis` class does not throw errors. Instead, it uses the `ImpactReport` to communicate
 * the results of the analysis, including any blocking impacts.
 *
 * ## Best Practices
 *
 * - **Always Analyze Before Deleting:** Before deleting any data domain object, always use `DomainImpactAnalysis`
 *   to understand the consequences.
 * - **Handle Blocking Impacts:** Pay close attention to `blocking` impacts and implement appropriate
 *   logic to handle them.
 * - **Inform the User:** If a deletion cannot proceed, inform the user about the blocking impacts and provide
 *   guidance on how to resolve them.
 * - **Consider Forced Deletion:** In some cases, you may want to allow users to force a deletion even if there are
 *   blocking impacts. In such cases, you should clearly communicate the risks to the user.
 *
 * ## Conclusion
 *
 * The `DomainImpactAnalysis` class is an essential tool for maintaining data integrity when working with complex
 * data domain models. By providing detailed impact reports, it empowers developers to make informed decisions
 * about data deletion and prevent unintended consequences.
 * @deprecated
 */
export class DataImpactAnalysis {
  private report: DataImpactReport
  private root: DataNamespace

  constructor(root: DataNamespace) {
    this.root = root
    this.report = {
      key: '',
      kind: DataNamespaceKind,
      impact: [],
      canProceed: false,
    }
  }

  /**
   * Generates a report of how the data domain will be impacted by the deletion of a data domain object.
   * @param key The key of the impacted data domain object.
   * @param kind The kind of the impacted data object.
   * @returns The delete impact analysis report.
   * @deprecated
   */
  deleteAnalysis(key: string, kind: DataImpactKinds): DataImpactReport {
    this.report = {
      key,
      kind,
      impact: [],
      canProceed: true,
    }
    this.report.impact = this.createDeleteImpact(key, kind, key)
    // this.report.impact.unshift({
    //   key,
    //   kind,
    //   type: 'delete',
    //   impact: `The ${this.kindToLabel(kind)} with key ${key} will be deleted.`,
    //   blocking: false,
    // })
    return this.report
  }

  protected createDeleteImpact(key: string, kind: DataImpactKinds, rootKey: string): DataImpactItem[] {
    switch (kind) {
      case DataNamespaceKind:
        return this.deleteNamespaceAnalysis(key, rootKey)
      case DataModelKind:
        return this.deleteDataModelAnalysis(key, rootKey)
      case DataEntityKind:
        return this.deleteEntityAnalysis(key, rootKey)
      case DataPropertyKind:
        return this.deletePropertyAnalysis(key)
      case DataAssociationKind:
        return this.deleteAssociationAnalysis(key)
      default:
        return []
    }
  }

  protected deleteNamespaceAnalysis(key: string, rootKey: string): DataImpactItem[] {
    const result: DataImpactItem[] = []
    const ns = this.root.findNamespace(key)
    if (!ns) {
      return result
    }
    result.push({
      key: ns.key,
      kind: ns.kind,
      type: 'delete',
      impact: `The ${ns.info.getLabel()} ${this.kindToLabel(DataNamespaceKind)} will be deleted.`,
      blocking: false,
    })
    const namespaces = ns.listNamespaces()
    namespaces.forEach((child) => {
      const items = this.deleteNamespaceAnalysis(child.key, rootKey)
      result.push(...items)
    })
    const models = ns.listDataModels()
    models.forEach((child) => {
      const items = this.deleteDataModelAnalysis(child.key, rootKey)
      result.push(...items)
    })
    return result
  }

  protected deleteDataModelAnalysis(key: string, rootKey: string): DataImpactItem[] {
    const result: DataImpactItem[] = []
    const model = this.root.findDataModel(key)
    if (!model) {
      return result
    }
    result.push({
      key: model.key,
      kind: model.kind,
      type: 'delete',
      impact: `The ${model.info.getLabel()} ${this.kindToLabel(DataModelKind)} will be deleted.`,
      blocking: false,
    })
    model.entities.forEach((child) => {
      const items = this.deleteEntityAnalysis(child.key, rootKey)
      result.push(...items)
    })
    return result
  }

  protected deleteEntityAnalysis(key: string, rootKey: string): DataImpactItem[] {
    const result: DataImpactItem[] = []
    const entity = this.root.findEntity(key)
    if (!entity) {
      return result
    }
    result.push({
      key: entity.key,
      kind: entity.kind,
      type: 'delete',
      impact: `The ${entity.info.getLabel()} ${this.kindToLabel(DataEntityKind)} will be deleted.`,
      blocking: false,
    })
    // We need to know whether the entity is a parent of another entity
    const children = this.root.definitions.entities.filter((domainEntity) => {
      if (domainEntity.key === entity.key) {
        // ignore self
        return false
      }
      if (!domainEntity.parents.includes(entity.key)) {
        // no relationship
        return false
      }
      if (domainEntity.isChildOf(rootKey)) {
        // No need to include this parent as it itself is being deleted
        return false
      }
      // the entity has a parent-child relationship to the deleted entity.
      return true
    })
    if (children.length) {
      children.forEach((child) => {
        const pLabel = entity.info.getLabel()
        const cLabel = child.info.getLabel()
        result.push({
          key: child.key,
          kind: child.kind,
          type: 'delete',
          impact: `The "${cLabel}" ${this.kindToLabel(DataEntityKind)} will become an orphan because it is a child of the "${pLabel}" entity.`,
          resolution: `The "${pLabel}" entity will be removed as the parent of the "${cLabel}" entity.`,
          blocking: true,
          relationship: 'child',
        })
      })
      this.report.canProceed = false
    }
    // We need to know whether there's another entity that has an association to this entity.
    const inAssociations = this.root.definitions.associations.filter((association) => {
      return association.targets.some((item) => {
        if (item.key === entity.key) {
          const related = association.getParentInstance()
          if (!related) {
            return false
          }
          if (related.isChildOf(rootKey)) {
            // No need to include this association as the related entity itself is being deleted
            return false
          }
          return true
        }
        return false
      })
    })
    if (inAssociations.length) {
      inAssociations.forEach((association) => {
        result.push({
          key: association.key,
          kind: association.kind,
          type: 'delete',
          impact: `The ${association.info.getLabel()} ${this.kindToLabel(DataAssociationKind)} will be broken because it has a target to ${entity.info.getLabel()}.`,
          resolution: `The ${association.info.getLabel()} ${this.kindToLabel(DataAssociationKind)} will be removed from ${entity.info.getLabel()}.`,
          blocking: true,
        })
      })
      this.report.canProceed = false
    }
    entity.properties.forEach((child) => {
      const items = this.deletePropertyAnalysis(child.key)
      result.push(...items)
    })
    entity.associations.forEach((child) => {
      const items = this.deleteAssociationAnalysis(child.key)
      result.push(...items)
    })
    return result
  }

  protected deletePropertyAnalysis(key: string): DataImpactItem[] {
    const result: DataImpactItem[] = []
    const property = this.root.findProperty(key)
    if (!property) {
      return result
    }
    result.push({
      key: property.key,
      kind: property.kind,
      type: 'delete',
      impact: `The ${property.info.getLabel()} ${this.kindToLabel(DataPropertyKind)} will be deleted.`,
      blocking: false,
    })
    return result
  }

  protected deleteAssociationAnalysis(key: string): DataImpactItem[] {
    const result: DataImpactItem[] = []
    const association = this.root.findAssociation(key)
    if (!association) {
      return result
    }
    result.push({
      key: association.key,
      kind: association.kind,
      type: 'delete',
      impact: `The ${association.info.getLabel()} ${this.kindToLabel(DataAssociationKind)} will be deleted.`,
      blocking: false,
    })
    return result
  }

  protected kindToLabel(kind: DataImpactKinds): string {
    switch (kind) {
      case DataNamespaceKind:
        return 'namespace'
      case DataEntityKind:
        return 'entity'
      case DataModelKind:
        return 'data model'
      case DataPropertyKind:
        return 'property'
      case DataAssociationKind:
        return 'association'
      default:
        return 'unknown'
    }
  }
}
