import { DomainNamespaceKind, DomainEntityKind, DomainModelKind, DomainPropertyKind, DomainAssociationKind, } from '../models/kinds.js';
/**
 * # DomainImpactAnalysis
 *
 * The `DomainImpactAnalysis` class is a tool for analyzing the consequences of deleting data domain elements
 * within a `DataDomain`. 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 `DomainImpactReport`.
 *     This report details the potential consequences of a deletion operation, including:
 *     -   The object being deleted (`key`, `kind`).
 *     -   A list of `DomainImpactItem` objects, each describing a specific consequence.
 *     -   Whether the deletion can proceed safely (`canProceed`).
 *
 * -   **DomainImpactItem:** Each `DomainImpactItem` 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.
 *     -   `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.
 *     -   `severity`: The severity of the impact, which can be `info`, `warning`, or `error`.
 *
 * -   **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. Blocking impacts are indicated by the `severity` property set to `error`.
 *
 * -   **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 `DataDomain` as an argument.
 *
 *     ```typescript
 *     import { DataDomain } from './DataDomain';
 *     import { DomainImpactAnalysis } from './DomainImpactAnalysis';
 *
 *     const dataDomain = new DataDomain();
 *     // ... add some data to the domain
 *     const analyzer = new DomainImpactAnalysis(dataDomain);
 *     ```
 *
 * 2.  **Performing Analysis:** Use the `deleteAnalysis()` method to generate an `DomainImpactReport` for
 *     a specific deletion.
 *     Provide the `key` and `kind` of the object to be deleted.
 *
 *     ```typescript
 *     import { DomainEntityKind } from '../models/kinds.js';
 *     // ...
 *     const entityKey = 'some-entity-key';
 *     const report = analyzer.deleteAnalysis(entityKey, DomainEntityKind);
 *     ```
 *
 * 3.  **Interpreting the Report:** Examine the `DomainImpactReport` 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.severity` 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.severity === 'error') {
 *           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 element types:
 *
 * - **Namespaces (`DomainNamespaceKind`):** Deleting a namespace also impacts all its child namespaces,
 *   data models, entities, properties, and associations.
 * - **Data Models (`DomainModelKind`):** Deleting a data model impacts all its entities, properties, and associations.
 * - **Entities (`DomainEntityKind`):** Deleting an entity impacts its properties, associations, and any other
 *   entities that have it as a parent or target in an association.
 * - **Properties (`DomainPropertyKind`):** Deleting a property impacts the entity it belongs to.
 * - **Associations (`DomainAssociationKind`):** Deleting an association impacts the entity it belongs to.
 * - **Data Domain (`DataDomainKind`):** Deleting a data domain is not supported.
 *
 * ## Example: Deleting an Entity
 *
 * Consider the following scenario:
 *
 * ```
 * DataDomain
 *   - 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": "DomainEntityKind",
 *   "impact": [
 *     {
 *       "key": "Product",
 *       "kind": "DomainEntityKind",
 *       "type": "delete",
 *       "impact": "The entity with key Product will be deleted.",
 *       "severity": "warning"
 *     },
 *     {
 *       "key": "SpecialProduct",
 *       "kind": "DomainEntityKind",
 *       "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.",
 *       "severity": "error",
 *       "relationship": "child"
 *     },
 *     {
 *       "key": "category",
 *       "kind": "DomainAssociationKind",
 *       "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.",
 *       "severity": "error"
 *     },
 *     {
 *       "key": "name",
 *       "kind": "DomainPropertyKind",
 *       "type": "delete",
 *       "impact": "The property with key name will be deleted.",
 *       "severity": "info"
 *     }
 *   ],
 *   "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
 *
 * ### `DomainImpactKinds`
 *
 * -   **Description:** A type alias for the kinds of data domain elements that can be analyzed.
 * -   **Values:**
 *     - `DomainNamespaceKind`
 *     - `DomainEntityKind`
 *     - `DomainModelKind`
 *     - `DomainPropertyKind`
 *     - `DomainAssociationKind`
 *     - `DataDomainKind`
 *
 * ## Error Handling
 *
 * The `DomainImpactAnalysis` class does not throw errors. Instead, it uses the `DomainImpactReport` to communicate
 * the results of the analysis, including any blocking impacts.
 *
 * ## Best Practices
 *
 * - **Always Analyze Before Deleting:** Before deleting any data domain element, always use `DomainImpactAnalysis`
 *   to understand the consequences.
 * - **Handle Blocking Impacts:** Pay close attention to `severity = "error"` 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.
 */
