// Updated cat.js with Blob URL Integration and Bundling Parameter

(function() {
  'use strict';

  /**
   * Global configuration object for Cat.js
   * Developers can set CatConfig.bundleAll before loading components.
   */
  window.CatConfig = window.CatConfig || {
      bundleAll: false // Default: Per-Component Blob Files
  };

  /**
   * Registry module to track loaded components and dependencies.
   */
  const Registry = (function() {
      const loadedComponents = new Set();
      const pendingComponents = new Set();
      const componentDependencies = new Map(); // Map of component -> Set of dependencies

      return {
          /**
           * Checks if a component has already been loaded.
           * @param {string} path - The path to the component.
           * @returns {boolean} - True if loaded, else false.
           */
          isLoaded: (path) => loadedComponents.has(path),

          /**
           * Marks a component as loaded.
           * @param {string} path - The path to the component.
           */
          markLoaded: (path) => loadedComponents.add(path),

          /**
           * Adds a component to the pending set.
           * @param {string} path - The path to the component.
           */
          addPending: (path) => pendingComponents.add(path),

          /**
           * Removes a component from the pending set.
           * @param {string} path - The path to the component.
           */
          removePending: (path) => pendingComponents.delete(path),

          /**
           * Checks if there are any pending components.
           * @returns {boolean} - True if pending, else false.
           */
          hasPending: () => pendingComponents.size > 0,

          /**
           * Adds a dependency for a component.
           * @param {string} component - The parent component.
           * @param {string} dependency - The dependent component.
           */
          addDependency: (component, dependency) => {
              if (!componentDependencies.has(component)) {
                  componentDependencies.set(component, new Set());
              }
              componentDependencies.get(component).add(dependency);
          },

          /**
           * Retrieves dependencies of a component.
           * @param {string} component - The component.
           * @returns {Set<string>} - Set of dependencies.
           */
          getDependencies: (component) => componentDependencies.get(component) || new Set(),

          /**
           * Retrieves all components.
           * @returns {Set<string>} - Set of all loaded components.
           */
          getAllComponents: () => loadedComponents,

          /**
           * Retrieves the dependency graph.
           * @returns {Map<string, Set<string>>} - Dependency graph.
           */
          getDependencyGraph: () => componentDependencies
      };
  })();

  /**
   * Loader module for fetching and parsing component files.
   */
  const Loader = (function() {
    /**
     * Loads an HTML file from the given path.
     * @param {string} path - The path to the HTML file (without .html extension).
     * @returns {Promise<string>} - A promise that resolves to the content of the HTML file.
     */
    async function loadFile(path) {
        const fullPath = `${path}.html`;

        try {
            const response = await fetch(fullPath);
            if (!response.ok) {
                throw new Error(`Failed to load: ${fullPath} (Status: ${response.status})`);
            }
            const content = await response.text();
            return content;
        } catch (error) {
            console.error(error.message);
            throw error;
        }
    }

    /**
     * Parses an HTML string into a Document object.
     * @param {string} htmlString - The HTML string to parse.
     * @returns {Document} - The parsed Document object.
     */
    function parseHTML(htmlString) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(htmlString, 'text/html');
        return doc;
    }

    /**
     * Extracts load() paths from script content using RegExp.
     * Assumes load() is called with string literals.
     * @param {string} scriptContent - The script content.
     * @returns {Array<string>} - Array of paths to load.
     */
    function extractLoadPaths(scriptContent) {
        const loadPaths = [];
        const loadRegex = /load\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
        let match;
        while ((match = loadRegex.exec(scriptContent)) !== null) {
            loadPaths.push(match[1]);
        }
        return loadPaths;
    }

    /**
     * Removes load() declarations from script content.
     * @param {string} scriptContent - The script content.
     * @returns {string} - The cleaned script content.
     */
    function removeLoadCalls(scriptContent) {
        return scriptContent.replace(/load\s*\(\s*['"]([^'"]+)['"]\s*\);?\s*/g, ''); // Added semicolon and optional whitespace
    }

    return {
        loadFile,
        parseHTML,
        extractLoadPaths,
        removeLoadCalls
    };
})();

  /**
   * Compiler module for generating and registering component classes using Blob URLs.
   */
  const Compiler = (function() {
      // Array to store compiled component codes in bundling mode
      let compiledComponents = [];

      /**
       * Compiles a single component's template and script into a class definition.
       * @param {string} name - The name of the component (e.g., 'my-component').
       * @param {string} templateContent - The inner HTML of the component's <template>.
       * @param {string} scriptContent - The content of the component's <script> (without load() calls).
       * @returns {string} - The compiled class code as a string.
       */
      function compileComponentCode(name, templateContent, scriptContent) {
          // Helper functions
          function componentNameToClassName(name) {
              return name.split('-').map(s => s[0].toUpperCase() + s.slice(1)).join('');
          }
          function extractDynamicExpressions(text) {
              const regex = /\{([^}]+)\}/g;
              let match;
              const expressions = [];
              while ((match = regex.exec(text)) !== null) {
                  expressions.push(match[1].trim());
              }
              return expressions;
          }
          function replaceExpressions(text) {
              return text.replace(/\{([^}]+)\}/g, '${this.$1}');
          }

          const className = componentNameToClassName(name);

          // Create a template element and set its innerHTML to the templateContent
          const templateElement = document.createElement('template');
          templateElement.innerHTML = templateContent;
          const templateContentFragment = templateElement.content;

          // Initialize code snippets
          let elementCode = '';
          let propertyInitializations = '';
          let getterSetterCode = '';

          const dynamicProperties = new Set();
          const propertyToUpdateCode = {};
          const initialProperties = {};
          const propertiesHandled = new Set();

          let nodeCounter = 1;

          /**
           * Processes a DOM node to generate corresponding JavaScript code.
           * @param {Node} node - The DOM node to process.
           * @param {string} parentVarName - The variable name of the parent element in the generated code.
           */
          function processNode(node, parentVarName) {
              if (node.nodeType === Node.ELEMENT_NODE) {
                  const nodeName = node.tagName.toLowerCase();

                  if (nodeName === 'style') {
                      // Create code to create and append style element
                      const styleContent = node.textContent;
                      if (styleContent.trim()) {
                          elementCode += `const style = document.createElement('style');\n`;
                          elementCode += `style.textContent = ${JSON.stringify(styleContent)};\n`;
                          elementCode += `${parentVarName}.appendChild(style);\n`;
                      }
                      return;
                  }

                  // Assign instance variable name
                  let nodeVarName;
                  if (node.id) {
                      nodeVarName = `this.$${node.id}`;
                  } else if (['button', 'input', 'div', 'span', 'p', 'a'].includes(nodeName)) {
                      nodeVarName = `this._${nodeName}${nodeCounter++}`;
                  } else {
                      nodeVarName = `node${nodeCounter++}`;
                  }

                  // Create node
                  if (nodeVarName.startsWith('this.')) {
                      // Assign to this property without 'const'
                      elementCode += `${nodeVarName} = document.createElement('${nodeName}');\n`;
                  } else {
                      // Local variable with 'const'
                      elementCode += `const ${nodeVarName} = document.createElement('${nodeName}');\n`;
                  }

                  // Process attributes
                  for (let attr of node.attributes) {
                      const attrName = attr.name;
                      const attrValue = attr.value;
                      const dynamicExprs = extractDynamicExpressions(attrValue);

                      if (dynamicExprs.length > 0) {
                          dynamicExprs.forEach(expr => {
                              dynamicProperties.add(expr);
                              if (!propertyToUpdateCode[expr]) {
                                  propertyToUpdateCode[expr] = new Set();
                              }
                              const updateCode = `${nodeVarName}.setAttribute('${attrName}', \`${replaceExpressions(attrValue)}\`);`;
                              propertyToUpdateCode[expr].add(updateCode);
                          });

                          // Initialize attribute with current property values
                          elementCode += `${nodeVarName}.setAttribute('${attrName}', \`${replaceExpressions(attrValue)}\`);\n`;

                      } else {
                          // Static attribute
                          elementCode += `${nodeVarName}.setAttribute('${attrName}', ${JSON.stringify(attrValue)});\n`;
                      }
                  }

                  // Process child nodes
                  if (node.childNodes.length > 0) {
                      for (let child of node.childNodes) {
                          processNode(child, nodeVarName);
                      }
                  }

                  // Append to parent
                  elementCode += `${parentVarName}.appendChild(${nodeVarName});\n`;

              } else if (node.nodeType === Node.TEXT_NODE) {
                  // Handle text nodes directly under parentVarName
                  const textContent = node.nodeValue;
                  if (textContent.trim() !== '') {
                      const dynamicExprs = extractDynamicExpressions(textContent);

                      if (dynamicExprs.length > 0) {
                          const textNodeVar = `this._textNode${nodeCounter++}`;
                          if (!propertiesHandled.has(textNodeVar)) {
                              propertiesHandled.add(textNodeVar);
                              if (textNodeVar.startsWith('this.')) {
                                  elementCode += `${textNodeVar} = document.createTextNode(\`${replaceExpressions(textContent)}\`);\n`;
                              } else {
                                  elementCode += `const ${textNodeVar} = document.createTextNode(\`${replaceExpressions(textContent)}\`);\n`;
                              }
                          }
                          elementCode += `${parentVarName}.appendChild(${textNodeVar});\n`;

                          dynamicExprs.forEach(expr => {
                              dynamicProperties.add(expr);
                              if (!propertyToUpdateCode[expr]) {
                                  propertyToUpdateCode[expr] = new Set();
                              }
                              const updateCode = `${textNodeVar}.textContent = \`${replaceExpressions(textContent)}\`;`;
                              propertyToUpdateCode[expr].add(updateCode);
                          });
                      } else {
                          const textNodeVar = `document.createTextNode(${JSON.stringify(textContent)})`;
                          elementCode += `${parentVarName}.appendChild(${textNodeVar});\n`;
                      }
                  }
              }
          }

          // Start processing from templateContentFragment
          elementCode += `const shadowRoot = this.attachShadow({ mode: 'open' });\n`;

          // Process child nodes of the template content
          const rootVarName = 'shadowRoot';
          for (let child of templateContentFragment.childNodes) {
              processNode(child, rootVarName);
          }

          // Now process scriptContent to find initial property assignments and function definitions

          /**
           * Extracts function definitions and remaining code from the script content.
           * @param {string} scriptContent - The script content to parse.
           * @returns {object} - An object containing arrays of function definitions and remaining code.
           */
          function extractFunctionDefinitions(scriptContent) {
              const functionDefinitions = [];
              const nonFunctionLines = [];

              let lines = scriptContent.split('\n');
              let insideFunction = false;
              let currentFunctionLines = [];
              let braceLevel = 0;

              for (let line of lines) {
                  // Include the line in the function or non-function code
                  if (!insideFunction) {
                      if (line.trim().startsWith('function ')) {
                          insideFunction = true;
                          braceLevel = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
                          currentFunctionLines.push(line);
                      } else {
                          // Collect initial property assignments
                          const propInitMatch = line.trim().match(/^this\.(\w+)\s*=\s*(.*);$/);
                          if (propInitMatch) {
                              const propName = propInitMatch[1];
                              const propValue = propInitMatch[2];
                              initialProperties[propName] = propValue;
                          } else if (line.trim() !== '') {
                              nonFunctionLines.push(line);
                          }
                      }
                  } else {
                      currentFunctionLines.push(line);
                      braceLevel += (line.match(/\{/g) || []).length;
                      braceLevel -= (line.match(/\}/g) || []).length;
                      if (braceLevel === 0) {
                          insideFunction = false;
                          functionDefinitions.push(currentFunctionLines.join('\n'));
                          currentFunctionLines = [];
                      }
                  }
              }

              return {
                  functionDefinitions,
                  remainingCode: nonFunctionLines.join('\n')
              };
          }

          const { functionDefinitions, remainingCode } = extractFunctionDefinitions(scriptContent);

          // Collect function names
          const functionNames = [];
          let classMethodsCode = '';

          // Process each function definition
          for (let funcDef of functionDefinitions) {
              // Extract function name, parameters, body
              const funcMatch = /^function\s+(\w+)\s*\(([^)]*)\)\s*\{([\s\S]*?)\}\s*$/m.exec(funcDef.trim());
              if (funcMatch) {
                  const funcName = funcMatch[1];
                  const funcParams = funcMatch[2];
                  let funcBody = funcMatch[3];

                  // Replace 'component.' with 'this.'
                  funcBody = funcBody.replace(/\bcomponent\./g, 'this.');

                  // Add to class methods
                  classMethodsCode += `
          ${funcName}(${funcParams}) {
              ${funcBody}
          }
          `;

                  // Collect function names
                  functionNames.push(funcName);
              }
          }

          // Initialize properties not assigned in scriptContent
          dynamicProperties.forEach(prop => {
              if (!initialProperties.hasOwnProperty(prop)) {
                  initialProperties[prop] = 'null';
              }
          });

          // Build property initializations
          for (let [prop, value] of Object.entries(initialProperties)) {
              propertyInitializations += `this._${prop} = ${value};\n`;
          }

          // Build getters and setters with update code
          for (let prop of dynamicProperties) {
              let updateCode = '';
              if (propertyToUpdateCode[prop]) {
                  updateCode = Array.from(propertyToUpdateCode[prop]).join('\n');
              }
              getterSetterCode += `
          get ${prop}() {
              return this._${prop};
          }
          set ${prop}(value) {
              this._${prop} = value;
              ${updateCode}
          }
          `;
          }

          // Adjust constructor code to bind methods
          let constructorCode = remainingCode;

          // Prepend 'const functionName = this.functionName.bind(this);'
          let bindingCode = '';
          for (let funcName of functionNames) {
              bindingCode += `const ${funcName} = this.${funcName}.bind(this);\n`;
          }

          // Prepend binding code to constructor code
          constructorCode = bindingCode + constructorCode;

          // Generate the full class code
          const classCode = `
      class ${className} extends HTMLElement {
          constructor() {
              super();
              ${propertyInitializations}
              ${elementCode}
              ${constructorCode}
          }

          ${getterSetterCode}

          ${classMethodsCode}

      }
      customElements.define('${name}', ${className});
              `;

          return classCode;
      }

      /**
       * Compiles and registers multiple components in bundling mode.
       */
      function compileAndRegisterBundle() {
          if (compiledComponents.length === 0) return;

          // Combine all component codes into one string
          const bundledCode = compiledComponents.join('\n');

          // Create a Blob from the bundled code
          const blob = new Blob([bundledCode], { type: 'application/javascript' });
          const blobURL = URL.createObjectURL(blob);

          // Create a script element
          const script = document.createElement('script');
          script.src = blobURL;
          script.type = 'module'; // Use module type for modern JavaScript features
          script.onload = () => {
              console.log(`All components loaded and registered as a bundle.`);
              // Revoke the Blob URL after the script has been loaded to free memory
              URL.revokeObjectURL(blobURL);
          };
          script.onerror = (error) => {
              console.error(`Error loading component bundle:`, error);
              // Revoke the Blob URL on error to free memory
              URL.revokeObjectURL(blobURL);
          };

          // Append the script to the document head
          document.head.appendChild(script);
      }

      /**
       * Compiles and registers a single component.
       * @param {string} name - The name of the component.
       * @param {string} code - The compiled JavaScript class code.
       */
      function compileAndRegisterComponent(name, code) {
          // Create a Blob from the code
          const blob = new Blob([code], { type: 'application/javascript' });
          const blobURL = URL.createObjectURL(blob);

          // Create a script element
          const script = document.createElement('script');
          script.src = blobURL;
          script.type = 'module'; // Use module type for modern JavaScript features
          script.onload = () => {
              console.log(`Component ${name} loaded and registered.`);
              // Revoke the Blob URL after the script has been loaded to free memory
              URL.revokeObjectURL(blobURL);
          };
          script.onerror = (error) => {
              console.error(`Error loading component ${name}:`, error);
              // Revoke the Blob URL on error to free memory
              URL.revokeObjectURL(blobURL);
          };

          // Append the script to the document head
          document.head.appendChild(script);
      }

      return {
          /**
           * Compiles a component and adds it to the bundling array or registers immediately.
           * @param {string} name - The name of the component.
           * @param {string} templateContent - The template content.
           * @param {string} scriptContent - The script content (load() calls removed).
           */
          compileComponent: function(name, templateContent, scriptContent) {
              const classCode = compileComponentCode(name, templateContent, scriptContent);
              if (window.CatConfig.bundleAll) {
                  compiledComponents.push(classCode);
              } else {
                  compileAndRegisterComponent(name, classCode);
              }
          },

          /**
           * Finalizes the compilation process by injecting the bundle if bundling is enabled.
           */
          finalizeCompilation: function() {
              if (window.CatConfig.bundleAll) {
                  compileAndRegisterBundle();
              }
          }
      };
  })();

  /**
   * Main Engine module to orchestrate loading, parsing, and compiling components.
   */
  const Engine = (function() {
      /**
       * Recursively processes a component and its dependencies.
       * @param {string} path - The path to the component.
       * @returns {Promise<void>}
       */
      async function processComponent(path) {
          if (Registry.isLoaded(path)) {
              return;
          }

          Registry.markLoaded(path);
          Registry.addPending(path);

          try {
              const content = await Loader.loadFile(path);
              const doc = Loader.parseHTML(content);

              // Extract the <template> with the 'name' attribute
              const template = doc.querySelector('template[name]');
              if (!template) {
                  console.error(`No <template name="..."> found in ${path}.html`);
                  return;
              }

              const componentName = template.getAttribute('name');
              const templateContent = template.innerHTML;

              // Extract the <script> content
              const script = doc.querySelector('script');
              const rawScriptContent = script ? script.textContent : '';
              const loadPaths = Loader.extractLoadPaths(rawScriptContent);

              // Remove load() calls from script
              const cleanedScriptContent = Loader.removeLoadCalls(rawScriptContent);

              // Record dependencies
              loadPaths.forEach(depPath => {
                  Registry.addDependency(path, depPath);
              });

              // Compile the component
              Compiler.compileComponent(componentName, templateContent, cleanedScriptContent);

              // Recursively process dependencies
              for (let depPath of loadPaths) {
                  await processComponent(depPath);
              }

              console.log(`Component "${componentName}" processed.`);

          } catch (error) {
              console.error(`Error processing component "${path}":`, error);
          } finally {
              Registry.removePending(path);
          }
      }

      /**
       * Initiates the loading and compilation process.
       * @param {Array<string>} initialPaths - Initial component paths to load.
       * @returns {Promise<void>}
       */
      async function initiateLoad(initialPaths) {
          const loadPromises = initialPaths.map(path => processComponent(path));
          await Promise.all(loadPromises);

          // Finalize compilation after all components are processed
          Compiler.finalizeCompilation();
      }

      function removeCatScript() {
        const catScript = document.querySelector('script[cat-engine]');
        if (catScript) {
            catScript.remove();
        }
      }


      return {
          /**
           * Starts the engine with specified initial components.
           * @param {Array<string>} initialPaths - Initial component paths to load.
           */
          start: function(initialPaths) {
              initiateLoad(initialPaths).then(() => {
                removeCatScript();
              });
          }
      };
  })();

  /**
   * Overriding the global load() function to integrate with the Engine.
   * Instead of loading immediately, it queues the path for processing.
   * Multiple load() calls are accumulated and processed together.
   */
  window.load = function(path) {
    if (window.CatConfig.bundleAll) {
        console.log(`Queued for bundling: ${path}`);
        if (!Engine._loadQueue) {
            Engine._loadQueue = new Set();
        }
        Engine._loadQueue.add(path);

        // Ensure Engine.start is called only once after all load() calls
        clearTimeout(Engine._startTimeout);
        Engine._startTimeout = setTimeout(() => {
            Engine.start(Array.from(Engine._loadQueue));
            Engine._loadQueue.clear(); // Clear the queue after processing
        }, 0);

    } else {
        console.log(`Queued for loading: ${path}`);
        Engine.start([path]);
    }
};

  /**
   * Extend the Engine to accumulate all loads for bundling.
   * This ensures that multiple load() calls are processed together.
   */
  (function extendLoadForBundling() {
      if (window.CatConfig.bundleAll) {
          const originalLoad = window.load;
          const loadQueue = new Set();

          // Override load to add paths to the queue instead of processing immediately
          window.load = function(path) {
              console.log(`Queued for bundling: ${path}`);
              loadQueue.add(path);
          };

          // Modify the Engine's start method to process all queued paths
          const originalStart = Engine.start;
          Engine.start = function(initialPaths) {
              initialPaths.forEach(path => loadQueue.add(path));
              if (Engine._processing) return; // Prevent multiple initiations
              Engine._processing = true;
              originalStart(Array.from(loadQueue)).then(() => {
                  Engine._processing = false;
              });
          };
      }
  })();

  /**
   * Optional: Automatically finalize bundling when the window loads.
   * This ensures that all load() calls made before window load are captured.
   */
  window.addEventListener('load', function() {
      if (window.CatConfig.bundleAll) {
          Engine.start([]); // Start processing all queued loads
      }
  });

})();

---

I need to transform this engine into a AOT / Build Time compiler, where the dev, before deploy to production runs `npm cat compile`, and compile all the components, and set up the project ready to be deploy to production.

Think edge cases, consider dev stage, and set up everything, output the code and the step by step implementation.