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

const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');
const {
  parseComponent,
  generateComponentClassCode,
} = require('./component');
const {
  parsePageComponent,
  generatePageClassCode,
} = require('./page');
const { parseScript } = require('./scripter');

/**
 * 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 components and pages.
 * @param {Map<string, string>} classes - Map of tagName to compiled class code.
 * @returns {string} - JavaScript code for registering components and pages.
 */
function generateRegistrationScript(classes) {
  let script = '';

  classes.forEach((classCode, tagName) => {
    script += `
${classCode}
    `;
  });

  return script;
}

/**
 * Generate the router script for handling <load> and <router> elements.
 * @param {Array<{ route: string, tagName: string }>} routeToTagName - Mappings of routes to tag names.
 * @returns {string} - JavaScript code for the router.
 */
function generateRouterScript(routeToTagName) {
  let routeMappings = routeToTagName
    .map(({ route, tagName }) => `    '${route}': '${tagName}'`)
    .join(',\n');

  return `
const routeToTagName = {
${routeMappings}
};

function loadRouteContent(routerId, route) {
  const tagName = routeToTagName[route];
  if (tagName) {
    const routerElement = document.getElementById(routerId);
    if (routerElement) {
      // Clear existing content
      routerElement.innerHTML = '';
      // Create and append the new page element
      const customElement = document.createElement(tagName);
      routerElement.appendChild(customElement);
      // Set up router for the newly loaded content
      setupRouterForElement(routerElement);
    } else {
      console.error('No router found with id:', routerId);
    }
  } else {
    console.error('No page found for route:', route);
  }
}

function handleRouterClick(event) {
  event.preventDefault();
  const navElement = event.currentTarget;
  const routerId = navElement.getAttribute('router');
  const route = navElement.getAttribute('route');

  // Remove 'current' attribute from all navigation elements with the same router
  document.querySelectorAll('[router="' + routerId + '"][route]').forEach(function(el) {
    el.removeAttribute('current');
  });

  // Set 'current' attribute on the clicked navigation element
  navElement.setAttribute('current', '');

  // Load the corresponding route content into the router container
  loadRouteContent(routerId, route);
}

function setupRouterForElement(element) {
  // Handle <load route=""> elements
  element.querySelectorAll('load[route]').forEach(function(loadElement) {
    const route = loadElement.getAttribute('route');
    const tagName = routeToTagName[route];
    if(tagName) {
      const customElement = document.createElement(tagName);
      // Copy all attributes from <load> to the custom element
      for (let i = 0; i < loadElement.attributes.length; i++) {
        let attr = loadElement.attributes[i];
        if (attr.name !== 'route') {
          customElement.setAttribute(attr.name, attr.value);
        }
      }
      loadElement.parentNode.replaceChild(customElement, loadElement);
      setupRouterForElement(customElement);
    } else {
      console.error('No page found for route:', route);
    }
  });

  // Handle navigation elements with [router] and [route] attributes
  element.querySelectorAll('[router][route]').forEach(function(navElement) {
    // Remove existing event listener if any
    navElement.removeEventListener('click', handleRouterClick);

    // Add the new click event listener
    navElement.addEventListener('click', handleRouterClick);
  });

  // Initial loading based on elements with 'current' attribute
  element.querySelectorAll('[router][route][current]').forEach(function(navElement) {
    const routerId = navElement.getAttribute('router');
    const route = navElement.getAttribute('route');
    loadRouteContent(routerId, route);
  });
}

function setupRouter() {
  setupRouterForElement(document);
}

document.addEventListener('DOMContentLoaded', setupRouter);
`;
}

