set this class up so i can use it in code pen, make a rect factory too
"""
class DrawableArea extends EventTarget {
  // A WeakSet to keep track of initialized containers
  static initializedContainers = new WeakSet();

  constructor({ container, factory }) {
    super(); // Initialize EventTarget

    if (!container) {
      throw new TypeError('DrawableArea: "container" element is required.');
    }
    if (!(container instanceof HTMLElement)) {
      throw new TypeError('DrawableArea: "container" must be an instance of HTMLElement.');
    }
    if (!factory) {
      throw new TypeError('DrawableArea: "factory" instance is required.');
    }

    this.container = container;
    this.factory = factory;

    // Bind event handlers to maintain context
    this.handleMouseDown = this.handleMouseDown.bind(this);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.handleMouseUp = this.handleMouseUp.bind(this);

    // State variables
    this.isDrawing = false;
    this.startX = 0;
    this.startY = 0;
    this.currentRect = null;

    // Internal flags
    this.isInitialized = false;
    this.isStarted = false;
  }

  /**
   * Factory method to create and initialize a DrawableArea instance.
   * @param {Object} options - Configuration options.
   * @returns {DrawableArea} - The initialized DrawableArea instance.
   */
  static create(options) {
    const instance = new DrawableArea(options);
    instance.init();
    return instance;
  }

  /**
   * Initializes the DrawableArea by preparing the container.
   * Ensures that the container has the appropriate positioning context.
   * Dispatches the 'init' event upon successful initialization.
   */
  init() {
    if (this.isInitialized) {
      console.warn('DrawableArea: Already initialized.');
      return this;
    }

    // Ensure the container has a positioning context
    const position = getComputedStyle(this.container).position;
    if (!['relative', 'absolute', 'fixed', 'sticky'].includes(position)) {
      this.container.style.position = 'relative';
    }

    // Mark the container as initialized
    DrawableArea.initializedContainers.add(this.container);
    this.isInitialized = true;

    // Dispatch the 'init' event
    this.dispatchEvent(new Event('init'));

    return this; // Enable method chaining
  }

  /**
   * Starts the drawing functionality by attaching event listeners.
   * Dispatches the 'start' event upon successful start.
   * @returns {DrawableArea} - The DrawableArea instance for chaining.
   * @throws {Error} - If the DrawableArea is not initialized.
   */
  start() {
    if (!this.isInitialized) {
      throw new Error('DrawableArea: Must be initialized before starting.');
    }

    if (this.isStarted) {
      console.warn('DrawableArea: Already started.');
      return this;
    }

    // Attach mouse event listeners
    this.container.addEventListener('mousedown', this.handleMouseDown);
    window.addEventListener('mousemove', this.handleMouseMove);
    window.addEventListener('mouseup', this.handleMouseUp);

    this.isStarted = true;

    // Dispatch the 'start' event
    this.dispatchEvent(new Event('start'));

    return this; // Enable method chaining
  }

  /**
   * Stops the drawing functionality by detaching event listeners.
   * Dispatches the 'stop' event upon successful stop.
   * @returns {DrawableArea} - The DrawableArea instance for chaining.
   * @throws {Error} - If the DrawableArea is not initialized.
   */
  stop() {
    if (!this.isInitialized) {
      throw new Error('DrawableArea: Must be initialized before stopping.');
    }

    if (!this.isStarted) {
      console.warn('DrawableArea: Not started or already stopped.');
      return this;
    }

    // Detach mouse event listeners
    this.container.removeEventListener('mousedown', this.handleMouseDown);
    window.removeEventListener('mousemove', this.handleMouseMove);
    window.removeEventListener('mouseup', this.handleMouseUp);

    this.isStarted = false;

    // If currently drawing, cancel the operation
    if (this.isDrawing) {
      this.isDrawing = false;
      if (this.currentRect) {
        this.currentRect.remove();
        this.currentRect = null;
      }
    }

    // Dispatch the 'stop' event
    this.dispatchEvent(new Event('stop'));

    return this; // Enable method chaining
  }

  /**
   * Cleans up event listeners and removes the initialization flag.
   * Dispatches the 'destroy' event upon successful destruction.
   * @returns {DrawableArea} - The DrawableArea instance for chaining.
   * @throws {Error} - If the DrawableArea is not initialized.
   */
  destroy() {
    if (!this.isInitialized) {
      throw new Error('DrawableArea: Not initialized or already destroyed.');
    }

    if (this.isStarted) {
      this.stop();
    }

    // Remove the container from the initialized set
    DrawableArea.initializedContainers.delete(this.container);
    this.isInitialized = false;

    // Dispatch the 'destroy' event
    this.dispatchEvent(new Event('destroy'));

    // Additional cleanup can be performed here (e.g., removing created elements)

    return this; // Enable method chaining
  }

  /**
   * Handles the mousedown event to start drawing.
   * Dispatches 'drawn:start' and 'drawn:failure' or 'drawn:success' events as appropriate.
   * @param {MouseEvent} e
   */
  handleMouseDown(e) {
    // Only respond to left mouse button
    if (e.button !== 0) return;

    // Prevent drawing if clicking on an existing rectangle
    if (e.target !== this.container) return;

    e.preventDefault(); // Prevent text selection and other default behaviors

    this.isDrawing = true;
    const rect = this.container.getBoundingClientRect();
    this.startX = e.clientX - rect.left;
    this.startY = e.clientY - rect.top;

    // Initialize the rectangle with minimal size
    try {
      this.currentRect = this.factory.createRect({ x: this.startX, y: this.startY, width: 0, height: 0 });
      this.dispatchEvent(
        new CustomEvent('drawn:start', {
          rectangle: this.currentRect,
        })
      );
    } catch (error) {
      console.error('DrawableArea: Failed to create rectangle.', error);
      this.isDrawing = false;
      this.dispatchEvent(
        new CustomEvent('drawn:failure', {
          reason: 'creation_failed', 
          error,
        })
      );
    }
  }

  /**
   * Handles the mousemove event to update the drawing rectangle.
   * @param {MouseEvent} e
   */
  handleMouseMove(e) {
    if (!this.isDrawing || !this.currentRect) return;

    const rect = this.container.getBoundingClientRect();
    const currentX = e.clientX - rect.left;
    const currentY = e.clientY - rect.top;

    let x = this.startX;
    let y = this.startY;
    let width = currentX - this.startX;
    let height = currentY - this.startY;

    // Adjust for negative width
    if (width < 0) {
      x = currentX;
      width = Math.abs(width);
    }

    // Adjust for negative height
    if (height < 0) {
      y = currentY;
      height = Math.abs(height);
    }

    // Update the rectangle's properties using the factory's update method
    try {
      this.currentRect.update({ x, y, width, height });
    } catch (error) {
      console.error('DrawableArea: Failed to update rectangle.', error);
      this.currentRect.remove();
      this.currentRect = null;
      this.isDrawing = false;
      this.dispatchEvent(
        new CustomEvent('drawn:failure', {
          reason: 'update_failed',
          error,
        })
      );
    }
  }

  /**
   * Handles the mouseup event to finalize the drawing.
   * Dispatches 'drawn:success' or 'drawn:failure' and a general 'drawn' event.
   * @param {MouseEvent} e
   */
  handleMouseUp(e) {
    if (!this.isDrawing) return;
    this.isDrawing = false;

    // Optional: Validate the final size of the rectangle
    let isSuccess = true;
    let failureReason = null;
    let failureError = null;
    try {
      // fix get param tho
      const {width, height} = this.currentRect.bbox();

      const MIN_SIZE = 10;

      if (width < MIN_SIZE || height < MIN_SIZE) {
        // Remove the rectangle if it's too small
        this.currentRect.remove();
        isSuccess = false;
        failureReason = 'rectangle_too_small';
      }
    } catch (error) {
      console.error('DrawableArea: Failed to finalize rectangle.', error);
      isSuccess = false;
      failureReason = 'finalization_failed';
      failureError = error;
    }

    // Emit events based on success or failure
    if (isSuccess) {
      this.dispatchEvent(
        new CustomEvent('drawn:success', {
          rectangle: this.currentRect,
        })
      );
    } else {
      this.dispatchEvent(
        new CustomEvent('drawn:failure', {
          reason: failureReason,
          error: failureError
        })
      );
    }

    this.currentRect = null;
  }
}

"""