domain/entities/Element.js

/**
 * @module Domain/Entities
 * @description
 * 🏗️ **Domain Layer - Circuit Elements**
 *
 * Core entities representing physical circuit components in the JSCircuit Editor.
 * This module provides the foundation for all circuit elements following Domain-Driven Design principles.
 */

import { Label } from '../valueObjects/Label.js';
import { Position } from '../valueObjects/Position.js';
import { Properties } from '../valueObjects/Properties.js';

/**
 * @class Element
 * @abstract
 * @description
 * Abstract base class for all circuit elements in the JSCircuit Editor.
 *
 * This class serves as the foundation for all physical circuit components (resistors,
 * capacitors, wires, etc.) and defines the common interface that all elements must implement.
 *
 * **🔧 Extension Point for Developers:**
 * To create a new circuit element type, extend this class and implement the required
 * properties and methods. See the examples below for implementation patterns.
 *
 * **Key Properties:**
 * - `id`: Unique identifier for the element
 * - `nodes`: Array of Position objects defining connection points
 * - `label`: Optional text label for display
 * - `type`: Element type identifier (set by subclasses)
 * - `properties`: Container for element-specific properties
 *
 * @example
 * // Creating a new element type
 * class CustomElement extends Element {
 *   constructor(id, nodes, label, properties) {
 *     super(id, nodes, label, properties);
 *     this.type = 'custom';
 *   }
 *
 *   describe() {
 *     return `Custom element ${this.id}`;
 *   }
 * }
 *
 * @example
 * // Using an existing element
 * const resistor = new Resistor(
 *   'R1',
 *   [new Position(0, 0), new Position(50, 0)],
 *   new Label('1kΩ'),
 *   new Properties({ resistance: 1000 })
 * );
 */
export class Element {
    /**
     * Creates an instance of an Element.
     * 
     * @param {string} id - The unique identifier for the element.
     * @param {Position[]} nodes - The list of node positions.
     * @param {Label|null} label - The label of the element (optional).
     * @param {Properties} properties - A container for the element's specific properties.
     * @throws {Error} If attempting to instantiate the abstract class directly.
     */
    constructor(id, nodes, label = null, properties) 
        {
        if (new.target === Element) {
            throw new Error("Cannot instantiate abstract class Element directly.");
        }
        if (!Array.isArray(nodes) || !nodes.every(t => t instanceof Position)) {
            throw new Error("Nodes must be an array of Position instances.");
        }
        if (label !== null && !(label instanceof Label)) {
            throw new Error("Label must be an instance of Label or null.");
        }
        if (!(properties instanceof Properties)) {
            throw new Error("Properties must be an instance of Properties.");
        }

        this.id = id;
        this.nodes = nodes;
        this.label = label;
        this.type = null; // Each subclass must define its type
        this.properties = properties; // Properties container
    }

    /**
     * Describes the element with its type, id, nodes, label, and properties.
     * 
     * @returns {string} A string description of the element.
     */
    describe() {
        const labelText = this.label ? `, label: "${this.label}"` : '';
        const nodesText = this.nodes.map(t => `(${t.x}, ${t.y})`).join(', ');
        return `${this.type} (${this.id}): nodes: [${nodesText}]${labelText}, properties: ${this.properties.describe()}`;
    }

    /**
     * Gets the properties container for this element.
     *
     * @returns {Properties} The properties container.
     */
    getProperties() {
        return this.properties;
    }
}