/**
 * Compiler Function
 * @param {string} indexPath - Path to the index.html file.
 * @returns {Promise<string>} - Compiled HTML content with component registrations.
 */
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);

  // Initialize style accumulator
  let accumulatedStyles = '';

  // -----------------------
  // Step 1: Process Components and Pages
  // -----------------------

  // Find all .comp.html and .page.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 pageFiles = glob.sync('**/*.page.html', {
    cwd: projectRoot,
    absolute: true,
    ignore: ['node_modules/**', 'cat/**'],
  });

  const componentMap = new Map(); // Map of tagName to compiled class code
  const parsedComponents = new Set(); // To track parsed components
  const routeTagMap = []; // Array of { route, tagName }

  // 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 = generateComponentClassCode({
          tagName,
          html,
          style,
          properties,
          methods,
        });

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

        // Accumulate styles
        if (style && style.trim()) {
          accumulatedStyles += `\n/* Styles for component: ${tagName} */\n${style}`;
        }

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

  // Parse and compile each page file as a custom element
  for (const file of pageFiles) {
    try {
      const { tagName, route, html, script, style } = await parsePageComponent(
        file
      );

      // Generate class code for all pages, regardless of usage
      const { properties, methods } = parseScript(script);
      const classCode = generatePageClassCode({
        tagName,
        html,
        style,
        properties,
        methods,
      });

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

      // Accumulate styles
      if (style && style.trim()) {
        accumulatedStyles += `\n/* Styles for page: ${tagName} */\n${style}`;
      }

      // Add to routeTagMap
      routeTagMap.push({ route, tagName });

      console.log(
        `📄 Parsed and compiled page: <${tagName}> with route "${route}" from ${path.relative(
          projectRoot,
          file
        )}`
      );
    } catch (error) {
      console.error(`❌ Failed to parse page ${file}: ${error.message}`);
    }
  }

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

  // -----------------------
  // Step 2: Detect Used Components and Pages
  // -----------------------

  // 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 and pages are used across all HTML files
  const availableComponents = new Set(componentMap.keys());
  const usedComponents = detectUsedComponents(
    htmlContents,
    availableComponents
  );

  // Also detect used routes from <load> tags and <router> tags
  const loadTagRegex = /<load\s+[^>]*route=["']([^"']+)["'][^>]*>/gi;
  let loadRoutes = [];
  htmlContents.forEach((content) => {
    let match;
    while ((match = loadTagRegex.exec(content)) !== null) {
      const route = match[1];
      loadRoutes.push(route);
    }
  });

  // Additionally, handle <router name="..."/> tags
  const routerTagRegex = /<router\s+name=["']([^"']+)["']\s*\/>/gi;
  let routerNames = [];
  htmlContents.forEach((content) => {
    let match;
    while ((match = routerTagRegex.exec(content)) !== null) {
      const routerName = match[1];
      routerNames.push(routerName);
    }
  });

  // Map routes to tagNames
  loadRoutes.forEach((route) => {
    const mapping = routeTagMap.find((r) => r.route === route);
    if (mapping) {
      usedComponents.add(mapping.tagName);
    } else {
      console.error(
        `❌ No page found for route "${route}". Please ensure a .page.html file exists with route="${route}".`
      );
    }
  });

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

  // -----------------------
  // Step 3: Generate Registration and Router Script
  // -----------------------

  // Generate the registration script for all components and pages
  const registrationScript = generateRegistrationScript(componentMap);

  // Generate the router script
  const routerScript = generateRouterScript(routeTagMap);

  // Combine scripts
  const fullScript = `
${registrationScript}
${routerScript}
`;

  // -----------------------
  // Step 4: Inject Styles and Script
  // -----------------------

  // Inject the accumulated styles into the <head> tag
  if (accumulatedStyles.trim()) {
    const styleTag = `<style>
${accumulatedStyles}
</style>
`;

    // Check if <head> exists
    if (indexContent.includes('</head>')) {
      indexContent = indexContent.replace('</head>', `${styleTag}</head>`);
    } else {
      // If no </head>, check for <html> tag to insert after
      if (indexContent.includes('<html')) {
        indexContent = indexContent.replace(
          /<html[^>]*>/,
          `$&\n<head>\n${styleTag}\n</head>`
        );
      } else {
        // If no <html> tag, prepend the style tag
        indexContent = `<head>\n${styleTag}\n</head>\n${indexContent}`;
      }
    }
    console.log('🎨 Injected accumulated styles into <head>');
  }

  // Inject the script before the closing </body> tag
  if (fullScript.trim()) {
    const scriptTag = `<script>
${fullScript}
</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, page registrations, and router script.');
  }

  // -----------------------
  // Step 5: Return Compiled Content
  // -----------------------

  return indexContent;
}

module.exports = { compile };

// cat/src/scripter.js

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

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

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

/**
 * Sanitize a string to be a valid JavaScript variable name.
 * @param {string} name - The original name.
 * @returns {string} - The sanitized name.
 */
function sanitizeVarName(name) {
  // Replace invalid characters with underscores
  return name.replace(/[^a-zA-Z0-9_$]/g, '_');
}

/**
 * Generate DOM creation code from HTML using htmlparser2.
 * @param {string} html - The inner HTML string of the component or page.
 * @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 safeName = sanitizeVarName(node.name);
      const varName = safeName + ++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 /, '');

      if (node.name === 'router') {
        // Handle router tag
        const nameAttr = node.attribs.name;
        code += `    ${declaration} = document.createElement('div');
    ${varReference}.id = '${nameAttr}';
`;
      } else {
        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 if (node.name !== 'router' || attrName !== 'name') {
          // For other attributes, excluding 'name' attribute of router
          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 };
}

/**
 * Generate the class body code for components and pages.
 * @param {object} options - Options for class body generation.
 * @returns {string} - The class body code.
 */
function generateClassBodyCode({ properties, methods, domCode, cachedNodes, eventListeners, isComponent, parentClass }) {
  let classBody = '';

  // Constructor
  classBody += `  constructor() {
    `;

  // Include super() call if there is a parent class
  if (parentClass) {
    classBody += `super();
    `;
  }

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

  // For components, do not build DOM in constructor
  if (!isComponent) {
    // For pages, build DOM in constructor
    classBody += `
    this._buildDOM();
    `;
  }

  classBody += `  }

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

  // Include DOM creation code
  classBody += domCode;

  if (isComponent) {
    classBody += `
    this._fragment = fragment;
  }
  `;
  } else {
    classBody += `
    this.rootElement = fragment;
  }
  `;
  }

  // connectedCallback
  classBody += `
  connectedCallback() {
  `;

  if (isComponent) {
    classBody += `    this._buildDOM();
    this.appendChild(this._fragment);
  `;
  }

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

  disconnectedCallback() {
  `;

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

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

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

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

  return classBody;
}

module.exports = {
  parseScript,
  transformMethodBody,
  generateDOMCode,
  generateClassBodyCode,
};

// cat/src/component.js

const fs = require('fs-extra');
const path = require('path');
const {
  parseScript,
  generateDOMCode,
  generateClassBodyCode,
} = require('./scripter');

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

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

  // Adjust tagName if it doesn't have a hyphen
  if (!tagName.includes('-')) {
    tagName = `${tagName}-comp`;
  }

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

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

/**
 * Generate the class code for the component.
 * @param {object} component - Component details.
 * @returns {string} - The compiled class code.
 */
function generateComponentClassCode({
  tagName,
  html,
  style,
  properties,
  methods,
}) {
  const className = toPascalCase(tagName);

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

  // Generate class body
  const classBody = generateClassBodyCode({
    properties,
    methods,
    domCode,
    cachedNodes,
    eventListeners,
    isComponent: true, // Components are custom elements
    parentClass: 'HTMLElement', // Components extend HTMLElement
  });

  // Assemble the full class code
  let classCode = `class ${className} extends HTMLElement {
${classBody}
}

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

  return classCode;
}

module.exports = {
  parseComponent,
  generateComponentClassCode,
  toPascalCase,
};

// cat/src/page.js

const fs = require('fs-extra');
const path = require('path');
const {
  parseScript,
  generateDOMCode,
  generateClassBodyCode,
} = require('./scripter');
const { toPascalCase } = require('./component');

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

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

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

  let tagName = match[1];
  const attributesString = match[2];
  let innerContent = match[3];

  // Extract route attribute
  const routeRegex = /route\s*=\s*['"]([^'"]+)['"]/i;
  const routeMatch = attributesString.match(routeRegex);
  let route = '';
  if (routeMatch) {
    route = routeMatch[1];
  } else {
    throw new Error(
      `No 'route' attribute found in page tag in ${path.basename(filePath)}`
    );
  }

  // Adjust tagName if it doesn't have a hyphen
  if (!tagName.includes('-')) {
    tagName = `${tagName}-page`;
  }

  // Extract all <script> sections from the entire content
  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 from the entire content
  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,
    route,
    html: innerContent,
    script: scriptContent,
    style: styleContent,
  };
}

