#!/usr/bin/env python3
"""
Script para comparar dois arquivos HTML e identificar divergências visuais.
Foco em: estrutura DOM, classes CSS/Tailwind, atributos relevantes, texto.
"""

import re
from html.parser import HTMLParser
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List, Set, Tuple, Optional
from difflib import unified_diff

@dataclass
class NodeDiff:
    """Representa uma diferença encontrada entre dois nós"""
    path: str
    type: str  # 'structure', 'classes', 'text', 'attributes'
    vue_value: Optional[str]
    blade_value: Optional[str]
    severity: int  # 1-10, onde 10 é mais crítico visualmente

class HTMLNormalizer:
    """Normaliza HTML removendo whitespace redundante e ordenando atributos"""
    
    @staticmethod
    def normalize(html: str) -> str:
        """Normaliza HTML para comparação"""
        # Remove comentários HTML
        html = re.sub(r'<!--.*?-->', '', html, flags=re.DOTALL)
        
        # Remove whitespace entre tags
        html = re.sub(r'>\s+<', '><', html)
        
        # Normaliza espaços em branco dentro de texto
        html = re.sub(r'\s+', ' ', html)
        
        # Remove espaços no início e fim
        html = html.strip()
        
        return html
    
    @staticmethod
    def normalize_attributes(tag: str) -> str:
        """Normaliza atributos de uma tag HTML ordenando-os"""
        # Extrai nome da tag e atributos
        match = re.match(r'<(\w+)(.*?)(/?)>', tag, re.DOTALL)
        if not match:
            return tag
        
        tag_name = match.group(1)
        attrs_str = match.group(2).strip()
        self_closing = match.group(3)
        
        if not attrs_str:
            return f'<{tag_name}{self_closing}>'
        
        # Extrai pares atributo="valor"
        attr_pattern = r'(\w+(?:-\w+)*)=["\']([^"\']*)["\']'
        attrs = re.findall(attr_pattern, attrs_str)
        
        # Ordena atributos por nome
        attrs.sort(key=lambda x: x[0])
        
        # Reconstrói a tag
        attrs_str_normalized = ' '.join(f'{k}="{v}"' for k, v in attrs)
        return f'<{tag_name} {attrs_str_normalized}{self_closing}>'

class HTMLParser(HTMLParser):
    """Parser HTML que extrai estrutura, classes e atributos"""
    
    def __init__(self):
        super().__init__()
        self.nodes = []
        self.current_path = []
        self.current_node = None
    
    def handle_starttag(self, tag, attrs):
        self.current_path.append(tag)
        path_str = '/'.join(self.current_path)
        
        # Extrai classes
        classes = set()
        attrs_dict = {}
        for attr, value in attrs:
            attrs_dict[attr] = value
            if attr == 'class':
                classes = set(value.split())
        
        self.nodes.append({
            'path': path_str,
            'tag': tag,
            'classes': classes,
            'attrs': attrs_dict,
            'text': None
        })
    
    def handle_endtag(self, tag):
        if self.current_path and self.current_path[-1] == tag:
            self.current_path.pop()
    
    def handle_data(self, data):
        data = data.strip()
        if data and self.nodes:
            if self.nodes[-1]['text'] is None:
                self.nodes[-1]['text'] = data
            else:
                self.nodes[-1]['text'] += ' ' + data

def parse_html(html: str) -> List[Dict]:
    """Parse HTML e retorna lista de nós"""
    normalized = HTMLNormalizer.normalize(html)
    parser = HTMLParser()
    parser.feed(normalized)
    return parser.nodes

def extract_classes_by_selector(html: str) -> Dict[str, Set[str]]:
    """Extrai classes agrupadas por seletor CSS aproximado"""
    parser = HTMLParser()
    parser.feed(HTMLNormalizer.normalize(html))
    
    class_map = defaultdict(set)
    for node in parser.nodes:
        selector = node['path']
        class_map[selector].update(node['classes'])
    
    return dict(class_map)

