// cat.js 

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

})();

--------------------------------------------

// compiler/compiler.js

const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');
const { Command } = require('commander');

const program = new Command();

// Define paths
const COMPONENTS_DIR = path.resolve(__dirname, '../app/comp');
const INDEX_HTML_SRC = path.resolve(__dirname, '../index.html');
const DIST_DIR = path.resolve(__dirname, '../dist');
const DIST_INDEX_HTML = path.resolve(DIST_DIR, 'index.html');
const BUNDLE_FILE = path.join(DIST_DIR, 'components.bundle.js');

// Register 'compile' command
program
  .command('compile')
  .description('Compile all components for production')
  .action(async () => {
    try {
      console.log('Starting compilation...');
      await prepareDist();
      const components = await loadComponents();
      const dependencyGraph = buildDependencyGraph(components);
      const sortedComponents = topologicalSort(dependencyGraph);
      const bundledCode = compileComponents(sortedComponents, components);
      await bundleComponents(bundledCode);
      await copyIndexHTML();
      console.log('Compilation and bundling completed successfully.');
    } catch (error) {
      console.error('Compilation failed:', error.message);
      process.exit(1);
    }
  });

program.parse(process.argv);

/**
 * Prepares the distribution directory.
 */
async function prepareDist() {
  await fs.ensureDir(DIST_DIR);
  // Clean previous build
  await fs.emptyDir(DIST_DIR);
  console.log('Prepared dist directory.');
}

/**
 * Loads all component files.
 * @returns {Object} - An object mapping component names to their content.
 */
