```
class PluginNotFoundError extends Error {
  constructor(functionRef, pluginClass) {
    const functionName = functionRef.name || 'UnknownFunction';
    const pluginClassName = pluginClass.name || pluginClass;
    const message = `${functionName}: No plugin context found for the class: "${pluginClassName}". Ensure the plugin is registered correctly and the class is valid.`;
    super(message);
    this.name = 'PluginNotFoundError';
  }
}

class Unit {
  constructor(value, type = null) {
    this.type = type;
    this.suffix = typeof type === 'string' ? type : null;
    this.assign(value);
  }

  assign(value) {
    if (value === this.value || value === this.css) {
      return;
    }
    if (this.type === null) {
      this.css = this.value = value;
      return;
    }
    
    const type = typeof value;
    let parsedValue;

    if (type === 'number') {
      parsedValue = value;
    } else if (type === 'string') {
      parsedValue = parseFloat(value);
      if (isNaN(parsedValue)) {
        throw new Error(`Invalid value: ${value}`);
      }
    } else {
      throw new Error(`Invalid type for assignment: Expected number or string, received ${type}.`);
    }
    
    const valueStr = parsedValue.toString();
    this.value = parsedValue;
    this.css = this.suffix ? (valueStr + this.suffix) : valueStr;
  }

  clone() {
    const cloned = Object.create(Unit.prototype);
    Object.assign(cloned, this);
    return cloned;
  }

  static px(value) {
    return new Unit(value, 'px');
  }

  static number(value) {
    return new Unit(value, Number);
  }

  static identity(value) {
    return new Unit(value);
  }

  static unitfyProperties (obj) {
    for (const key in obj) {
      const val = obj[key];
      if (!(val instanceof Unit)) {
        obj[key] = Unit.identity(val);
      }
    }
    return obj;
  }
}


class ParameterPlugin {
  params = {};
  pluginName = '';
  cssVariableMap = null;
  host = null;

  initialize(newPluginContexts, options = {}) {
    const [globalContext, instanceContext] = newPluginContexts(this.params);
    this.globalContext = globalContext;
    this.instanceContext = instanceContext;
    if (!this.cssVariableMap) {
      this.cssVariableMap = this.createCSSVariableMap();
    }
    Object.assign(globalContext, options);
    this.compileCSSRules();
  }

  compileCSSRules() {
    this.css = [this.baseRules, this.additionalCSS].map(css => 
      typeof css === 'function' ? css(this.globalContext) : css
    );
  }

  static createPluginClass(config) {
    return class extends ParameterPlugin {
      constructor(...args) {
        super();
        Object.assign(this, config);
        this.initialize(...args);
      }
    };
  }
  
  camelToKebabRegex(str) {
    if (!str) return '';
  
    return str
      // Insert a hyphen before all uppercase letters except the first character
      .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
      // Convert the entire string to lowercase
      .toLowerCase();
  }

  createCSSVariableMap() {
    const map = {};
    const {pluginName, params} = this;
    
    for (const key in params) {
        const kebabCaseKey = this.camelToKebabRegex(key);
        map[key] = pluginName ? `--${pluginName}-${kebabCaseKey}` : `--${kebabCaseKey}`;
    }
    return map;
  }

  getCSS() {
    return this.css;
  }

  attach(host) {
    this.host = host || this.host;
    this.setGlobalCSS();
  }

  detach() {
    this.unsetGlobalCSS();
    this.host = null;
  }

  updateGlobal(params = {}) {
      Object.assign(this.globalContext, params);
      this.setGlobalCSS();
      this.compileCSSRules();
  }

  updateRectElement(rectElement, params) {
      const {instanceContext, cssVariableMap} = this;
      const rectContext = instanceContext.get(rectElement);
      for (const key in params) {
          const cssVar = cssVariableMap[key];
          if (cssVar) {
            rectContext[key] = params[key];
            rectElement.style.setProperty(cssVar, rectContext[key].css);
          }
      }
  }

  toggleGlobalCSS(enable) {
    if (!this.host) return;
    const { globalContext, cssVariableMap, host } = this;
  
    for (const key in globalContext) {
      const cssVar = cssVariableMap[key];
      if (cssVar) {
        host.style[enable ? 'setProperty' : 'removeProperty'](
          cssVar,
          enable ? globalContext[key].css : undefined
        );
      }
    }
  }
  
  setGlobalCSS() { this.toggleGlobalCSS(true); }
  unsetGlobalCSS() { this.toggleGlobalCSS(false); }
}

const BasePlugin = ParameterPlugin.createPluginClass({
  params: {
    width: Unit.px(20),
    height: Unit.px(20),
    x: Unit.px(0),
    y: Unit.px(0),
    opacity: Unit.number(1),
    fill: 'cyan',
    borderSize: Unit.px(2),
    borderColor: '#000'
  },
  baseRules: `
    width: var(--width);
    height: var(--height);
    left: var(--x);
    top: var(--y);
    opacity: var(--opacity);
    background-color: var(--fill);
    border: var(--border-size) solid var(--border-color);
  `,
});


const VertexPlugin = ParameterPlugin.createPluginClass({
  params: {
    size: Unit.px(24),
    color: '#3498db',
    autohide: false,
  },
  pluginName: 'vertex',
  baseRules: `
    --extended-size: calc(var(--border-size, 2px) + var(--vertex-size));
    --offset: calc(-0.5 * var(--extended-size));
    --adjusted-width: calc(var(--width) - var(--border-size, 2px));
    --adjusted-height: calc(var(--height) - var(--border-size, 2px));
    `,
  additionalCSS: ({autohide}) => `
    .rect-region.vertex::before {
      content: '';
      transition: opacity 0.3s ease-in-out;
      opacity: 1;
      position: absolute;
      width: var(--vertex-size);
      height: var(--vertex-size);
      background-color: var(--vertex-color);
      box-sizing: border-box;

      /* Intermediate variables */

      /* Positioning the first vertex */
      top: var(--offset);
      left: var(--offset);

      /* Creating three more vertices using box-shadow, positioned at each corner */
      box-shadow:
        var(--adjusted-width) 0 0 0 var(--vertex-color),
        var(--adjusted-width) var(--adjusted-height) 0 0 var(--vertex-color),
        0 var(--adjusted-height) 0 0 var(--vertex-color);
    }
    .rect-region.vertex::after {
      content: '';
      position: absolute;
      top: var(--offset);
      left: var(--offset);
      width: calc(100% + var(--extended-size));
      height: calc(100% + var(--extended-size));
    }
    ${autohide.value ? `
      .rect-region.vertex:not(:hover)::before {
        opacity: 0;
      }
    ` : ''}
  `
});

class RectCursorAction {
  static {
    // Action Type Constants
    const RESIZE_NW = 0;  // Resize from the North-West corner
    const RESIZE_NE = 1;  // Resize from the North-East corner
    const RESIZE_SW = 2;  // Resize from the South-West corner
    const RESIZE_SE = 3;  // Resize from the South-East corner
    const RESIZE_N = 4;   // Resize from the North edge
    const RESIZE_S = 5;   // Resize from the South edge
    const RESIZE_W = 6;   // Resize from the West edge
    const RESIZE_E = 7;   // Resize from the East edge
    const MOVE = 8;       // Move the rectangle
    const NULL = 9;       // No action

    // Action Lookup Table
    const ACTION_TABLE = [
      [RESIZE_NW, RESIZE_NW,  RESIZE_NW,  NULL,     RESIZE_NE,  RESIZE_NE, RESIZE_NE], // Y=0
      [RESIZE_NW, RESIZE_NW,  RESIZE_NW,  RESIZE_N, RESIZE_NE,  RESIZE_NE, RESIZE_NE], // Y=1
      [RESIZE_NW, RESIZE_NW,  RESIZE_NW,  MOVE,     RESIZE_NE,  RESIZE_NE, RESIZE_NE], // Y=2
      [NULL,      RESIZE_W,   MOVE,       MOVE,     MOVE,       RESIZE_E,  NULL     ], // Y=3
      [RESIZE_SW, RESIZE_SW,  RESIZE_SW,  MOVE,     RESIZE_SE,  RESIZE_SE, RESIZE_SE], // Y=4
      [RESIZE_SW, RESIZE_SW,  RESIZE_SW,  RESIZE_S, RESIZE_SE,  RESIZE_SE, RESIZE_SE], // Y=5
      [RESIZE_SW, RESIZE_SW,  RESIZE_SW,  NULL,     RESIZE_SE,  RESIZE_SE, RESIZE_SE], // Y=6
    ];

    // Change Tables
    const WIDTH_CHANGE_TABLE  = [-1,  1, -1,  1,  0, 0, -1,  1];
    const HEIGHT_CHANGE_TABLE = [-1, -1,  1,  1, -1, 1,  0,  0];
    const X_CHANGE_TABLE      = [ 1,  0,  1,  0,  0, 0,  1,  0];
    const Y_CHANGE_TABLE      = [ 1,  1,  0,  0,  1, 0,  0,  0];
    const CURSOR_TABLE        = ['nw-resize', 'ne-resize', 'sw-resize', 'se-resize', 'n-resize', 's-resize', 'w-resize', 'e-resize', 'move', 'auto'];

    // Assign to this
    this.RESIZE_NW = RESIZE_NW;
    this.RESIZE_NE = RESIZE_NE;
    this.RESIZE_SW = RESIZE_SW;
    this.RESIZE_SE = RESIZE_SE;
    this.RESIZE_N = RESIZE_N;
    this.RESIZE_S = RESIZE_S;
    this.RESIZE_W = RESIZE_W;
    this.RESIZE_E = RESIZE_E;
    this.MOVE = MOVE;
    this.NULL = NULL;
    this.ACTION_TABLE = ACTION_TABLE;
    this.WIDTH_CHANGE_TABLE = WIDTH_CHANGE_TABLE;
    this.HEIGHT_CHANGE_TABLE = HEIGHT_CHANGE_TABLE;
    this.X_CHANGE_TABLE = X_CHANGE_TABLE;
    this.Y_CHANGE_TABLE = Y_CHANGE_TABLE;
    this.CURSOR_TABLE = CURSOR_TABLE;
  }

  static lastAction = this.NULL;

  static getCursorAction(rect, mouseevent, bbox) {
    const {width, height} = bbox ?? rect.bbox();

    const borderSize = rect.getParam('borderSize', BasePlugin) ?? 6;
    const vertexSize = rect.getParam('size', VertexPlugin) ?? 0;
    
    const {left, top} = rect.rectElement.getBoundingClientRect();

    const cursorOffsetX = mouseevent.clientX - left;
    const cursorOffsetY = mouseevent.clientY - top;

    const xOffset = width - borderSize;
    const yOffset = height - borderSize;

    const halfVertexMinusBorder = (vertexSize - borderSize) / 2;
    const halfVertexPlusBorder = (vertexSize + borderSize) / 2;

    const minX = -halfVertexMinusBorder;
    const maxX = halfVertexPlusBorder + xOffset;

    const minY = minX;
    const maxY = halfVertexPlusBorder + yOffset;

    // Check if cursorOffsetX and cursorOffsetY are outside the boundaries
    if (cursorOffsetX < minX || cursorOffsetX > maxX || cursorOffsetY < minY || cursorOffsetY > maxY) {
      return this.NULL;
    }

    // Sorted array excluding the first and last elements
    const xSorted = [
      0,
      borderSize,
      halfVertexPlusBorder,
      -halfVertexMinusBorder + xOffset,
      width - borderSize,
      width
    ];

    const ySorted = [
      0,
      borderSize,
      halfVertexPlusBorder,
      -halfVertexMinusBorder + yOffset,
      height - borderSize,
      height
    ];

    let xi = 0;
    let yi = 0;

    // Find the segment index for cursorOffsetX
    for (; xi < 6 && cursorOffsetX >= xSorted[xi]; xi++) { }

    // Find the segment index for cursorOffsetY
    for (; yi < 6 && cursorOffsetY >= ySorted[yi]; yi++) { }

    return this.lastAction = this.ACTION_TABLE[yi][xi];
  }

  static calculateResizedRect(initialRect, action, dx, dy, minWidth, minHeight) {
    const { x: initialX, y: initialY, width: initialWidth, height: initialHeight } = initialRect;

    const width = Math.max(initialWidth + this.WIDTH_CHANGE_TABLE[action] * dx, minWidth);
    const height = Math.max(initialHeight + this.HEIGHT_CHANGE_TABLE[action] * dy, minHeight);

    const x = initialX + this.X_CHANGE_TABLE[action] * (initialWidth - width);
    const y = initialY + this.Y_CHANGE_TABLE[action] * (initialHeight - height);

    return { x, y, width, height };
  }
}

class MouseButton {
  static LEFT = 0;
  static MIDDLE = 1;
  static RIGHT = 2;
}

class DndPlugin {
  /**
   * Constructor initializes the plugin's state and binds event handlers.
   */
  constructor(_, __, params) {
    // Current rectangle being interacted with
    this.currentRect = null;

    // Current action being performed (e.g., MOVE, RESIZE_NW)
    this.currentAction = RectCursorAction.NULL;

    // Host element to which the plugin is attached
    this.host = null;

    // Starting mouse coordinates when interaction begins
    this.initialMouseX = 0;
    this.initialMouseY = 0;

    // Initial state of the rectangle before interaction
    this.initialRectBbox = null;

    // Bound event handlers for mouse move and mouse up events
    this.mouseMoveHandler = this.onMouseMoveDocument.bind(this);
    this.mouseUpHandler = this.onMouseUp.bind(this);

    this.rectElementStates = new WeakMap();

    // Flags and storage for requestAnimationFrame optimization
    this.isAnimating = false;  // Indicates if an animation frame is already scheduled

    // Minimum size constraints to prevent the rectangle from becoming too small
    this.minRectWidth = params?.minRectWidth ?? 50;  // Minimum width in pixels
    this.minRectHeight = params?.minRectHeight ?? 50; // Minimum height in pixels
    this.preventPropagation = params?.preventPropagation ?? true;
  }

  /**
   * Attaches the plugin to a host element.
   * @param {HTMLElement} host - The host element.
   * @returns {null} Always returns null.
   */
  attach(host) {
    if (host) {
      this.host = host;
    }
    return null;
  }

  enable(rect, rectElement) {
    const callback = this.onMouseDown.bind(this, rect);
    const states = [callback];
    this.rectElementStates.set(rectElement, states);
    rectElement.addEventListener('mousedown', callback);
    return states;
  }

  disable(rect, rectElement) {
    const states = this.rectElementStates.get(rectElement);
    
    if (states) {
      const [callback] = states;
      rectElement.removeEventListener('mousedown', callback);
      this.rectElementStates.delete(rectElement);
    }
    return states;
  }

  onMouseDown(rect, event, useLastAction = false) {
    if (event.button !== MouseButton.LEFT) {
      return;
    }

    const rectElement = event.target;
    if (!rectElement) return;

    const bbox = rect.bbox();


    // Lookup the action based on the position of cursor in particular [y, x] segment
    this.currentAction = useLastAction ? RectCursorAction.lastAction: RectCursorAction.getCursorAction(rect, event, bbox);
    // If the action is NULL, no operation should be performed
    if (this.currentAction === RectCursorAction.NULL) {
      return;
    }

    // Store starting mouse coordinates
    this.initialMouseX = event.clientX;
    this.initialMouseY = event.clientY;

    // Store the current rectangle and its initial state
    this.currentRect = rect;
    this.initialRectBbox = bbox;

    // Prevent default browser behavior (e.g., text selection)
    event.preventDefault();

    // top rect gets the mousedown only
    if (this.preventPropagation) {
      event.stopPropagation();
    }
    // Add global event listeners for mousemove and mouseup
    document.addEventListener('mousemove', this.mouseMoveHandler);
    document.addEventListener('mouseup', this.mouseUpHandler);

    return true;
  }

  onMouseMoveDocument(event) {
    if (!this.currentRect || this.isAnimating) return;
    this.isAnimating = true;

    const dx = event.clientX - this.initialMouseX;
    const dy = event.clientY - this.initialMouseY;

    if (this.currentAction === RectCursorAction.MOVE) {
      this.moveRect(this.currentRect, this.initialRectBbox, dx, dy);
    } else {
      this.resizeRect(this.currentRect, this.initialRectBbox, dx, dy);
    }
  }

  moveRect(rect, initialRectBbox, dx, dy) {
    let {x, y} = initialRectBbox;
    x += dx;
    y += dy;

    requestAnimationFrame(() => {
      rect.update({x, y});
      // Reset the animation flag
      this.isAnimating = false;
    });
  }

  resizeRect(rect, initialRectBbox, dx, dy) {
    const resizedRectBbox = RectCursorAction.calculateResizedRect(
      initialRectBbox,
      this.currentAction,
      dx,
      dy,
      this.minRectWidth,
      this.minRectHeight
    );

    requestAnimationFrame(() => {
      this.updateRect(rect, initialRectBbox, resizedRectBbox);
      // Reset the animation flag and latestRect storage
      this.isAnimating = false;
    });
  }

  /**
   * Updates the rectangle's properties based on the new state.
   * @param {Object} newRect - The new state of the rectangle.
   */
  updateRect(rect, initialRectBbox, newRect) {
    const updates = {};

    // Compare each property and collect updates that have changed
    for (const key in newRect) {
      if (newRect[key] !== initialRectBbox[key]) {
        updates[key] = newRect[key];
      }
    }

    rect.update(updates);
  }

  /**
   * Handles the mouseup event. Cleans up event listeners and resets state.
   */
  onMouseUp() {
    // Remove global event listeners for mousemove and mouseup
    document.removeEventListener('mousemove', this.mouseMoveHandler);
    document.removeEventListener('mouseup', this.mouseUpHandler);

    // Reset the current rectangle and currentAction
    this.currentRect = null;

    // Reset animation flags and storage
    this.isAnimating = false;
  }
}

class MousePlugin extends DndPlugin {
  deleteButtonSvg = `
      <svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
          <line x1="1" y1="1" x2="11" y2="11" stroke="white" stroke-width="2"/>
          <line x1="11" y1="1" x2="1" y2="11" stroke="white" stroke-width="2"/>
      </svg>
  `;
  css = [null, 
    `
      .rect-region .del-btn {
        position: absolute;
        top: 4px;
        right: 4px;
        background: rgba(231, 76, 60, 0.8);
        color: white;
        border: none;
        border-radius: 50%;
        width: 20px;
        height: 20px;
        display: flex;
        cursor: pointer;
        align-items: center;
        justify-content: center;
        padding: 0;
        box-shadow: 0 0 5px rgba(0,0,0,0.3);
        transition: background 0.2s, transform 0.2s, opacity 0.3s ease;
        opacity: 0;
        z-index: 2000;
      }
      .rect-region .del-btn:hover {
        background: rgba(192, 57, 43, 0.9);
        transform: scale(1.1);
      }
      .rect-region:hover .del-btn, .rect-region .del-btn:hover {
        opacity: 1;
      }
      .rect-region.vertex.on-drag::before {
        opacity: 1;
      }
  `];

  constructor(...args) {
      super(...args);
      this.boundMouseLeave = this.onMouseLeave.bind(this);
  }

  attach(host) {
    super.attach(host);
  }

  detach() {
    if (!this.host) {
      return;
    }
    this.host.style.removeProperty('cursor');
    this.host = null;
  }

  getCSS() {
    return this.css;
  }

  enable(rect, rectElement) {
    const states = super.enable(rect, rectElement);
    const callback = this.onMouseMoveElement.bind(this, rect);
    const deleteButton = this.addDeleteButton(rect, rectElement);
    rectElement.addEventListener('mousemove', callback);
    rectElement.addEventListener('mouseleave', this.boundMouseLeave);
    states.push(callback);
    states.push(deleteButton);
  }

  disable(rect, rectElement) {
    const states = super.disable(rect, rectElement);
    if (states) {
      const [, callback, deleteButton] = states;
      rectElement.removeEventListener('mousemove', callback);
      rectElement.removeEventListener('mouseleave', this.boundMouseLeave);
      deleteButton?.remove();
    }
  }

  addDeleteButton(rect, rectElement) {
    const deleteButton = this.createDeleteButton();
    deleteButton.addEventListener('click', () => {
      this.disable(rect, rectElement);
      rect.remove();
    });
    rectElement.appendChild(deleteButton);
    return deleteButton;
  }

  createDeleteButton() {
    const btn = document.createElement('button');
    btn.className = 'del-btn';
    btn.innerHTML = this.deleteButtonSvg;
    return btn;
  }

  onMouseDown(rect, event) {
    super.onMouseDown(rect, event, true);
    this.currentRect?.rectElement.classList.add('on-drag');
  }

  onMouseMoveElement(rect, event) {
    if (this.currentRect) {
      return;
    }
    const { currentAction: lastAction } = this;
    const currentAction = RectCursorAction.getCursorAction(rect, event);
    this.currentAction = currentAction;
    if (lastAction !== currentAction) {
      this.host?.style.setProperty('cursor', RectCursorAction.CURSOR_TABLE[currentAction]);
    }
  }

  onMouseLeave() {
    if (this.currentRect) {
      return;
    }
    this.currentAction = RectCursorAction.lastAction = RectCursorAction.NULL;
    this.host?.style.setProperty('cursor', RectCursorAction.CURSOR_TABLE[RectCursorAction.NULL]);
  }

  onMouseUp() {
    // remove drag class before clearing currentRect
    this.currentRect?.rectElement.classList.remove('on-drag');
    super.onMouseUp();
  }
}

// Rectangle Wrapper
class Rect {
  /**
   * Creates a Rect instance.
   * @param {HTMLElement} rect - The rectangle element.
   * @param {RectRegionShapeFactory} factory - Reference to the factory.
   */
  constructor(rectElement, factory) {
    this.rectElement = rectElement;
    this.factory = factory;
    this.activePlugins = new Map();
  }

  togglePlugins(pluginClasses, enable) {
    for (const pluginClass of pluginClasses) {
      if (!enable && !this.activePlugins.has(pluginClass)) {
        continue;
      }

      const plugin = this.factory.getPluginByClass(pluginClass);
      if (!plugin) {
        throw new PluginNotFoundError(enable ? this.enablePlugins : this.disablePlugins, pluginClass);
      }

      if (plugin.pluginName && typeof plugin.pluginName === 'string') {
        this.rectElement.classList[enable ? 'add' : 'remove'](plugin.pluginName);
      }

      plugin[enable ? 'enable' : 'disable']?.(this, this.rectElement);
      this.activePlugins[enable ? 'set' : 'delete'](pluginClass, plugin);
    }
  }

  enablePlugins(pluginClasses) {
    this.togglePlugins(pluginClasses, true);
  }

  disablePlugins(pluginClasses) {
    this.togglePlugins(pluginClasses, false);
  }

  isPluginEnabled(pluginClass) {
    return this.activePlugins.has(pluginClass);
  }

  /**
   * Removes the rectangle from the DOM and cleans up plugin-specific settings.
   */
  remove() {
    if (!this.rectElement) {
      return;
    }
    this.disablePlugins(this.activePlugins);
    this.rectElement.remove();
    this.factory._onRectRemove(this, this.rectElement);
    this.rectElement = null;
  }

  update(params, pluginClass = BasePlugin) {
    const plugin = this.factory.getPluginByClass(pluginClass);
    if (!plugin) {
      throw new PluginNotFoundError(this.update, pluginClass);
    }
    if (plugin) {
      plugin.updateRectElement?.(this.rectElement, params);
    } else {
      throw new Error(`No plugin context found for the class: "${pluginClass}". Ensure the plugin is registered correctly and the class is valid.`);
    }
  }

  updateMany(pluginClassParamPairs = []) {
    for (let i = 0; i < pluginClassParamPairs.length; i++) {
      const [pluginClass, params]  = pluginClassParamPairs[i];
      this.update(params, pluginClass);
    }
  }

  getPluginRectContext(pluginClass) {
    const contexts = this.factory.getPluginContext(pluginClass);
    if (!contexts) {
      throw new PluginNotFoundError(this.getParam, pluginClass);
    }
    const [_, instanceContext] = contexts;
    return instanceContext.get(this.rectElement);
  }

  getParam(name, pluginClass = BasePlugin) {
    return this.getPluginRectContext(pluginClass)?.[name]?.value;
  }

  bbox(pluginClass = BasePlugin) {
    const { x: { value: x }, y: { value: y }, width: { value: width }, height: { value: height } } = this.getPluginRectContext(pluginClass);
    return { x, y, width, height };
  }
}

class DefaultMap extends Map {
  constructor(defaultValueFactory, iterable) {
    super(iterable);
    this.defaultValueFactory = defaultValueFactory;
  }

  get(key) {
    if (!this.has(key)) {
      this.set(key, this.defaultValueFactory());
    }
    return super.get(key);
  }
}

const UnitContext = (parentContext, defaultContext) => {
  if (defaultContext) {
    defaultContext = Unit.unitfyProperties(defaultContext)
  }
  const context = new Proxy(defaultContext || {}, {
    set(target, prop, val) {
      if (prop in target) {
        target[prop].assign(val);
      } else if (parentContext && prop in parentContext) {
        // Clone and assign value from parentContext if it exists
        const clonedUnit = parentContext[prop].clone();
        clonedUnit.assign(val);
        target[prop] = clonedUnit;
      } else {
        // no assignment
        // this is intentional
      }
      return true;  // Indicate the operation was successful
    },
    get(target, prop) {
      return prop in target ? target[prop] : parentContext[prop];
    }
  });
  return context;
};


// Rectangle Factory
class RectRegionShapeFactory {
  /**
   * Constructor to initialize and mount on a container with optional plugins.
   * @param {HTMLElement} container - The DOM element to mount the rectangles.
   * @param {Array<PluginInterface|PluginInterface>} plugins - Array of plugin instances.
   */
  constructor(container) {
    if (!(container instanceof HTMLElement)) {
      throw new Error('Container must be a valid DOM element.');
    }

    this.container = container;
    this.allPlugins = new Map();
    this.globalPlugins = new Map();
    this.pluginContexts = new Map();

    // Create a shadow root on the container
    this.shadow = container.attachShadow({
      mode: 'open'
    });

    // Initialize a style element inside the shadow DOM
    this.styleElement = document.createElement('style');
    this.shadow.appendChild(this.styleElement);

    // Initialize a container inside the shadow DOM to hold rectangles
    this.rects = new Set();
    this.rectContainer = document.createElement('div');
    this.rectContainer.style.position = 'relative'; // Ensures absolute children are positioned correctly
    this.shadow.appendChild(this.rectContainer);

    this.baseRules = `
      position: absolute;
      box-sizing: border-box;
  `.trim();

    // Debounce renderCSS calls using a microtask queue
    this.debounceTask = null;

    // Initially render all CSS
    this.renderCSS();
    this.registerPlugin(BasePlugin);
  }

  /**
   * Schedules renderCSS to run as a microtask.
   */
  scheduleRenderCSS() {
    if (!this.debounceTask) {
      this.debounceTask = Promise.resolve().then(() => {
        this.renderCSS();
        this.debounceTask = null;
      });
    }
  }

  /**
   * Registers a plugin by attaching it and storing it internally.
   * @param {PluginInterface|PluginInterface} plugin - The plugin instance.
   */
  registerPlugin(pluginClass, options = {global: true}) {
    if (this.allPlugins.has(pluginClass)) {
      console.warn(`Plugin ${pluginClass.name} is already registered.`);
      return;
    }
    const plugin = new pluginClass(this._newPluginContext.bind(this, pluginClass), options);
    this.allPlugins.set(pluginClass, plugin);
    if (options?.global ?? true) {
      this.globalPlugins.set(pluginClass, plugin);
      for (const rect of this.rects) {
        rect.enablePlugins([pluginClass]);
      }
    }
    plugin.attach?.(this.shadow.host);
    this.scheduleRenderCSS();
  }

  _newPluginContext(pluginClass, globalDefaults) {
    const globalContext = UnitContext(null, globalDefaults);
    const instanceContext = new DefaultMap(() => (UnitContext(globalContext)));
    const contexts = [globalContext, instanceContext];
    this.pluginContexts.set(pluginClass, contexts);
    return contexts;
  }

  unregisterPlugin(pluginClass) {
    const plugin = this.allPlugins.get(pluginClass);
    if (plugin) {
      plugin.detach?.(this.shadow.host);
      for (const rect of this.rects) {
        rect.disablePlugins([pluginClass]);
      }
      this.allPlugins.delete(pluginClass);
      this.globalPlugins.delete(plugin);
      this.pluginContexts.delete(pluginClass);
      this.scheduleRenderCSS();
    } else {
      console.warn(`Plugin ${pluginClass.name} not found.`);
    }
  }

  execPluginCommand(pluginClass, command, ...args) {
    // get plugin instance
    // doCommand, if exists

    // onSelectedChange
    // const selected = this.execPluginCommand(SelectPlugin, SelectedPlugin.COMMAND_GET_SELECTED);
    // plugin must registers what commands are allowed
    // param for vertex, additional class to limit its visibility mimicing toggle effect

    // hover style, and whether it should be on and off per rect...
    // imagine you have existing rects and you drag to create new one, which may hover on the current one or others....
    // which hover shouldnt be active
  }

  updateGlobalPlugin(pluginClass, options = {global: false}) {
    const plugin = this.allPlugins.get(pluginClass);
    if (plugin) {
      // Update the plugin's global configurations
      plugin.updateGlobal?.(options);
      if (options?.global ?? false) {
        this.globalPlugins.set(pluginClass, plugin);
        for (const rect of this.rects) {
          rect.enablePlugins([pluginClass]);
        }
      }
      this.scheduleRenderCSS();
    } else {
      console.warn(`Plugin ${pluginClass.name} not found.`);
    }
  }

  enablePlugins(pluginClasses) {
    for (const rect of this.rects) {
      rect.enablePlugins(pluginClasses)
    }
  }

  disablePlugins(pluginClasses) {
    for (const rect of this.rects) {
      rect.enablePlugins(pluginClasses)
    }
  }

  /**
   * Aggregates CSS from all plugins and the base styles, then injects into the style element.
   */
  renderCSS() {
    const baseRules = [this.baseRules];
    const conditionalBaseRules = [];
    const additionalRules = [];
  
    this.allPlugins.forEach((plugin) => {
      const css = plugin.getCSS?.();
      if (!css) {
        return;
      }
      const [base, additional] = css;
      if (base) {
        plugin.pluginName
          ? conditionalBaseRules.push(`.rect-region.${plugin.pluginName} { ${base} }`)
          : baseRules.push(base);
      }
      if (additional) {
        additionalRules.push(additional);
      }
    });
  
    const aggregatedCSS = `
      .rect-region {
        ${baseRules.join('\n')}
      }
      ${conditionalBaseRules.join('\n')}  
      ${additionalRules.join('\n')}
    `;
  
    this.styleElement.textContent = aggregatedCSS;
  }


  /**
   * This function can be called in two ways:
   * 
   * 1. **Single Argument (`basePluginParams`)**
   *    - `basePluginParams` can either be:
   *      a) An object representing params for `BasePlugin`.
   *         - Example: create({ someParam: 'value' });
   *         - Result: [[BasePlugin, { someParam: 'value' }]]
   *      b) An array of plugin-class/params pairs.
   *         - Example: create([[CustomPlugin, { param1: 'value1' }], [AnotherPlugin, { param2: 'value2' }]]);
   *         - Result: [[CustomPlugin, { param1: 'value1' }], [AnotherPlugin, { param2: 'value2' }]]
   * 
   * 2. **Two Arguments (`basePluginParams`, `pluginClassParamPairs`)**
   *    - `basePluginParams`: The params for `BasePlugin`.
   *    - `pluginClassParamPairs`: An array of additional plugin-class/params pairs.
   *      a) If `pluginClassParamPairs` is a non-empty array:
   *         - Example: create({ baseParam: 'value' }, [[CustomPlugin1, { param1: 'value1' }]]);
   *         - Result: [[BasePlugin, { baseParam: 'value' }], [CustomPlugin1, { param1: 'value1' }]]
   *      b) If `pluginClassParamPairs` is an empty array:
   *         - Example: create({ baseParam: 'value' }, []);
   *         - Result: [[BasePlugin, { baseParam: 'value' }]]
   * 
   * @param {Object|Array} basePluginParams - An object representing parameters for `BasePlugin`, 
   *                                          or an array of plugin-class/params pairs.
   * @param {Array} [pluginClassParamPairs] - An optional array of additional plugin-class/params pairs.
   * 
   * @throws {Error} If the arguments are invalid or the array of pairs is empty.
   */
  createRect(basePluginParams, pluginClassParamPairs) {
    let finalPluginClassParamPairs;

    // Case 1: Only `basePluginParams` is provided
    if (arguments.length === 1) {
        if (!basePluginParams) {
            throw new Error("Invalid input: 'basePluginParams' must be provided.");
        }

        const isBasePluginParamsArray = Array.isArray(basePluginParams);
        finalPluginClassParamPairs = isBasePluginParamsArray ? basePluginParams : [[BasePlugin, basePluginParams]];

    // Case 2: Both `basePluginParams` and `pluginClassParamPairs` are provided
    } else if (arguments.length === 2) {
        if (!Array.isArray(pluginClassParamPairs) || pluginClassParamPairs.length === 0) {
            throw new Error("Error: 'pluginClassParamPairs' must be a non-empty array of plugin class-params pairs.");
        }
        // Add BasePlugin with its params to the beginning of the list
        finalPluginClassParamPairs = [[BasePlugin, basePluginParams], ...pluginClassParamPairs];
    } else {
        throw new Error("Error: Function accepts either one or two arguments.");
    }

    // Ensure there is at least one valid pair
    if (!finalPluginClassParamPairs.length) {
        throw new Error("Error: 'finalPluginClassParamPairs' must contain at least one valid pair.");
    }

    // Create the rectangle div
    const rectElement = document.createElement('div');
    rectElement.classList.add('rect-region');
    // Append the rectangle to the shadow DOM's container
    this.rectContainer.appendChild(rectElement);

    // Instantiate the wrapper with reference to this factory and the rectangle
    const rect = new Rect(rectElement, this);
    this.rects.add(rect);
    rect.enablePlugins(this.globalPlugins.keys());
    rect.updateMany(finalPluginClassParamPairs);
    return rect;
  }

  _onRectRemove(rect, rectElement) {
    this.rects.delete(rect);
    for (const [_, instanceContext] of this.pluginContexts.values()) {
      instanceContext.delete(rectElement);
    }
  }

  getPluginByClass(pluginClass) {
    return this.allPlugins.get(pluginClass);
  }

  getPluginContext(pluginClass) {
    return this.pluginContexts.get(pluginClass);
  }

  getAll() {
    return Array.from(this.rects);
  }
}


heres how we use it:
```
// Get the container element
const container = document.getElementById('rectangle-container');

// Instantiate the factory with plugins
const factory = new RectRegionShapeFactory(container);
factory.registerPlugin(VertexPlugin, {size: '20px', color: '#e74c3c', autohide: true, global: false});
factory.registerPlugin(MousePlugin);

// Create multiple rectangles with different properties
const rect1 = factory.createRect({x: 50, y:50, width: 200, height: 100});
const rect2 = factory.createRect({x: 300, y:150, width: 250, height: 150});
const rect3 = factory.createRect({x: 600, y:80, width: 180, height: 120, opacity: '0.5', borderSize: 12});

setTimeout(() => {
  factory.updateGlobalPlugin(VertexPlugin, {global: true});
},2000);
```

however, i want a different approach, we still have one factory,
but
```
const bar = factory.foo();
foo.usePluginMany([[Plugin A, option], [...]]);
const qux =  factory.foo();
qux.usePlugin(Plugin B, options);
qux.usePlugin(Plugin C, options);
bar.createRect(...); // using plugin A and others
qux.createRect(...);//using Plugin B and C
```
suggest the naming for `foo` method`, and the architecture for the factory need to change to for this to be implemented, show what needs to be changed and the code.