```
project-root/
├── cat/
│   ├── bin/
│   │   └── cat.js
│   └── src/
│       └── compiler.js
|       └── component.js
|       └── page.js
├── index.html
├── package.json
└── cat.config.json
```

```
// cat/src/compiler.js

const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');
const { parseComponent, parseScript, generateClassCode, transformMethodBody, generateDOMCode } = require('./component');
const { parsePage, detectLoadRoutes } = require('./page');
const { parseDocument } = require('htmlparser2');

/**
 * Detect used components in the provided HTML contents by parsing the HTML tags.
 * @param {Array<string>} htmlContents - Array of HTML file contents.
 * @param {Set<string>} availableComponents - Set of all available component tag names.
 * @returns {Set<string>} - Set of used component tag names.
 */
function detectUsedComponents(htmlContents, availableComponents) {
  const usedComponents = new Set();

  // Regex to match all tags in the HTML
  const tagRegex = /<\s*([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*>/g;

  htmlContents.forEach(content => {
    let match;
    while ((match = tagRegex.exec(content)) !== null) {
      let tag = match[1];
      if (availableComponents.has(tag)) {
        usedComponents.add(tag);
      }
    }
  });

  return usedComponents;
}

/**
 * Generate the registration script for the used components.
 * @param {Map<string, string>} components - Map of tagName to compiled class code.
 * @returns {string} - JavaScript code for registering components.
 */
function generateRegistrationScript(components) {
  let script = '';

  components.forEach((classCode) => {
    script += `
${classCode}
  `;
  });

  return script;
}

/**
 * Compiler Function
 * @param {string} indexPath - Path to the index.html file.
 * @returns {Promise<string>} - Compiled HTML content with component registrations and page injections.
 */
async function compile(indexPath) {
  // Read the index.html content
  let indexContent = await fs.readFile(indexPath, 'utf-8');

  // Define project root
  const projectRoot = path.dirname(indexPath);

  // -----------------------
  // Step 1: Process Components
  // -----------------------

  // Find all .comp.html files in the project directory
  const compFiles = glob.sync('**/*.comp.html', {
    cwd: projectRoot,
    absolute: true,
    ignore: ['node_modules/**', 'cat/**'] // Ignore node_modules and cat directory to prevent recursion
  });

  const componentMap = new Map(); // Map of tagName to compiled class code
  const parsedComponents = new Set(); // To track parsed components

  // Parse and compile each component file
  for (const file of compFiles) {
    try {
      const { tagName, html, script, style } = await parseComponent(file);

      if (!parsedComponents.has(tagName)) {
        // Parse script to extract properties and methods
        const { properties, methods } = parseScript(script);

        // Generate class code
        const classCode = generateClassCode({
          tagName,
          html,
          style,
          properties,
          methods,
        });

        componentMap.set(tagName, classCode);
        parsedComponents.add(tagName);

        console.log(`🔍 Parsed and compiled component: <${tagName}> from ${path.relative(projectRoot, file)}`);
      } else {
        console.log(`ℹ️  Component <${tagName}> from ${path.relative(projectRoot, file)} has already been parsed.`);
      }
    } catch (error) {
      console.error(`❌ Failed to parse ${file}: ${error.message}`);
    }
  }

  if (componentMap.size === 0) {
    console.warn('⚠️  No components found to compile.');
  }

  // -----------------------
  // Step 2: Process Pages
  // -----------------------

  // Find all .page.html files in the project directory
  const pageFiles = glob.sync('**/*.page.html', {
    cwd: projectRoot,
    absolute: true,
    ignore: ['node_modules/**', 'cat/**']
  });

  const routeMap = new Map(); // Map of route to page content

  for (const file of pageFiles) {
    try {
      const { route, content } = await parsePage(file);
      if (routeMap.has(route)) {
        console.warn(`⚠️  Duplicate route "${route}" found in ${path.relative(projectRoot, file)}. Overwriting previous route.`);
      }
      routeMap.set(route, content);
      console.log(`📄 Mapped route "${route}" to ${path.relative(projectRoot, file)}`);
    } catch (error) {
      console.error(`❌ Failed to parse page ${file}: ${error.message}`);
    }
  }

  if (routeMap.size === 0) {
    console.warn('⚠️  No pages found to map routes.');
  }

  // -----------------------
  // Step 3: Detect Used Components
  // -----------------------

  // Find all .html files in the project directory for usage detection
  const htmlFiles = glob.sync('**/*.html', {
    cwd: projectRoot,
    absolute: true,
    ignore: ['node_modules/**', 'cat/**']
  });

  const htmlContents = [];
  for (const file of htmlFiles) {
    try {
      const content = await fs.readFile(file, 'utf-8');
      htmlContents.push(content);
    } catch (error) {
      console.error(`❌ Failed to read HTML file ${file}: ${error.message}`);
    }
  }

  // Detect which components are used across all HTML files
  const availableComponents = new Set(componentMap.keys());
  const usedComponents = detectUsedComponents(htmlContents, availableComponents);

  if (usedComponents.size === 0) {
    console.warn('⚠️  No used components found in HTML files.');
  } else {
    console.log(`📦 Used components: ${[...usedComponents].join(', ')}`);
  }

  // -----------------------
  // Step 4: Handle <load> Tags and Inject Page Content
  // -----------------------

  // Detect all <load> routes in all HTML files
  const loadRoutes = detectLoadRoutes(htmlContents);

  // Prepare a map of route to page content
  // Verify that each load route exists in the routeMap
  loadRoutes.forEach(route => {
    if (!routeMap.has(route)) {
      console.error(`❌ No page found for route "${route}". Please ensure a .page.html file exists with route="${route}".`);
    }
  });

  // Now, replace <load route="..."> tags in indexContent with the corresponding page content
  const loadTagRegex = /<load\s+[^>]*route=["']([^"']+)["'][^>]*>/gi;

  indexContent = indexContent.replace(loadTagRegex, (match, route) => {
    if (routeMap.has(route)) {
      console.log(`🔄 Injecting content for route "${route}"`);
      return routeMap.get(route);
    } else {
      console.warn(`⚠️  No content found for route "${route}". Keeping <load> tag.`);
      return match; // Keep the <load> tag if no content is found
    }
  });

  // -----------------------
  // Step 5: Generate Registration Script
  // -----------------------

  const registrationScript = generateRegistrationScript(
    Array.from(usedComponents).reduce((map, tag) => map.set(tag, componentMap.get(tag)), new Map())
  );

  // -----------------------
  // Step 6: Inject Registration Script
  // -----------------------

  if (registrationScript.trim()) {
    // Inject the registration script before the closing </body> tag
    const scriptTag = `<script>