async function loadComponents() {
  const componentFiles = glob.sync('**/*.html', { cwd: COMPONENTS_DIR });
  const components = {};

  for (const file of componentFiles) {
    const filePath = path.join(COMPONENTS_DIR, file);
    const content = await fs.readFile(filePath, 'utf-8');

    let componentName, templateContent, scriptContent;

    // Attempt to extract <template name="...">
    const templateNameMatch = content.match(/<template\s+name=['"]([\w-]+)['"]>/);
    if (templateNameMatch) {
      componentName = templateNameMatch[1];
      const templateMatch = content.match(/<template\s+name=['"][\w-]+['"]>([\s\S]*?)<\/template>/);
      templateContent = templateMatch ? templateMatch[1].trim() : '';
    } else {
      // Fallback: Use the root tag as the component name
      const rootTagMatch = content.match(/^<([\w-]+)>/);
      if (rootTagMatch) {
        componentName = rootTagMatch[1];
        const innerContentMatch = content.match(/^<[\w-]+>([\s\S]*?)<\/[\w-]+>$/);
        templateContent = innerContentMatch ? innerContentMatch[1].trim() : '';
      } else {
        throw new Error(`Component in ${file} is missing a <template name="..."> tag or a root custom element.`);
      }
    }

    if (components[componentName]) {
      throw new Error(`Duplicate component name detected: ${componentName}`);
    }

    // Extract script content
    const scriptMatch = templateContent.match(/<script>([\s\S]*?)<\/script>/);
    const rawScript = scriptMatch ? scriptMatch[1].trim() : '';

    // Extract dependencies from load() calls
    const dependencies = extractLoadCalls(rawScript);

    // Remove load() calls from script
    const cleanedScript = removeLoadCalls(rawScript);

    // Remove <script> tags from templateContent
    templateContent = templateContent.replace(/<script>[\s\S]*?<\/script>/, '').trim();

    components[componentName] = {
      name: componentName,
      template: templateContent,
      script: cleanedScript,
      dependencies
    };

    console.log(`Loaded component: ${componentName}`);
  }

  return components;
}

/**
 * Extracts paths from load() calls in script content.
 * @param {string} script - The script content.
 * @returns {Array<string>} - Array of dependency paths.
 */
function extractLoadCalls(script) {
  const loadRegex = /load\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
  const loadPaths = [];
  let match;

  while ((match = loadRegex.exec(script)) !== null) {
    loadPaths.push(match[1]);
  }

  return loadPaths;
}

/**
 * Removes load() calls from script content.
 * @param {string} script - The script content.
 * @returns {string} - Cleaned script without load() calls.
 */
function removeLoadCalls(script) {
  return script.replace(/load\s*\(\s*['"]([^'"]+)['"]\s*\);?/g, '').trim();
}

/**
 * Builds a dependency graph from components.
 * @param {Object} components - The components object.
 * @returns {Map<string, Set<string>>} - Dependency graph.
 */
function buildDependencyGraph(components) {
  const graph = new Map();

  for (const [name, component] of Object.entries(components)) {
    graph.set(name, new Set());

    for (const depPath of component.dependencies) {
      // Assume depPath is relative, e.g., 'app/comp/name'
      const depFileName = path.basename(depPath);
      const depName = depFileName; // Assuming component name matches file name without extension

      if (!components[depName]) {
        throw new Error(`Component "${name}" has missing dependency: "${depName}"`);
      }
      graph.get(name).add(depName);
    }
  }

  return graph;
}

/**
 * Performs topological sort on the dependency graph.
 * @param {Map<string, Set<string>>} graph - The dependency graph.
 * @returns {Array<string>} - Sorted components.
 */
function topologicalSort(graph) {
  const visited = new Set();
  const sorted = [];

  function visit(node, ancestors = []) {
    if (visited.has(node)) return;
    if (ancestors.includes(node)) {
      throw new Error(`Circular dependency detected: ${ancestors.join(' -> ')} -> ${node}`);
    }
    ancestors.push(node);
    for (const neighbor of graph.get(node)) {
      visit(neighbor, ancestors.slice());
    }
    visited.add(node);
    sorted.push(node);
  }

  for (const node of graph.keys()) {
    visit(node);
  }

  return sorted;
}

/**
 * Compiles components into JavaScript classes.
 * @param {Array<string>} sortedComponents - Components in load order.
 * @param {Object} components - All components.
 * @returns {string} - Combined JavaScript code.
 */
function compileComponents(sortedComponents, components) {
  let bundledCode = '';

  for (const componentName of sortedComponents) {
    const component = components[componentName];
    const className = toPascalCase(componentName);

    // Extract style if present
    const styleMatch = component.template.match(/<style>([\s\S]*?)<\/style>/);
    const styleContent = styleMatch ? styleMatch[1].trim() : '';

    // Extract inner HTML without style
    const htmlWithoutStyle = component.template.replace(/<style>[\s\S]*?<\/style>/, '').trim();

    // Escape backticks and dollar signs in HTML
    const escapedHTML = htmlWithoutStyle
      .replace(/`/g, '\\`')
      .replace(/\$/g, '\\$');

    // Process dynamic expressions in HTML
    const processedHTML = processDynamicExpressions(escapedHTML);

    // Process class properties and methods from script
    const { properties, methods, eventListeners } = parseScript(component.script);

    // Generate property initializations
    const propertyInitializations = Object.entries(properties)
      .map(([prop, value]) => `this.${prop} = ${value};`)
      .join('\n      ');

    // Generate method bindings
    const methodBindings = Object.keys(methods)
      .map(method => `this.${method} = this.${method}.bind(this);`)
      .join('\n      ');

    // Generate method definitions
    const methodDefinitions = Object.entries(methods)
      .map(([method, body]) => `${method}() {\n        ${body}\n      }`)
      .join('\n\n      ');

    // Generate event listeners
    const generatedEventListeners = eventListeners
      .map(listener => `this.shadowRoot.querySelector('${listener.selector}').addEventListener('${listener.event}', this.${listener.handler});`)
      .join('\n      ');

    // Construct the class definition
    const classDef = `
  class ${className} extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });

      ${styleContent ? `const style = document.createElement('style');
      style.textContent = \`
${indent(styleContent, 8)}
      \`;
      this.shadowRoot.appendChild(style);\n` : ''}

      this.shadowRoot.innerHTML += \`
${indent(processedHTML, 6)}
      \`;

      ${propertyInitializations}

      ${methodBindings}

      ${generatedEventListeners}
    }

    connectedCallback() {
      ${Object.keys(methods).length > 0 ? '// Bind methods\n' : ''}
      // Initialize component
      if (typeof this.init === 'function') {
        this.init();
      }
    }

    ${methodDefinitions}
  }

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

    bundledCode += classDef + '\n';
    console.log(`Compiled component: ${componentName}`);
  }

  return bundledCode;
}

/**
 * Converts kebab-case to PascalCase.
 * @param {string} str - The kebab-case string.
 * @returns {string} - PascalCase string.
 */
function toPascalCase(str) {
  return str
    .split('-')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join('');
}

/**
 * Indents each line of the input string by the specified number of spaces.
 * @param {string} str - The input string.
 * @param {number} spaces - Number of spaces to indent.
 * @returns {string} - Indented string.
 */
function indent(str, spaces) {
  const padding = ' '.repeat(spaces);
  return str
    .split('\n')
    .map(line => padding + line)
    .join('\n');
}

/**
 * Processes dynamic expressions in HTML (e.g., {property}).
 * Replaces them with template literals to allow dynamic updates.
 * @param {string} html - The HTML string with dynamic expressions.
 * @returns {string} - Processed HTML string.
 */
function processDynamicExpressions(html) {
  // Replace {property} with \${this.property}
  return html.replace(/\{([\w.]+)\}/g, '${this.$1}');
}

/**
 * Parses the script content to extract properties, methods, and event listeners.
 * @param {string} script - The cleaned script content.
 * @returns {Object} - Contains properties, methods, and eventListeners.
 */
function parseScript(script) {
  const lines = script.split('\n').map(line => line.trim()).filter(line => line.length > 0);
  const properties = {};
  const methods = {};
  const eventListeners = [];

  const propertyRegex = /^this\.(\w+)\s*=\s*(.+);$/;
  const functionRegex = /^function\s+(\w+)\s*\(([^)]*)\)\s*\{([\s\S]*)\}$/;
  const eventListenerRegex = /^([a-zA-Z0-9_$.]+)\.addEventListener\(['"](\w+)['"],\s*(\w+)\);?$/;

  for (let line of lines) {
    // Property initialization
    let propMatch = line.match(propertyRegex);
    if (propMatch) {
      const propName = propMatch[1];
      const propValue = propMatch[2];
      properties[propName] = propValue;
      continue;
    }

    // Function definition
    let funcMatch = line.match(functionRegex);
    if (funcMatch) {
      const funcName = funcMatch[1];
      const params = funcMatch[2];
      const body = funcMatch[3];
      methods[funcName] = body;
      continue;
    }

    // Event listener
    let eventMatch = line.match(eventListenerRegex);
    if (eventMatch) {
      const selector = extractSelector(eventMatch[1]);
      const eventType = eventMatch[2];
      const handler = eventMatch[3];
      eventListeners.push({ selector, event: eventType, handler });
      continue;
    }

    // Other lines can be added as needed
  }

  return { properties, methods, eventListeners };
}

/**
 * Extracts the selector from a querySelector string.
 * @param {string} query - The querySelector string (e.g., "this.shadowRoot.querySelector('button')").
 * @returns {string} - The selector string (e.g., "button").
 */
function extractSelector(query) {
  const selectorMatch = query.match(/querySelector\(['"]([^'"]+)['"]\)/);
  return selectorMatch ? selectorMatch[1] : '';
}

/**
 * Bundles the compiled code into a single JavaScript file.
 * @param {string} code - The combined JavaScript code.
 */
async function bundleComponents(code) {
  // For simplicity, wrap the code in an IIFE to avoid global scope pollution
  const wrappedCode = `(function() {\n${code}\n})();\n`;

  await fs.writeFile(BUNDLE_FILE, wrappedCode, 'utf-8');
  console.log(`Bundled components into ${path.relative(DIST_DIR, BUNDLE_FILE)}`);
}

/**
 * Copies and modifies index.html to include the bundled script.
 */
async function copyIndexHTML() {
  const indexContent = await fs.readFile(INDEX_HTML_SRC, 'utf-8');

  // Inject the bundled script before closing </body> tag
  const scriptTag = `<script src="components.bundle.js"></script>`;
  const modifiedIndex = indexContent.replace(
    /<\/body>/i,
    `  ${scriptTag}\n</body>`
  );

  await fs.writeFile(DIST_INDEX_HTML, modifiedIndex, 'utf-8');
  console.log(`Generated dist/index.html with bundled components.`);
}

----------------------

I need you update the compiler.js to compile exactly as the cat.js compiled, i mean, output the exact compiled code for a component as cat.js does.