/**
 * Class to format HTML code with proper indentation and style cleanup.
 */
class CodeFormatter {
  constructor() {
    /**
     * Formats a given HTML source string by applying indentation and cleaning up styles.
     * Content inside preserve tags (pre) is preserved as-is without formatting.
     *
     * @param {string} source - The HTML source string to format.
     * @param {CodeFormatOptions} options - Formatting options.
     * @returns {string} The formatted HTML string.
     */
    this.format = (source, options = {}) => {
      const { tabWidth = 4, useTabs = true } = options;
      const indentUnit = useTabs ? '\t' : ' ';
      const INDENT = indentUnit.repeat(tabWidth);
      const tagRegex = /(<\/?[^>]+>)/g;
      // Split source into tokens using tag boundaries while filtering out empty strings
      const tokens = source.split(tagRegex).filter((t) => t.length > 0);
      let indentLevel = 0;
      let output = '';
      let preserveDepth = 0;
      for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        const tagName = CodeFormatter.getTagName(token);
        // Track if we're entering or exiting preserve tags
        if (CodeFormatter.PRESERVE_CONTENT_TAGS.has(tagName)) {
          if (token.startsWith('</')) {
            preserveDepth = Math.max(0, preserveDepth - 1);
          }
          else if (token.startsWith('<')) {
            preserveDepth++;
          }
        }
        // If inside preserve tag, output as-is without formatting
        if (preserveDepth > 0) {
          output += token;
          continue;
        }
        // Decrease indentation for closing tags (after exiting preserve tags)
        if (token.startsWith('</')) {
          indentLevel = Math.max(0, indentLevel - 1);
        }
        const indent = INDENT.repeat(indentLevel);
        if (token.startsWith('<')) {
          // Clean and normalize inline style attributes
          token = token.replace(/style\s*=\s*"([^"]*)"/g, (_, styleContent) => {
            return `style="${this.formatStyle(styleContent)}"`;
          });
          const isInline = CodeFormatter.INLINE_TAGS.has(tagName);
          // Inline tag grouping: e.g., <strong>Text</strong> or <span></span> stays on one line
          // Check for empty tags: <tag></tag> (closing tag immediately follows opening tag)
          if (isInline && i + 1 < tokens.length && tokens[i + 1] === `</${tagName}>`) {
            // Empty inline tag: <span></span>
            output += `${indent}${token}${tokens[i + 1]}\n`;
            i += 1;
          }
          else if (isInline && i + 2 < tokens.length && tokens[i + 2] === `</${tagName}>`) {
            // Group consecutive inline tags with content
            let inlineGroup = `${token}${tokens[i + 1]}${tokens[i + 2]}`;
            i += 2;
            // Keep merging consecutive inline tag pairs
            while (i + 3 < tokens.length &&
              tokens[i + 1].startsWith('<') &&
              CodeFormatter.INLINE_TAGS.has(CodeFormatter.getTagName(tokens[i + 1])) &&
              tokens[i + 3] === `</${CodeFormatter.getTagName(tokens[i + 1])}>`) {
              inlineGroup += `${tokens[i + 1]}${tokens[i + 2]}${tokens[i + 3]}`;
              i += 3;
            }
            output += `${indent}${inlineGroup}\n`;
          }
          else {
            output += `${indent}${token}\n`;
            // Increase indentation for non-void opening tags
            if (!token.startsWith('</') &&
              !token.endsWith('/>') &&
              !CodeFormatter.VOID_TAGS.has(tagName)) {
              indentLevel++;
            }
          }
        }
        else {
          // Add plain text content with current indentation
          const text = token;
          if (text.length > 0) {
            output += `${INDENT.repeat(indentLevel)}${text}\n`;
          }
        }
      }
      return output.trim();
    };
  }
  /**
   * Extracts the tag name from an HTML tag string.
   *
   * @param {string} tag - A raw HTML tag string
   * @returns {string} The lowercase tag name, or an empty string if no match is found.
   */
  static getTagName(tag) {
    var _a;
    const match = tag.match(/^<\/?([a-z0-9]+)/i);
    return (_a = match === null || match === void 0 ? void 0 : match[1].toLowerCase()) !== null && _a !== void 0 ? _a : '';
  }
  /**
   * Formats a CSS style string by trimming white space and ensuring consistent semicolon spacing.
   *
   * @param {string} style - The CSS style string to format.
   * @returns {string} The formatted style string.
   */
  formatStyle(style) {
    return style
      .split(';')
      .map((s) => s.trim())
      .filter(Boolean)
      .join('; ');
  }
}
/**
 * Inline/phrasing elements that should be kept on a single line with their content.
 * These tags are formatted as: <tag>content</tag> on one line.
 */
CodeFormatter.INLINE_TAGS = new Set([
  'span',
  'strong',
  'em',
  'a',
  'b',
  'i',
  'u',
  'small',
  'abbr',
  'code',
  'b',
  'u',
  's',
  'mark',
  'time',
  'del',
  'ins',
]);
/**
 * Void/self-closing elements that don't have closing tags.
 * Used to avoid increasing indentation for these elements.
 */
CodeFormatter.VOID_TAGS = new Set([
  'area',
  'base',
  'br',
  'col',
  'embed',
  'hr',
  'img',
  'input',
  'link',
  'meta',
  'param',
  'source',
  'track',
  'wbr',
]);
/**
 * Tags whose content should be preserved as-is without formatting.
 */
CodeFormatter.PRESERVE_CONTENT_TAGS = new Set(['pre']);
export default new CodeFormatter();