${registrationScript}
</script>
`;

    if (indexContent.includes('</body>')) {
      indexContent = indexContent.replace('</body>', `${scriptTag}</body>`);
    } else {
      // If no </body> tag, append the script at the end
      indexContent += `\n${scriptTag}`;
    }
    console.log('📝 Injected component registration scripts.');
  }

  // -----------------------
  // Step 7: Return Compiled Content
  // -----------------------

  return indexContent;
}

module.exports = { compile };
```

```
// cat/src/component.js

const fs = require('fs-extra');
const path = require('path');
const acorn = require('acorn'); // Import Acorn for JavaScript parsing
const { parseDocument } = require('htmlparser2');

/**
 * Parse a component file to extract tagName, HTML, script, and style sections.
 * @param {string} filePath - Path to the .comp.html file.
 * @returns {Promise<{ tagName: string, html: string, script: string, style: string }>} - Component details.
 */
async function parseComponent(filePath) {
  const content = await fs.readFile(filePath, 'utf-8');

  // Extract the outermost tag name and inner HTML
  const componentRegex = /<([\w-]+)>([\s\S]*?)<\/\1>/;
  const componentMatch = content.match(componentRegex);

  if (!componentMatch) {
    throw new Error(`Invalid component structure in ${path.basename(filePath)}`);
  }

  const tagName = componentMatch[1];
  let innerContent = componentMatch[2];

  // Extract all <script> sections
  const scriptRegex = /<script>([\s\S]*?)<\/script>/g;
  let scriptContent = '';
  let scriptMatch;
  while ((scriptMatch = scriptRegex.exec(content)) !== null) {
    scriptContent += scriptMatch[1].trim() + '\n';
  }

  // Extract all <style> sections
  const styleRegex = /<style>([\s\S]*?)<\/style>/g;
  let styleContent = '';
  let styleMatch;
  while ((styleMatch = styleRegex.exec(content)) !== null) {
    styleContent += styleMatch[1].trim() + '\n';
  }

  // Remove all <script> and <style> tags from innerContent
  innerContent = innerContent
    .replace(scriptRegex, '')
    .replace(styleRegex, '')
    .trim();

  return {
    tagName,
    html: innerContent,
    script: scriptContent,
    style: styleContent,
  };
}

/**
 * Parse the script content to extract properties and methods.
 * @param {string} script - The script content.
 * @returns {{ properties: Array, methods: Array }} - Parsed properties and methods.
 */
function parseScript(script) {
  const properties = [];
  const methods = [];

  try {
    // Parse the script content into an AST
    const ast = acorn.parse(script, { ecmaVersion: 2020, locations: true });

    ast.body.forEach(node => {
      if (node.type === 'VariableDeclaration') {
        node.declarations.forEach(declarator => {
          if (declarator.id.type === 'Identifier') {
            const varName = declarator.id.name;
            const varValue = script.substring(declarator.init.start, declarator.init.end);
            properties.push({ name: varName, value: varValue });
          }
        });
      } else if (node.type === 'FunctionDeclaration') {
        const funcName = node.id.name;
        const params = node.params.map(param => script.substring(param.start, param.end)).join(', ');
        const body = script.substring(node.body.start, node.body.end);
        methods.push({ name: funcName, params, body });
      }
    });
  } catch (error) {
    console.error(`Error parsing script: ${error.message}`);
  }

  return { properties, methods };
}

/**
 * Convert a kebab-case or other tag name to PascalCase for class naming.
 * @param {string} tagName - The custom element tag name.
 * @returns {string} - PascalCase string.
 */
function toPascalCase(tagName) {
  return tagName
    .split('-')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join('');
}

/**
 * Escape backticks in templates to avoid breaking template literals.
 * @param {string} str - The template string.
 * @returns {string} - Escaped string.
 */
function escapeBackticks(str) {
  return str.replace(/`/g, '\\`');
}

