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

----

Task
You must integrate, without altering or changing anything from the current compiler features, a way of passing and reading props to components. Looking for `prop.{...}` declarations into components, and creating an object `prop` inside the Component Class, all this during the HTML to Class JS transformation. 

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

<!-- tester.comp.html -->
<counter-logic>
    <div class="card">
      <h1>{prop.uid}</h1>
      <h1 class="{status} title">You have clicked, {count}!</h1>
      <button onclick="counter">Increment</button>
    </div>
</counter-logic>
  
<script>
  let count = 0;
  let status = 'on';
  
  function counter() {
    count++
    if(status === 'on') {status = 'off'}
    else {status = 'on'}
  }
</script>
  
<style>
  .card {
    border: 1px solid #ccc;
    padding: 10px;
  }
  .on{
    color: red;
  }
  .off{
    color: blue;
  }
  .title{
    font-size: larger;
  }
</style>

Once you done the integration, output the files without cuts or abbreviations. Take all the time you need, and do simulations of how would look the counter-logic component after compiled.