def compare_structure(vue_nodes: List[Dict], blade_nodes: List[Dict]) -> List[NodeDiff]:
    """Compara estrutura DOM entre Vue e Blade"""
    diffs = []
    
    vue_paths = {n['path']: n for n in vue_nodes}
    blade_paths = {n['path']: n for n in blade_nodes}
    
    # Nós que existem em Vue mas não em Blade
    for path, node in vue_paths.items():
        if path not in blade_paths:
            diffs.append(NodeDiff(
                path=path,
                type='structure',
                vue_value=f"<{node['tag']}>",
                blade_value=None,
                severity=8  # Alto - estrutura faltando
            ))
    
    # Nós que existem em Blade mas não em Vue
    for path, node in blade_paths.items():
        if path not in vue_paths:
            diffs.append(NodeDiff(
                path=path,
                type='structure',
                vue_value=None,
                blade_value=f"<{node['tag']}>",
                severity=8  # Alto - estrutura extra
            ))
    
    return diffs

def compare_classes(vue_nodes: List[Dict], blade_nodes: List[Dict]) -> List[NodeDiff]:
    """Compara classes CSS entre nós equivalentes"""
    diffs = []
    
    vue_map = {n['path']: n for n in vue_nodes}
    blade_map = {n['path']: n for n in blade_nodes}
    
    # Classes críticas visualmente (layout, spacing, typography, breakpoints)
    critical_classes = {
        'container', 'row', 'col', 'flex', 'grid', 'block', 'inline',
        'hidden', 'visible', 'absolute', 'relative', 'fixed', 'sticky',
        'w-', 'h-', 'p-', 'm-', 'px-', 'py-', 'mx-', 'my-', 'pt-', 'pb-', 'pl-', 'pr-',
        'mt-', 'mb-', 'ml-', 'mr-', 'space-', 'gap-',
        'text-', 'font-', 'leading-', 'tracking-',
        'bg-', 'border-', 'rounded-',
        'sm:', 'md:', 'lg:', 'xl:', '2xl:'
    }
    
    common_paths = set(vue_map.keys()) & set(blade_map.keys())
    
    for path in common_paths:
        vue_classes = vue_map[path]['classes']
        blade_classes = blade_map[path]['classes']
        
        missing_in_blade = vue_classes - blade_classes
        extra_in_blade = blade_classes - vue_classes
        
        if missing_in_blade or extra_in_blade:
            # Calcula severidade baseada em classes críticas
            severity = 5  # Base
            if any(c.startswith(tuple(critical_classes)) or any(cc in c for cc in critical_classes) 
                   for c in (missing_in_blade | extra_in_blade)):
                severity = 9  # Muito crítico - classes de layout/spacing
            
            diffs.append(NodeDiff(
                path=path,
                type='classes',
                vue_value=', '.join(sorted(vue_classes)),
                blade_value=', '.join(sorted(blade_classes)),
                severity=severity
            ))
    
    return diffs

def compare_text(vue_nodes: List[Dict], blade_nodes: List[Dict]) -> List[NodeDiff]:
    """Compara texto entre nós equivalentes"""
    diffs = []
    
    vue_map = {n['path']: n for n in vue_nodes}
    blade_map = {n['path']: n for n in blade_nodes}
    
    common_paths = set(vue_map.keys()) & set(blade_map.keys())
    
    for path in common_paths:
        vue_text = (vue_map[path].get('text') or '').strip()
        blade_text = (blade_map[path].get('text') or '').strip()
        
        # Ignora diferenças de whitespace
        vue_text_norm = ' '.join(vue_text.split())
        blade_text_norm = ' '.join(blade_text.split())
        
        if vue_text_norm != blade_text_norm:
            severity = 3 if len(vue_text_norm) < 20 else 6  # Mais crítico se texto longo
            
            diffs.append(NodeDiff(
                path=path,
                type='text',
                vue_value=vue_text_norm[:100],
                blade_value=blade_text_norm[:100],
                severity=severity
            ))
    
    return diffs

def compare_attributes(vue_nodes: List[Dict], blade_nodes: List[Dict]) -> List[NodeDiff]:
    """Compara atributos relevantes (href, src, alt, aria-*, data-*)"""
    diffs = []
    
    relevant_attrs = {'href', 'src', 'alt', 'aria-label', 'aria-hidden', 'data-*'}
    
    vue_map = {n['path']: n for n in vue_nodes}
    blade_map = {n['path']: n for n in blade_nodes}
    
    common_paths = set(vue_map.keys()) & set(blade_map.keys())
    
    for path in common_paths:
        vue_attrs = vue_map[path]['attrs']
        blade_attrs = blade_map[path]['attrs']
        
        all_attrs = set(vue_attrs.keys()) | set(blade_attrs.keys())
        
        for attr in all_attrs:
            if any(attr.startswith(ra.replace('*', '')) for ra in relevant_attrs):
                vue_val = vue_attrs.get(attr, None)
                blade_val = blade_attrs.get(attr, None)
                
                if vue_val != blade_val:
                    severity = 7 if attr in ('href', 'src') else 5
                    
                    diffs.append(NodeDiff(
                        path=f"{path}[{attr}]",
                        type='attributes',
                        vue_value=vue_val,
                        blade_value=blade_val,
                        severity=severity
                    ))
    
    return diffs

