// cat.js

// Immediately Invoked Function Expression (IIFE) to encapsulate the engine
(function() {
    'use strict';
  
    /**
     * Internal module for tracking loaded files and components.
     */
    const Registry = (function() {
      const loadedFiles = new Set();
      const registeredComponents = new Set();
  
      return {
        isFileLoaded: (path) => loadedFiles.has(path),
        markFileAsLoaded: (path) => loadedFiles.add(path),
        isComponentRegistered: (name) => registeredComponents.has(name),
        registerComponent: (name) => registeredComponents.add(name),
      };
    })();
  
    /**
     * Internal module for handling component registration.
     */
    const ComponentManager = (function() {
      const templateCache = new Map();
  
      function registerComponent(name, templateContent) {
        if (Registry.isComponentRegistered(name)) {
          console.warn(`Component already registered: ${name}`);
          return;
        }
  
        // Cache the template content
        templateCache.set(name, templateContent);
  
        class CustomElement extends HTMLElement {
          constructor() {
            super();
            const shadow = this.attachShadow({ mode: 'open' });
            shadow.innerHTML = templateCache.get(name);
          }
        }
  
        customElements.define(name, CustomElement);
        Registry.registerComponent(name);
        console.log(`Component registered: ${name}`);
      }
  
      return {
        registerComponent,
      };
    })();
  
    /**
     * Internal module for loading and parsing HTML files.
     */
    const Loader = (function() {
      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;
        }
      }
  
      function parseHTML(htmlString) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(htmlString, 'text/html');
        return doc;
      }
  
      return {
        loadFile,
        parseHTML,
      };
    })();
  
    /**
     * Internal module for handling script execution and nested loading.
     */
    const ScriptProcessor = (function() {
      async function processScripts(doc) {
        const scripts = doc.querySelectorAll('script');
        for (const script of scripts) {
          const scriptContent = script.textContent;
  
          // Find load calls and execute them sequentially
          const loadCalls = [...scriptContent.matchAll(/load\(\s*['"]([^'"]+)['"](?:\s*,\s*['"]([^'"]+)['"])?\s*\)/g)];
          for (const match of loadCalls) {
            const componentPath = match[1];
            const targetSelector = match[2];
            await window.load(componentPath, targetSelector);
          }
  
          // Execute the script content
          if (scriptContent.trim()) {
            try {
              new Function(scriptContent)();
            } catch (error) {
              console.error(`Error executing script in ${componentPath}: ${error.message}`);
            }
          }
        }
      }
  
      return {
        processScripts,
      };
    })();
  
    /**
     * Internal module for inserting content into the DOM.
     */
    const DOMHandler = (function() {
      function insertContent(doc, targetSelector) {
        const insert = () => {
          const targetElement = document.querySelector(targetSelector);
          if (targetElement) {
            const fragment = document.createDocumentFragment();
            const bodyContent = doc.body;
            while (bodyContent.firstChild) {
              fragment.appendChild(bodyContent.firstChild);
            }
            targetElement.appendChild(fragment);
          } else {
            console.error(`Target element not found: ${targetSelector}`);
          }
        };
  
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', insert);
        } else {
          insert();
        }
      }
  
      return {
        insertContent,
      };
    })();
  
    /**
     * Global load function.
     * @param {string} path - The path to the HTML file (without .html extension).
     * @param {string} [targetSelector] - Optional CSS selector of the target element to insert content into.
     * @returns {Promise<void>}
     */
    window.load = async function(path, targetSelector) {
      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);
  
        // Process components
        const templates = doc.querySelectorAll('template[name]');
        for (const template of templates) {
          const componentName = template.getAttribute('name');
          const templateContent = template.innerHTML;
          ComponentManager.registerComponent(componentName, templateContent);
        }
  
        // Process scripts and nested loads
        await ScriptProcessor.processScripts(doc);
  
        // Insert content into the target element if specified
        if (targetSelector) {
          DOMHandler.insertContent(doc, targetSelector);
        }
      } catch (error) {
        // Error has already been logged in Loader; no need to throw
      }
    };
  })();