/**
 * Generate the class code for the component.
 * @param {object} component - Component details.
 * @returns {string} - The compiled class code.
 */
function generateClassCode({ tagName, html, style, properties, methods }) {
  const className = toPascalCase(tagName);
  let classCode = `class ${className} extends HTMLElement {
  constructor() {
    super();
`;

  // Initialize properties
  properties.forEach(prop => {
    classCode += `    this._${prop.name} = ${prop.value};
`;
  });

  classCode += `
    this.attachShadow({ mode: 'open' });
    this._buildDOM();
  }

  _buildDOM() {
    const fragment = document.createDocumentFragment();
`;

  // Add styles
  if (style) {
    classCode += `    const style = document.createElement('style');
    style.textContent = \`
${style}
    \`;
    fragment.appendChild(style);

`;
  }

  // Generate DOM creation code
  const { code: domCode, cachedNodes, eventListeners } = generateDOMCode(html);
  classCode += domCode;

  classCode += `
    this.shadowRoot.appendChild(fragment);
  }
`;

  // connectedCallback
  classCode += `
  connectedCallback() {
`;
  
  // Attach event listeners
  if (eventListeners && eventListeners.length > 0) {
    eventListeners.forEach(listener => {
      classCode += `    this.${listener.elementVarName}.addEventListener('${listener.event}', this.${listener.handlerName}.bind(this));
`;
    });
  }

  classCode += `  }

  disconnectedCallback() {
`;

  // Remove event listeners
  if (eventListeners && eventListeners.length > 0) {
    eventListeners.forEach(listener => {
      classCode += `    this.${listener.elementVarName}.removeEventListener('${listener.event}', this.${listener.handlerName}.bind(this));
`;
    });
  }

  classCode += `  }
`;

  // Generate getters and setters
  Object.keys(cachedNodes).forEach(propName => {
    const nodes = cachedNodes[propName];

    classCode += `
  get ${propName}() { return this._${propName}; }
  set ${propName}(value) {
    if (this._${propName} !== value) {
      this._${propName} = value;
`;
    
    nodes.forEach(node => {
      if (node.type === 'text') {
        classCode += `      this.${node.varName}.textContent = ${node.expression};
`;
      } else if (node.type === 'attribute') {
        classCode += `      this.${node.elementVarName}.setAttribute('${node.attrName}', ${node.expression});
`;
      }
    });

    classCode += `    }
  }
`;
  });

  // Add methods
  methods.forEach(method => {
    const transformedBody = transformMethodBody(method.body, properties.map(p => p.name));
    classCode += `
  ${method.name}(${method.params}) {
${transformedBody}
  }
`;
  });

  classCode += `}
  
customElements.define('${tagName}', ${className});
`;

  return classCode;
}

/**
 * Transform method body to replace variable references with 'this.' prefix.
 * @param {string} body - The method body.
 * @param {Array<string>} propNames - The property names to replace.
 * @returns {string} - The transformed method body.
 */