/**
 * Generate the class code for the page.
 * @param {object} page - Page details.
 * @returns {string} - The compiled class code.
 */
function generatePageClassCode({
  tagName,
  html,
  style,
  properties,
  methods,
}) {
  const className = toPascalCase(tagName);

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

  // Generate class body
  const classBody = generateClassBodyCode({
    properties,
    methods,
    domCode,
    cachedNodes,
    eventListeners,
    isComponent: true, // Pages are custom elements
    parentClass: 'HTMLElement', // Pages extend HTMLElement
  });

  // Assemble the full class code
  let classCode = `class ${className} extends HTMLElement {
${classBody}
}

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

  return classCode;
}

module.exports = {
  parsePageComponent,
  generatePageClassCode,
};
```

Instructions:
Enable data binding for components, allowing users to define custom attributes. During compilation, these attributes will be transformed into 'let' variables in the class constructor, complete with their respective getters and setters. Additionally, the attributes will be adapted to align with the native data binding API for custom elements, using the format `data-{attribute name}`.

Once finish, output the files, only those that suffered a modification, fully without cuts or abbreviations.

Expected usage syntax
<!-- counter.page.html -->
<counter route="/counter">
	<item-comp user="" email=""></item-comp>
</counter>

<!-- item-comp.comp.html -->
<item-comp>
	  <p>{data.user}</p>
	  <p>{data.email}</p>
</item-comp>