#!/usr/bin/perl
# =============================================================================
# Sème la grille de mission en base, depuis ecovivo-grille.js.
#
#   perl outils/semer-grille.pl        depuis site/
#
# POURQUOI CET OUTIL EXISTE
# -------------------------
# La grille de mission (12 elements de mission de base, 12 missions
# complementaires, 11 postes de conseil photovoltaique, avec leurs livrables et
# leurs delais) est la donnee la plus precieuse de l outil : elle vient du
# generateur de PTF en production, et le handoff est formel, elle se reprend
# VERBATIM.
#
# La recopier a la main en PHP ou en SQL creerait une seconde version. Deux
# versions divergent : une correction de libellé faite d un cote resterait
# invisible de l autre, et la PTF envoyee au client ne dirait plus ce que la
# grille dit.
#
# Cet outil lit donc public/assets/js/ecovivo-grille.js, la seule copie, et
# produit migrations/004-grille-mission.sql. Le fichier .js reste intouche.
#
# A RELANCER si la grille evolue, puis rejouer la migration produite.
#
# CONTROLE : le total des parts de la mission de base doit faire 100 %.
# L outil le verifie et refuse d ecrire si le compte est faux.
# =============================================================================
use strict;
use warnings;
binmode(STDOUT, ':encoding(UTF-8)');

my $source = 'public/assets/js/ecovivo-grille.js';
my $cible  = 'migrations/004-grille-mission.sql';

open(my $h, '<:encoding(UTF-8)', $source) or die "Fichier introuvable : $source\n";
my $js = do { local $/; <$h> };
close $h;

# --- Les sequences d echappement JavaScript ---------------------------------
sub decoder {
    my ($t) = @_;
    $t =~ s/\\u([0-9a-fA-F]{4})/chr(hex($1))/ge;
    $t =~ s/\\"/"/g;
    $t =~ s/\\'/'/g;
    $t =~ s/\\\\/\\/g;
    return $t;
}

# Une chaine SQL : l apostrophe se double.
sub sql {
    my ($t) = @_;
    return 'NULL' unless defined $t;
    $t =~ s/'/''/g;
    return "'" . $t . "'";
}

# --- Les trois blocs, avec leur ordre ---------------------------------------
# Chaque bloc court de « <nom>:{ » jusqu au « ]}, » qui ferme sa liste d items.
my @blocs;
for my $nom (qw(base compl pv)) {
    my ($corps) = $js =~ /^\s*\Q$nom\E:\s*\{\s*\n\s*label:(.*?)\n\s*\]\}/ms;
    die "Bloc introuvable dans la grille : $nom\n" unless defined $corps;
    push @blocs, { nom => $nom, corps => $corps };
}

# --- Les elements de chaque bloc --------------------------------------------
my @elements;
for my $bloc (@blocs) {
    my $ordre = 0;
    my $corps = $bloc->{corps};
    # {k:"OAD",n:"...",t:"AMO",p:3,\n d:"..."}
    while ($corps =~ /\{k:"((?:[^"\\]|\\.)*)",\s*n:"((?:[^"\\]|\\.)*)",\s*t:"(\w+)",\s*p:(\d+),\s*d:"((?:[^"\\]|\\.)*)"\s*\}/gs) {
        $ordre++;
        push @elements, {
            bloc  => $bloc->{nom},
            code  => decoder($1),
            nom   => decoder($2),
            type  => $3,
            part  => $4 + 0,
            desc  => decoder($5),
            ordre => $ordre,
        };
    }
}

die "Aucun element lu dans la grille : le format a change.\n" unless @elements;

# --- Les livrables ----------------------------------------------------------
my ($bloc_livrables) = $js =~ /const LIVRABLES=\{(.*?)\n\};/s;
die "Bloc LIVRABLES introuvable.\n" unless defined $bloc_livrables;

my %livrables;
# CODE:["...","..."] ou "PRÉ":["...","..."]
while ($bloc_livrables =~ /(?:"((?:[^"\\]|\\.)*)"|([A-Za-z0-9_]+))\s*:\s*\[(.*?)\]/gs) {
    my $code = defined $1 ? decoder($1) : $2;
    my $liste = $3;
    my @items;
    while ($liste =~ /"((?:[^"\\]|\\.)*)"/gs) {
        push @items, decoder($1);
    }
    $livrables{$code} = \@items;
}

# --- Les delais -------------------------------------------------------------
my ($bloc_delais) = $js =~ /const DELAIS=\{(.*?)\n\};/s;
die "Bloc DELAIS introuvable.\n" unless defined $bloc_delais;