export class DomainImpactAnalysis {
    root;
    constructor(root) {
        this.root = root;
    }
    /**
     * 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.
     */
    deleteAnalysis(key, kind) {
        const report = {
            key,
            kind,
            impact: [],
            canProceed: true,
        };
        this.createDeleteImpact(key, kind, key, report.impact);
        report.canProceed = this.computeCanProceed(report.impact);
        return report;
    }
    /**
     * Generates a report of how the data domain will be impacted by the removal of a foreign namespace.
     * @param key The key of the foreign namespace.
     * @returns The delete impact analysis report.
     */
    removeForeignNamespaceAnalysis(key) {
        const report = {
            key,
            kind: DomainNamespaceKind,
            impact: [],
            canProceed: true,
        };
        this.createRemoveForeignNamespaceImpact(key, report.impact);
        report.canProceed = this.computeCanProceed(report.impact);
        return report;
    }
    /**
     * Computes whether the deletion can proceed based on the impact report.
     * @param impact The list of impacts from the analysis.
     * @returns `true` if the deletion can proceed, `false` otherwise.
     */
    computeCanProceed(impact) {
        return !impact.some((item) => item.severity === 'error');
    }
    createDeleteImpact(key, kind, rootKey, impact) {
        switch (kind) {
            case DomainNamespaceKind:
                this.deleteNamespaceAnalysis(key, rootKey, impact);
                break;
            case DomainModelKind:
                this.deleteModelAnalysis(key, rootKey, impact);
                break;
            case DomainEntityKind:
                this.deleteEntityAnalysis(key, rootKey, impact);
                break;
            case DomainPropertyKind:
                this.deletePropertyAnalysis(key, impact);
                break;
            case DomainAssociationKind:
                this.deleteAssociationAnalysis(key, impact);
                break;
            default:
            // ignore unknown kinds
        }
    }
    deleteNamespaceAnalysis(key, rootKey, impact) {
        const ns = this.root.findNamespace(key);
        if (!ns) {
            return;
        }
        impact.push({
            key: ns.key,
            kind: ns.kind,
            type: 'delete',
            impact: `The ${ns.info.getLabel()} ${this.kindToLabel(DomainNamespaceKind)} will be deleted.`,
            severity: 'info',
        });
        for (const child of ns.listNamespaces()) {
            this.deleteNamespaceAnalysis(child.key, rootKey, impact);
        }
        for (const child of ns.listModels()) {
            this.deleteModelAnalysis(child.key, rootKey, impact);
        }
    }
    deleteModelAnalysis(key, rootKey, impact) {
        const model = this.root.findModel(key);
        if (!model) {
            return;
        }
        impact.push({
            key: model.key,
            kind: model.kind,
            type: 'delete',
            impact: `The ${model.info.getLabel()} ${this.kindToLabel(DomainModelKind)} will be deleted.`,
            severity: 'info',
        });
        for (const child of model.listEntities()) {
            this.deleteEntityAnalysis(child.key, rootKey, impact);
        }
    }
    deleteEntityAnalysis(key, rootKey, impact) {
        const entity = this.root.findEntity(key);
        if (!entity) {
            return;
        }
        impact.push({
            key: entity.key,
            kind: entity.kind,
            type: 'delete',
            impact: `The ${entity.info.getLabel()} ${this.kindToLabel(DomainEntityKind)} will be deleted.`,
            severity: 'info',
        });
        // We need to know whether the entity is a parent of another entity
        for (const info of this.root.graph.inEdges(key)) {
            const edge = this.root.graph.edge(info);
            if (!edge || edge.type !== 'parent') {
                continue;
            }
            const childEntity = this.root.findEntity(info.v);
            if (!childEntity) {
                continue;
            }
            if (childEntity.isChildOf(rootKey)) {
                // No need to include this child as it itself is being deleted
                continue;
            }
            const pLabel = entity.info.getLabel();
            const cLabel = childEntity.info.getLabel();
            impact.push({
                key: childEntity.key,
                kind: childEntity.kind,
                type: 'delete',
                impact: `The "${cLabel}" ${this.kindToLabel(DomainEntityKind)} 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.`,
                relationship: 'child',
                severity: 'error',
            });
        }
        // We need to know whether there's another entity that has an association to this entity.
        for (const edgeInfo of this.root.graph.inEdges(entity.key)) {
            const edge = this.root.graph.edge(edgeInfo);
            if (!edge || edge.type !== 'association') {
                continue;
            }
            const association = this.root.findAssociation(edgeInfo.v);
            if (!association) {
                continue;
            }
            const targetEntity = association.getParentInstance();
            if (!targetEntity) {
                continue;
            }
            if (targetEntity.isChildOf(rootKey)) {
                // No need to include this association as the target entity itself is being deleted
                continue;
            }
            const aLabel = association.info.getLabel();
            const eLabel = entity.info.getLabel();
            impact.push({
                key: association.key,
                kind: association.kind,
                type: 'delete',
                impact: `The ${aLabel} ${this.kindToLabel(DomainAssociationKind)} will be broken because it has a target to ${eLabel}.`,
                resolution: `The ${aLabel} ${this.kindToLabel(DomainAssociationKind)} will be removed from ${eLabel}.`,
                severity: 'error',
            });
        }
        for (const child of entity.listProperties()) {
            this.deletePropertyAnalysis(child.key, impact);
        }
        for (const child of entity.listAssociations()) {
            this.deleteAssociationAnalysis(child.key, impact);
        }
    }
    deletePropertyAnalysis(key, impact) {
        const property = this.root.findProperty(key);
        if (!property) {
            return;
        }
        impact.push({
            key: property.key,
            kind: property.kind,
            type: 'delete',
            impact: `The ${property.info.getLabel()} ${this.kindToLabel(DomainPropertyKind)} will be deleted.`,
            severity: 'info',
        });
    }
    deleteAssociationAnalysis(key, impact) {
        const association = this.root.findAssociation(key);
        if (!association) {
            return;
        }
        impact.push({
            key: association.key,
            kind: association.kind,
            type: 'delete',
            impact: `The ${association.info.getLabel()} ${this.kindToLabel(DomainAssociationKind)} will be deleted.`,
            severity: 'info',
        });
    }
    kindToLabel(kind) {
        switch (kind) {
            case DomainNamespaceKind:
                return 'namespace';
            case DomainEntityKind:
                return 'entity';
            case DomainModelKind:
                return 'data model';
            case DomainPropertyKind:
                return 'property';
            case DomainAssociationKind:
                return 'association';
            default:
                return 'unknown';
        }
    }
    createRemoveForeignNamespaceImpact(key, impact) {
        const foreignNamespace = this.root.dependencies.get(key);
        if (!foreignNamespace) {
            return;
        }
        // Check for parent relationships to foreign entities
        for (const entity of this.root.listEntities()) {
            for (const info of this.root.graph.outEdges(entity.key)) {
                const edge = this.root.graph.edge(info);
                if (!edge) {
                    continue;
                }
                if (edge.type === 'parent' && edge.domain === key) {
                    // the parent is in the foreign namespace
                    const parentEntity = this.root.findEntity(info.w);
                    if (!parentEntity) {
                        continue;
                    }
                    const eLabel = entity.info.getLabel();
                    const pLabel = parentEntity.info.getLabel();
                    impact.push({
                        key: entity.key,
                        kind: entity.kind,
                        type: 'delete',
                        impact: `The "${eLabel}" ${this.kindToLabel(DomainEntityKind)} will become an orphan because its parent "${pLabel}" is in the foreign namespace "${foreignNamespace.key}".`,
                        resolution: `The "${pLabel}" entity will be removed as the parent of the "${eLabel}" entity.`,
                        relationship: 'child',
                        severity: 'error',
                    });
                }
                else if (edge.type === 'association') {
                    const association = this.root.findAssociation(info.w);
                    if (!association) {
                        continue;
                    }
                    const targets = association.targets.filter((t) => t.domain === key);
                    if (targets.length) {
                        for (const info of targets) {
                            const targetEntity = this.root.findForeignEntity(info.key, info.domain);
                            if (!targetEntity) {
                                continue;
                            }
                            const aLabel = association.info.getLabel();
                            const eLabel = entity.info.getLabel();
                            const tLabel = targetEntity.info.getLabel();
                            impact.push({
                                key: association.key,
                                kind: association.kind,
                                type: 'delete',
                                impact: `The "${aLabel}" ${this.kindToLabel(DomainAssociationKind)} from "${eLabel}" will be broken because it targets "${tLabel}" in the foreign namespace "${foreignNamespace.key}".`,
                                resolution: `The "${aLabel}" ${this.kindToLabel(DomainAssociationKind)} will be removed from "${eLabel}".`,
                                severity: 'error',
                            });
                        }
                    }
                }
            }
        }
    }
}
//# sourceMappingURL=DomainImpactAnalysis.js.map