function transformMethodBody(body, propNames) {
  let transformedBody = body;

  propNames.forEach(propName => {
    // Replace assignments like 'propName = value;' with 'this.propName = value;'
    const assignRegex = new RegExp(`([^\\.\\w$])${propName}\\s*=`, 'g');
    transformedBody = transformedBody.replace(assignRegex, `$1this.${propName} =`);

    // Replace variable references with 'this.propName'
    const varRegex = new RegExp(`([^\\.\\w$])${propName}([^\\w$])`, 'g');
    transformedBody = transformedBody.replace(varRegex, `$1this.${propName}$2`);
  });

  // Ensure braces are correctly balanced and formatted
  transformedBody = transformedBody.trim();

  // Remove extra outer braces if they exist
  if (transformedBody.startsWith('{') && transformedBody.endsWith('}')) {
    transformedBody = transformedBody.slice(1, -1).trim();
  }

  // Properly indent the method body
  transformedBody = transformedBody
    .split('\n')
    .map(line => '    ' + line.trim())
    .join('\n');

  return transformedBody;
}

/**
 * Generate DOM creation code from HTML using htmlparser2.
 * @param {string} html - The inner HTML string of the component.
 * @returns {object} - Object containing generated code, cached nodes, and event listeners.
 */
function generateDOMCode(html) {
  const dom = parseDocument(html).children; // Array of DOM nodes

  let code = '';
  let varCounter = 0;
  const cachedNodes = {}; // For properties to nodes
  const eventListeners = [];

  function traverse(node, parentVarName) {
    if (node.type === 'tag') {
      const varName = node.name + ++varCounter;

      // Decide whether to assign to 'this.' or 'const'
      let declaration;
      if (needsReference(node)) {
        declaration = `this.${varName}`;
      } else {
        declaration = `const ${varName}`;
      }

      const varReference = declaration.replace(/^const /, '');
      code += `    ${declaration} = document.createElement('${node.name}');
`;

      // Handle attributes
      const attribs = node.attribs || {};
      for (let [attrName, attrValue] of Object.entries(attribs)) {
        if (attrName === 'class' || attrName === 'style' || attrName.startsWith('data-') || attrName === 'id' || attrName === 'src' || attrName === 'alt') {
          // Handle attributes with embedded expressions
          const { expression, propNames } = generateAttributeExpression(attrValue);
          code += `    ${varReference}.setAttribute('${attrName}', ${expression});
`;

          // Cache nodes for reactive updates
          propNames.forEach(propName => {
            if (!cachedNodes[propName]) cachedNodes[propName] = [];
            cachedNodes[propName].push({
              type: 'attribute',
              elementVarName: varReference.replace(/^this\./, ''),
              attrName: attrName,
              expression: generateAttributeExpression(attrValue).expression,
            });
          });
        } else if (attrName.startsWith('on')) {
          // Event listener
          const event = attrName.substring(2);
          const handlerName = attrValue.replace(/\(\)$/, '').trim();
          eventListeners.push({ elementVarName: varReference.replace(/^this\./, ''), event, handlerName });
        } else {
          // For other attributes
          code += `    ${varReference}.setAttribute('${attrName}', '${attrValue}');
`;
        }
      }

      // Append to parent
      const parentRef = parentVarName ? parentVarName : 'fragment';
      code += `    ${parentRef}.appendChild(${varReference});
`;

      // Traverse children
      if (node.children && node.children.length) {
        for (let child of node.children) {
          traverse(child, varReference);
        }
      }
    } else if (node.type === 'text') {
      const textContent = node.data;

      if (textContent.trim()) {
        // Handle text nodes with expressions
        const { expression, propNames } = generateTextExpression(textContent);

        const varName = 'textNode' + ++varCounter;

        // Decide whether to assign to 'this.' or 'const'
        let declaration;
        if (expression.includes('this._')) {
          declaration = `this.${varName}`;
        } else {
          declaration = `const ${varName}`;
        }

        const varReference = declaration.replace(/^const /, '');
        code += `    ${declaration} = document.createTextNode(${expression});
`;
        code += `    ${parentVarName}.appendChild(${varReference});
`;

        // Cache nodes for reactive updates
        propNames.forEach(propName => {
          if (!cachedNodes[propName]) cachedNodes[propName] = [];
          cachedNodes[propName].push({
            type: 'text',
            varName: varReference.replace(/^this\./, ''),
            expression: generateTextExpression(textContent).expression,
          });
        });
      }
    }
  }

  function needsReference(node) {
    // Determines if the element needs to be assigned to 'this.'
    // Elements that are:
    // - Targets of event listeners
    // - Contain reactive expressions
    // Need to be accessible outside of _buildDOM
    if (node.attribs) {
      const hasEventListener = Object.keys(node.attribs).some(attr => attr.startsWith('on'));
      const hasReactiveAttr = Object.values(node.attribs).some(value => /\{([\w$]+)\}/.test(value));
      if (hasEventListener || hasReactiveAttr) {
        return true;
      }
    }

    if (node.children && node.children.some(child => child.type === 'text' && /\{([\w$]+)\}/.test(child.data))) {
      return true;
    }

    return false;
  }

  function generateTextExpression(textContent) {
    const exprRegex = /\{([\w$]+)\}/g;
    let segments = [];
    let propNames = [];
    let lastIndex = 0;
    let match;

    while ((match = exprRegex.exec(textContent)) !== null) {
      if (match.index > lastIndex) {
        segments.push(JSON.stringify(textContent.slice(lastIndex, match.index)));
      }
      segments.push(`this._${match[1]}`);
      propNames.push(match[1]);
      lastIndex = exprRegex.lastIndex;
    }

    if (lastIndex < textContent.length) {
      segments.push(JSON.stringify(textContent.slice(lastIndex)));
    }

    return {
      expression: segments.join(' + '),
      propNames,
    };
  }

  function generateAttributeExpression(attrValue) {
    const exprRegex = /\{([\w$]+)\}/g;
    let segments = [];
    let propNames = [];
    let lastIndex = 0;
    let match;

    while ((match = exprRegex.exec(attrValue)) !== null) {
      if (match.index > lastIndex) {
        segments.push(JSON.stringify(attrValue.slice(lastIndex, match.index)));
      }
      segments.push(`this._${match[1]}`);
      propNames.push(match[1]);
      lastIndex = exprRegex.lastIndex;
    }

    if (lastIndex < attrValue.length) {
      segments.push(JSON.stringify(attrValue.slice(lastIndex)));
    }

    return {
      expression: segments.join(' + '),
      propNames,
    };
  }

  dom.forEach(node => traverse(node, null));

  return { code, cachedNodes, eventListeners };
}