my %delais;
while ($bloc_delais =~ /(?:"((?:[^"\\]|\\.)*)"|([A-Za-z0-9_]+))\s*:\s*(null|\d+)/gs) {
    my $code = defined $1 ? decoder($1) : $2;
    my $val  = $3;
    $delais{$code} = ($val eq 'null') ? undef : $val + 0;
}

# --- Controles --------------------------------------------------------------
my $total_base = 0;
my %par_bloc;
for my $e (@elements) {
    $par_bloc{ $e->{bloc} }++;
    $total_base += $e->{part} if $e->{bloc} eq 'base';
}

printf("Éléments lus : base %d, compl %d, pv %d.\n",
    $par_bloc{base} // 0, $par_bloc{compl} // 0, $par_bloc{pv} // 0);
printf("Total de la mission de base : %d %%.\n", $total_base);

if ($total_base != 100) {
    die "ARRÊT : la mission de base totalise $total_base %, elle doit faire 100 %.\n";
}
if (($par_bloc{base} // 0) != 12) {
    die "ARRÊT : la mission de base compte $par_bloc{base} éléments, elle en attend 12.\n";
}

my $sans_livrable = 0;
for my $e (@elements) {
    $sans_livrable++ unless exists $livrables{ $e->{code} };
}
print "Éléments sans livrable déclaré : $sans_livrable.\n" if $sans_livrable;

# --- Ecriture de la migration ------------------------------------------------
open(my $o, '>:encoding(UTF-8)', $cible) or die "Écriture impossible : $cible\n";

print $o <<'TETE';
-- =============================================================================
-- Migration 004 : la grille de mission de référence
--
-- FICHIER PRODUIT PAR outils/semer-grille.pl, à partir de
-- public/assets/js/ecovivo-grille.js. La grille reste écrite à UN SEUL ENDROIT :
-- toute correction se fait dans le .js, puis on relance l outil et on rejoue
-- cette migration.
--
-- Contenu : 12 éléments de mission de base (total 100 %, articles R2431-1 et
-- suivants du Code de la commande publique), 12 missions complémentaires au
-- forfait, 11 postes de conseil photovoltaïque, avec leurs livrables et leurs
-- délais de production.
--
-- UN DÉLAI NULL se lit « conduit au fil de l opération, sans délai de production
-- isolable » : VISA, DET, AOR, DOE et OPC. Le module Ressources humaines les
-- étale en continu jusqu à la fin de l affaire. Les traiter comme un délai court
-- concentrerait 34 % de la mission (DET) sur un seul mois.
--
-- Rejouable : la grille est vidée puis réécrite. Les PTF déjà établies gardent
-- leurs propres lignes (ptf_element), elles ne sont pas touchées.
-- =============================================================================

SET NAMES utf8mb4;

DELETE FROM ptf_referentiel_livrable;
DELETE FROM ptf_referentiel;

TETE

# Les elements
print $o "INSERT INTO ptf_referentiel (bloc, code, nom, type, part, description, delai_semaines, ordre) VALUES\n";
my @lignes;
for my $e (@elements) {
    my $delai = exists $delais{ $e->{code} } && defined $delais{ $e->{code} }
        ? $delais{ $e->{code} } : 'NULL';
    push @lignes, sprintf("    (%s, %s, %s, %s, %s, %s, %s, %d)",
        sql($e->{bloc}), sql($e->{code}), sql($e->{nom}), sql($e->{type}),
        $e->{part}, sql($e->{desc}), $delai, $e->{ordre});
}
print $o join(",\n", @lignes), ";\n\n";

# Les livrables, rattaches par le couple bloc + code
print $o "-- Les livrables : ce que le client lit sous chaque phase de la PTF éditée.\n";
for my $e (@elements) {
    my $liste = $livrables{ $e->{code} } or next;
    next unless @$liste;
    my $ordre = 0;
    my @l;
    for my $item (@$liste) {
        $ordre++;
        push @l, sprintf("    ((SELECT id FROM (SELECT id FROM ptf_referentiel WHERE bloc = %s AND code = %s) AS r), %s, %d)",
            sql($e->{bloc}), sql($e->{code}), sql($item), $ordre);
    }
    print $o "INSERT INTO ptf_referentiel_livrable (referentiel_id, libelle, ordre) VALUES\n";
    print $o join(",\n", @l), ";\n";
}

print $o "\nINSERT IGNORE INTO migration (numero, libelle) VALUES\n"
       . "    ('004', 'Grille de mission de référence, semée depuis ecovivo-grille.js');\n";

close $o;

printf("\n%s écrit.\n", $cible);
print "Rejouer cette migration après 000-schema-complet.sql.\n";
exit 0;
