#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use File::Find;
use File::Basename;
use Encode qw(decode_utf8 is_utf8);

binmode(STDOUT, ':encoding(UTF-8)');

my $root = '/home/victor/davinti';
my $components_dir = "$root/Vitruvio/Documentação/Componentes";
my @sources = ("$root/Vitruvio/Paineis", "$root/Vitruvio/Processos");

my @component_files;
find(
   sub {
      return unless -f $_;
      return unless $_ =~ /\.ts$/i;
      return if $_ eq 'index.ts';
      push @component_files, $File::Find::name;
   },
   $components_dir
);

my @xml_files;
for my $src (@sources) {
   next unless -d $src;
   find(
      sub {
         return unless -f $_;
         return unless $_ =~ /\.xml$/i;
         push @xml_files, $File::Find::name;
      },
      $src
   );
}

for my $doc_file (@component_files) {
   my $component = basename($doc_file, '.ts');

   my %attrs;
   my %attr_values;
   my %events;
   my @examples;
   my $occ_total = 0;
   my $occ_paineis = 0;
   my $occ_processos = 0;

   my $tag_regex = qr{<\s*\Q$component\E\b([^<>]*?)(?:/\s*>|>(?:.*?)</\s*\Q$component\E\s*>)}si;

   for my $xml (@xml_files) {
      my $content = read_text($xml);
      next if $content eq '';

      while ($content =~ /$tag_regex/g) {
         my $inside = defined $1 ? $1 : '';
         $occ_total++;

         if ($xml =~ m{/Vitruvio/Paineis/}) {
            $occ_paineis++;
         } elsif ($xml =~ m{/Vitruvio/Processos/}) {
            $occ_processos++;
         }

         if (scalar(@examples) < 12) {
            my $line = line_from_offset($content, pos($content));
            push @examples, rel_path($xml, $root) . ':' . $line;
         }

         while ($inside =~ /([A-Za-z_][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g) {
            my $attr = $1;
            my $value = defined $2 ? $2 : $3;
            $attrs{$attr} = 1;
            $attr_values{$attr}{$value} = 1;
         }
      }

      while ($content =~ /<\s*\Q$component\E\b[\s\S]*?<event\b[^>]*\bname\s*=\s*(?:"([^"]+)"|'([^']+)')[^>]*>/gsi) {
         my $event = defined $1 ? $1 : $2;
         next unless defined $event;
         $events{$event} = 1;
      }
   }

   my @props_sorted = sort { lc($a) cmp lc($b) } keys %attrs;
   my @events_sorted = sort { lc($a) cmp lc($b) } keys %events;

   my $family = infer_family($component);
   my $purpose = infer_purpose($component, $family);

   my $ts = build_ts_doc(
      component => $component,
      family => $family,
      purpose => $purpose,
      total => $occ_total,
      paineis => $occ_paineis,
      processos => $occ_processos,
      attrs => \@props_sorted,
      attr_values => \%attr_values,
      events => \@events_sorted,
      examples => \@examples,
   );

   write_text($doc_file, $ts);
}

print "OK: " . scalar(@component_files) . " componentes documentados\n";

sub read_text {
   my ($file) = @_;
   open my $fh, '<:encoding(UTF-8)', $file or return '';
   local $/ = undef;
   my $txt = <$fh>;
   close $fh;
   return defined $txt ? $txt : '';
}

sub write_text {
   my ($file, $content) = @_;
   open my $fh, '>:encoding(UTF-8)', $file or die "Erro ao escrever $file: $!";
   print {$fh} $content;
   close $fh;
}

sub line_from_offset {
   my ($txt, $offset) = @_;
   $offset = 0 unless defined $offset;
   my $prefix = substr($txt, 0, $offset);
   my $count = ($prefix =~ tr/\n//);
   return $count + 1;
}

sub rel_path {
   my ($path, $base) = @_;
   $path = decode_utf8($path) unless is_utf8($path);
   $base = decode_utf8($base) unless is_utf8($base);
   $path =~ s/^\Q$base\E\/?//;
   return $path;
}

sub quote_str {
   my ($s) = @_;
   $s = '' unless defined $s;
   $s =~ s/\\/\\\\/g;
   $s =~ s/"/\\"/g;
   $s =~ s/\r/\\r/g;
   $s =~ s/\n/\\n/g;
   $s =~ s/\t/\\t/g;
   return '"' . $s . '"';
}

sub infer_family {
   my ($component) = @_;
   return 'BancoDeDados' if $component =~ /^(DB|Data)/;
   return 'Layout' if $component =~ /(Layout|Panel|Section|Window|Tab|Accordion|Grid|Cell|Container|Split|Card)$/;
   return 'EntradaDeDados' if $component =~ /(Field|Input|Combo|Select|Picker|Check|Radio|Upload|Editor|TextArea|Date|Time|Number)/;
   return 'Visualizacao' if $component =~ /(Label|Image|Chart|Table|List|Tree|Map|HTML|Widget|View|Viewer|Progress|Badge|Icon|Tooltip)/;
   return 'Acao' if $component =~ /(Button|Action|Menu|Dialog|Modal|Popup|Confirm|Toolbar)/;
   return 'Infraestrutura';
}

sub infer_purpose {
   my ($component, $family) = @_;
   return 'Componente orientado a dados com integracao de datasource SQL e bind de parametros.' if $family eq 'BancoDeDados';
   return 'Componente de organizacao visual da tela para estruturar conteudo e navegacao.' if $family eq 'Layout';
   return 'Componente de captura e edicao de dados em formularios e filtros.' if $family eq 'EntradaDeDados';
   return 'Componente para exibicao de informacoes, resultados e feedback visual.' if $family eq 'Visualizacao';
   return 'Componente de interacao para disparar acoes e fluxos de processo.' if $family eq 'Acao';
   return 'Componente de suporte para configuracao, integracao e comportamento da tela.';
}

sub build_ts_doc {
   my (%p) = @_;
   my @props = @{ $p{attrs} || [] };
   my %prop_values = %{ $p{attr_values} || {} };
   my @events = @{ $p{events} || [] };
   my @examples = @{ $p{examples} || [] };

   my $props_block = join(",\n", map { '         ' . quote_str($_) } @props);
   my $events_block = join(",\n", map { '         ' . quote_str($_) } @events);
   my $examples_block = join(",\n", map { '         ' . quote_str($_) } @examples);

   my @pv_lines;
   my @pv_type_lines;
   for my $prop (@props) {
      my @vals_raw = sort { lc($a) cmp lc($b) } keys %{ $prop_values{$prop} || {} };
      my @vals = grep { is_value_relevant($prop, $_) } @vals_raw;
      @vals = @vals_raw if !@vals && @vals_raw;

      my $value_type = infer_value_type($prop, \@vals);

      my $truncated = 0;
      if (scalar(@vals) > 15) {
         @vals = @vals[0..14];
         $truncated = 1;
      }
      push @vals, '__TRUNCADO__' if $truncated;
      my $vals_txt = '[' . join(', ', map { quote_str($_) } @vals) . ']';
      push @pv_lines, '         ' . quote_str($prop) . ': ' . $vals_txt;
      push @pv_type_lines, '         ' . quote_str($prop) . ': ' . quote_str($value_type);
   }

   my $property_values_block = join(",\n", @pv_lines);
   my $property_value_types_block = join(",\n", @pv_type_lines);

   my $txt = '';
   $txt .= "export type VitruvioComponentDoc = {\n";
   $txt .= "   component: string;\n";
   $txt .= "   summary: string;\n";
   $txt .= "   status: \"draft\" | \"review\" | \"ready\";\n";
   $txt .= "   context: {\n";
   $txt .= "      module: string;\n";
   $txt .= "      family: string;\n";
   $txt .= "      mappedFrom: string[];\n";
   $txt .= "      occurrences: {\n";
   $txt .= "         total: number;\n";
   $txt .= "         paineis: number;\n";
   $txt .= "         processos: number;\n";
   $txt .= "      };\n";
   $txt .= "   };\n";
   $txt .= "   sections: {\n";
   $txt .= "      purpose: string;\n";
   $txt .= "      basicUsage: string;\n";
   $txt .= "      properties: string[];\n";
   $txt .= "      propertyValues: { [property: string]: string[] };\n";
   $txt .= "      propertyValueTypes: { [property: string]: \"boolean\" | \"enum\" | \"numeric\" | \"text\" | \"mixed\" };\n";
   $txt .= "      events: string[];\n";
   $txt .= "      examples: string[];\n";
   $txt .= "      notes: string[];\n";
   $txt .= "   };\n";
   $txt .= "};\n\n";

   $txt .= "const " . $p{component} . "Doc: VitruvioComponentDoc = {\n";
   $txt .= "   component: " . quote_str($p{component}) . ",\n";
   $txt .= "   summary: " . quote_str("Documentacao do componente " . $p{component} . " mapeada a partir de exemplos reais de XML.") . ",\n";
   $txt .= "   status: \"draft\",\n";
   $txt .= "   context: {\n";
   $txt .= "      module: \"Vitruvio\",\n";
   $txt .= "      family: " . quote_str($p{family}) . ",\n";
   $txt .= "      mappedFrom: [\"Vitruvio/Paineis\", \"Vitruvio/Processos\"],\n";
   $txt .= "      occurrences: { total: " . ($p{total} || 0) . ", paineis: " . ($p{paineis} || 0) . ", processos: " . ($p{processos} || 0) . " }\n";
   $txt .= "   },\n";
   $txt .= "   sections: {\n";
   $txt .= "      purpose: " . quote_str($p{purpose}) . ",\n";
   $txt .= "      basicUsage: " . quote_str("Usar " . $p{component} . " no contexto adequado da tela; as propriedades abaixo foram observadas em exemplos reais do projeto.") . ",\n";
   $txt .= "      properties: [\n$props_block\n      ],\n";
   $txt .= "      propertyValues: {\n$property_values_block\n      },\n";
   $txt .= "      propertyValueTypes: {\n$property_value_types_block\n      },\n";
   $txt .= "      events: [\n$events_block\n      ],\n";
   $txt .= "      examples: [\n$examples_block\n      ],\n";
   $txt .= "      notes: [\n";
   $txt .= "         \"Mapeamento automatico baseado em uso observado nos XMLs de paineis e processos.\",\n";
   $txt .= "         \"Valores ruidosos/textos muito longos sao filtrados para facilitar consulta.\",\n";
   $txt .= "         \"propertyValueTypes classifica o perfil observado da propriedade (boolean, enum, numeric, text ou mixed).\",\n";
   $txt .= "         \"Validar com documentacao oficial do Vitruvio quando houver divergencia funcional.\"\n";
   $txt .= "      ]\n";
   $txt .= "   }\n";
   $txt .= "};\n\n";
   $txt .= "export default " . $p{component} . "Doc;\n";

   return $txt;
}

sub is_value_relevant {
   my ($prop, $value) = @_;
   return 0 unless defined $value;

   my $raw = $value;
   my $trim = $value;
   $trim =~ s/^\s+//;
   $trim =~ s/\s+$//;
   return 1 if $trim eq '';

   # Remove trechos tecnicos comuns de ruido em mapeamento automatico.
   return 0 if $raw =~ /<[^>]+>/;
   return 0 if $raw =~ /\{\{|\}\}|\$\{|#\{/;
   return 0 if $raw =~ /\bSELECT\b|\bUPDATE\b|\bINSERT\b|\bDELETE\b/i;

   my $is_text_prop = $prop =~ /caption|description|label|title|subCaption|placeholder|message|tooltip|hint/i;
   my $len = length($trim);
   my $word_count = scalar(grep { $_ ne '' } split(/\s+/, $trim));

   # Para campos textuais, ignora frases grandes que viram ruido na documentação.
   if ($is_text_prop) {
      return 0 if $len > 70;
      return 0 if $word_count > 10;
   }

   # Filtro leve global para blocos muito longos.
   return 0 if $len > 120;

   return 1;
}

sub infer_value_type {
   my ($prop, $vals_ref) = @_;
   my @vals = @{ $vals_ref || [] };
   return 'mixed' if !@vals;

   my @clean = map {
      my $x = $_;
      $x =~ s/^\s+//;
      $x =~ s/\s+$//;
      $x;
   } @vals;

   my @non_empty = grep { $_ ne '' } @clean;
   return 'text' if !@non_empty;

   my $all_boolean = 1;
   my $all_numeric = 1;
   my $has_space = 0;

   for my $v (@non_empty) {
      my $lc = lc($v);
      $all_boolean = 0 unless $lc eq 'true' || $lc eq 'false';
      $all_numeric = 0 unless $v =~ /^-?\d+(?:\.\d+)?(?:%|px)?$/;
      $has_space = 1 if $v =~ /\s/;
   }

   return 'boolean' if $all_boolean;
   return 'numeric' if $all_numeric;

   my $is_text_prop = $prop =~ /caption|description|label|title|subCaption|placeholder|message|tooltip|hint|text/i;
   return 'text' if $is_text_prop;

   my $unique = scalar(@non_empty);
   return 'enum' if $unique <= 20 && !$has_space;
   return 'mixed' if $unique <= 30;
   return 'text';
}
