// 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
      }
  });

})();

---

Turn off the Shadow DOM, make it an editable parameter from CatConfig, by default it's off. And when shadow DOM it's off, the content inside a style tag, it's going to be registered once, not by component instace, into a style tag in the head of the document. Output cat.js updated.