module.exports = {
  parseComponent,
  parseScript,
  toPascalCase,
  escapeBackticks,
  generateClassCode,
  transformMethodBody,
  generateDOMCode
};
```

```
// cat/src/page.js

const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');

/**
 * Parse a page file to extract the route and its content.
 * @param {string} filePath - Path to the .page.html file.
 * @returns {Promise<{ route: string, content: string }>} - Page details.
 */
async function parsePage(filePath) {
  const content = await fs.readFile(filePath, 'utf-8');

  // Regex to match the <page> tag and extract the route attribute
  const pageTagRegex = /<page\s+[^>]*route=["']([^"']+)["'][^>]*>/i;
  const match = content.match(pageTagRegex);

  if (!match) {
    throw new Error(`No valid <page> tag with 'route' attribute found in ${path.basename(filePath)}`);
  }

  const route = match[1];

  // Extract the inner HTML of the <page> tag
  const innerContentRegex = new RegExp(`<page[^>]*>([\\s\\S]*?)<\\/page>`, 'i');
  const innerMatch = content.match(innerContentRegex);

  const pageContent = innerMatch ? innerMatch[1].trim() : '';

  return { route, content: pageContent };
}

/**
 * Detect all <load> tags in the provided HTML contents and extract their routes.
 * @param {Array<string>} htmlContents - Array of HTML file contents.
 * @returns {Array<string>} - Array of routes found in <load> tags.
 */
function detectLoadRoutes(htmlContents) {
  const loadRoutes = [];

  // Regex to match <load route="..."> tags
  const loadTagRegex = /<load\s+[^>]*route=["']([^"']+)["'][^>]*>/gi;

  htmlContents.forEach(content => {
    let match;
    while ((match = loadTagRegex.exec(content)) !== null) {
      const route = match[1];
      loadRoutes.push(route);
    }
  });

  return loadRoutes;
}

module.exports = {
  parsePage,
  detectLoadRoutes
};
```

Task
Your task is to update `page.js` to compile the `{filename}.page.html` files as we do with the components. This involves transforming HTML into JavaScript, setting up dynamic expressions, getters, setters, and custom methods, same as how components are compiled. However, instead of registering these as custom elements, they will be defined within a class. The class name will be randomly generated. The `page.js` file will generate a router class responsible for constructing the page, where the load tag is defined.