// cat.js

// Immediately Invoked Function Expression (IIFE) to encapsulate the engine
(function() {
  'use strict';

  /**
   * Internal module for tracking loaded files.
   */
  const Registry = (function() {
    const loadedFiles = new Set();

    return {
      isFileLoaded: (path) => loadedFiles.has(path),
      markFileAsLoaded: (path) => loadedFiles.add(path),
    };
  })();

  /**
   * Compiler module for generating self-contained component classes.
   */
  const Compiler = (function() {
    /**
     * Compiles the component's template and script into a component 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>.
     * @returns {Function} - A function that defines and registers the component.
     */
    function compileComponent(name, templateContent, scriptContent) {
      return function() {
        class ComponentClass extends HTMLElement {
          constructor() {
            super();

            const shadowRoot = this.attachShadow({ mode: 'open' });

            // Parse the template content and clone it
            const template = document.createElement('template');
            template.innerHTML = templateContent;
            shadowRoot.appendChild(template.content.cloneNode(true));

            // Process the template content to find bindings
            this._bindings = [];

            const propertyNames = new Set();

            const processNode = (node) => {
              if (node.nodeType === Node.TEXT_NODE) {
                const originalText = node.textContent;
                const regex = /\{(\w+)\}/g;
                let match;
                const matches = [];
                while ((match = regex.exec(originalText)) !== null) {
                  const varName = match[1];
                  propertyNames.add(varName);
                  matches.push({ varName, index: match.index, length: match[0].length });
                }
                if (matches.length > 0) {
                  this._bindings.push({
                    type: 'text',
                    node,
                    originalText,
                    matches,
                  });
                }
              } else if (node.nodeType === Node.ELEMENT_NODE) {
                // Process attributes
                for (let attr of Array.from(node.attributes)) {
                  const originalValue = attr.value;
                  const regex = /\{(\w+)\}/g;
                  let match;
                  const matches = [];
                  while ((match = regex.exec(originalValue)) !== null) {
                    const varName = match[1];
                    propertyNames.add(varName);
                    matches.push({ varName, index: match.index, length: match[0].length });
                  }
                  if (matches.length > 0) {
                    this._bindings.push({
                      type: 'attribute',
                      node,
                      attributeName: attr.name,
                      originalValue,
                      matches,
                    });
                  }
                }
                // Process child nodes
                node.childNodes.forEach(childNode => {
                  processNode(childNode);
                });
              }
            };

            // Start processing from shadowRoot's child nodes
            shadowRoot.childNodes.forEach(node => {
              processNode(node);
            });

            // Define reactive properties with getters and setters
            propertyNames.forEach(varName => {
              let value = '';
              Object.defineProperty(this, varName, {
                get() {
                  return value;
                },
                set(newValue) {
                  value = newValue;
                  this._updateBindings(varName);
                },
              });
            });

            // Execute user script in the context of 'this'
            if (scriptContent.trim()) {
              (function(){
                'use strict';
                eval(scriptContent);
              }).call(this);
            }
          }

          _updateBindings(varName) {
            // Update bindings that are dependent on varName
            this._bindings.forEach(binding => {
              let shouldUpdate = false;
              binding.matches.forEach(match => {
                if (match.varName === varName) {
                  shouldUpdate = true;
                }
              });

              if (shouldUpdate) {
                if (binding.type === 'text') {
                  // Recompute textContent
                  let text = binding.originalText;
                  binding.matches.forEach(match => {
                    const val = this[match.varName];
                    text = text.replace('{' + match.varName + '}', val);
                  });
                  binding.node.textContent = text;
                } else if (binding.type === 'attribute') {
                  // Recompute attribute value
                  let value = binding.originalValue;
                  binding.matches.forEach(match => {
                    const val = this[match.varName];
                    value = value.replace('{' + match.varName + '}', val);
                  });
                  binding.node.setAttribute(binding.attributeName, value);
                }
              }
            });
          }

          connectedCallback() {
            // Component is connected to the DOM
            // Any additional initialization can be done here
          }

          disconnectedCallback() {
            // Clean up if necessary
          }
        }

        // Define the custom element
        customElements.define(name, ComponentClass);
      };
    }

    return {
      compileComponent,
    };
  })();

  /**
   * Internal module for loading and parsing HTML 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;
    }

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

  /**
   * Global load function.
   * @param {string} path - The path to the HTML file (without .html extension).
   * @returns {Promise<void>}
   */
  window.load = async function(path) {
    console.log(`Loading: ${path}`);

    if (Registry.isFileLoaded(path)) {
      console.log(`Already loaded: ${path}`);
      return;
    }

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

      Registry.markFileAsLoaded(path);

      // 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 scriptContent = script ? script.textContent : '';

      // Use compiler to generate and define the component
      const defineComponent = Compiler.compileComponent(componentName, templateContent, scriptContent);

      // Execute the function to define the component
      defineComponent();

      console.log(`Component registered: ${componentName}`);
    } catch (error) {
      console.error(`Error loading component from ${path}: ${error.message}`);
    }
  };
})();

Sample component:

```
<!-- app/comp/counter.html -->
<template name="my-counter">
    <style>
        .on {
            background-color: blue;
        }
        .off {
            background-color: aliceblue;
        }
        .btn {
            padding: 20px;
        }
    </style>

    <button class="{status} btn">Count: {count}</button>
</template>

<script>
    this.count = 0;
    this.status = 'on';
    
    const button = this.shadowRoot.querySelector('button');
    const span = button.querySelector('span');

    const component = this; // To maintain correct context inside functions

    function updateCount() {
        component.count++;

        component.status = component.status === 'on' ? 'off' : 'on';
        // The reactive system will automatically update the DOM elements where {count} and {status} are used
    }

    button.addEventListener('click', updateCount);
</script>
```

Show how the sample component looks likes after being compiled. And provide a score for the efficiency of this lib.