def main():
    """Função principal"""
    import sys
    
    if len(sys.argv) < 3:
        print("Uso: python compare_html.py <vue.html> <blade.html>")
        sys.exit(1)
    
    vue_file = sys.argv[1]
    blade_file = sys.argv[2]
    
    try:
        with open(vue_file, 'r', encoding='utf-8') as f:
            vue_html = f.read()
        
        with open(blade_file, 'r', encoding='utf-8') as f:
            blade_html = f.read()
    except FileNotFoundError as e:
        print(f"Erro: Arquivo não encontrado - {e}")
        sys.exit(1)
    
    # Parse HTML
    print("Parsing HTML...")
    vue_nodes = parse_html(vue_html)
    blade_nodes = parse_html(blade_html)
    
    print(f"Vue: {len(vue_nodes)} nós")
    print(f"Blade: {len(blade_nodes)} nós\n")
    
    # Comparações
    print("Comparando estruturas...")
    structure_diffs = compare_structure(vue_nodes, blade_nodes)
    
    print("Comparando classes...")
    class_diffs = compare_classes(vue_nodes, blade_nodes)
    
    print("Comparando texto...")
    text_diffs = compare_text(vue_nodes, blade_nodes)
    
    print("Comparando atributos...")
    attr_diffs = compare_attributes(vue_nodes, blade_nodes)
    
    # Agrupa todos os diffs
    all_diffs = structure_diffs + class_diffs + text_diffs + attr_diffs
    
    # Ordena por severidade (mais críticos primeiro)
    all_diffs.sort(key=lambda x: x.severity, reverse=True)
    
    # Relatório
    print("\n" + "="*80)
    print("RELATÓRIO DE DIFERENÇAS")
    print("="*80)
    
    print(f"\nTotal de diferenças encontradas: {len(all_diffs)}")
    print(f"  - Estrutura: {len(structure_diffs)}")
    print(f"  - Classes: {len(class_diffs)}")
    print(f"  - Texto: {len(text_diffs)}")
    print(f"  - Atributos: {len(attr_diffs)}")
    
    # Top 10 mais críticos
    print("\n" + "="*80)
    print("TOP 10 DIFERENÇAS MAIS CRÍTICAS (visualmente)")
    print("="*80)
    
    for i, diff in enumerate(all_diffs[:10], 1):
        print(f"\n{i}. [{diff.type.upper()}] Severidade: {diff.severity}/10")
        print(f"   Path: {diff.path}")
        if diff.vue_value:
            print(f"   Vue:   {diff.vue_value[:150]}")
        if diff.blade_value:
            print(f"   Blade: {diff.blade_value[:150]}")
    
    # Agrupa por tipo
    print("\n" + "="*80)
    print("DIFERENÇAS POR TIPO")
    print("="*80)
    
    for diff_type in ['structure', 'classes', 'text', 'attributes']:
        type_diffs = [d for d in all_diffs if d.type == diff_type]
        if type_diffs:
            print(f"\n### {diff_type.upper()} ({len(type_diffs)} diferenças)")
            for diff in type_diffs[:20]:  # Primeiras 20 de cada tipo
                print(f"  - {diff.path}")
                if diff.vue_value:
                    print(f"    Vue: {diff.vue_value[:80]}")
                if diff.blade_value:
                    print(f"    Blade: {diff.blade_value[:80]}")
    
    # Salva relatório completo
    with open('tmp/diff_report.txt', 'w', encoding='utf-8') as f:
        f.write("RELATÓRIO COMPLETO DE DIFERENÇAS\n")
        f.write("="*80 + "\n\n")
        for diff in all_diffs:
            f.write(f"[{diff.type.upper()}] {diff.path} (Severidade: {diff.severity}/10)\n")
            if diff.vue_value:
                f.write(f"  Vue:   {diff.vue_value}\n")
            if diff.blade_value:
                f.write(f"  Blade: {diff.blade_value}\n")
            f.write("\n")
    
    print(f"\nRelatório completo salvo em: tmp/diff_report.txt")

if __name__ == '__main__':
    main()

