-- ============================================================================
-- Migration 001 — schéma initial de la veille législative
--
-- Sémantique conforme au §4 du prompt de mission. Les libellés d'énumération
-- sont ceux du corpus de recherche : ils ne sont pas traduits ni normalisés,
-- de façon à ce qu'une valeur en base soit toujours retrouvable dans un
-- fichier de `data/input/`.
-- ============================================================================

PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;

-- ─────────────────────────────────────────────────────────────────────────────
-- Table principale : un texte législatif ou une décision juridictionnelle
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE textes (
    -- Identifiant lisible et stable, servant aussi d'URL : « loi-2026-491 ».
    id                      TEXT PRIMARY KEY,

    -- Numéro officiel une fois attribué (« 2026-491 »). NULL tant que le texte
    -- n'est pas promulgué : c'est précisément ce qui distingue un texte adopté
    -- d'un texte en vigueur.
    numero_officiel         TEXT,

    type                    TEXT NOT NULL CHECK (type IN (
                                'loi', 'loi_organique', 'pjl', 'ppl', 'ordonnance',
                                'decision_cc', 'decret', 'accord_international')),

    titre_court             TEXT NOT NULL,
    titre_officiel          TEXT,

    -- Règle d'or du rapport : le statut prime sur l'intitulé.
    statut                  TEXT NOT NULL CHECK (statut IN (
                                'promulguee', 'adoptee_non_promulguee', 'saisie_cc',
                                'validee_cc', 'censuree_partiellement', 'navette',
                                'deposee_non_examinee', 'annoncee', 'rejetee')),
    statut_date             TEXT,   -- date du dernier changement de statut (ISO 8601)

    date_depot              TEXT,
    date_adoption           TEXT,
    date_promulgation       TEXT,
    date_entree_vigueur     TEXT,
    prochaine_echeance      TEXT,
    prochaine_echeance_label TEXT,

    -- Tableaux JSON. SQLite les stocke en texte ; les fonctions json_*
    -- permettent de filtrer sans table de jointure supplémentaire.
    themes                  TEXT NOT NULL DEFAULT '[]',

    -- Objet JSON : { "individus": {"sens": "negatif", "note": "…"}, … }
    -- Sens admis : positif | negatif | mixte | neutre.
    impacts                 TEXT NOT NULL DEFAULT '{}',

    guadeloupe_pertinence   TEXT CHECK (guadeloupe_pertinence IN ('forte', 'moyenne', 'faible')),
    guadeloupe_note         TEXT,

    resume                  TEXT,
    points_cles             TEXT NOT NULL DEFAULT '[]',

    confiance               TEXT NOT NULL DEFAULT 'medium'
                                CHECK (confiance IN ('high', 'medium', 'low')),

    -- Fichier de `data/input/` dont provient l'entrée, ou nom du collecteur.
    source_seed             TEXT,

    -- Drapeau de revue humaine : toute classification automatique le lève.
    a_verifier              INTEGER NOT NULL DEFAULT 0 CHECK (a_verifier IN (0, 1)),
    motif_verification      TEXT,

    derniere_verif          TEXT,
    cree_le                 TEXT NOT NULL DEFAULT (datetime('now')),
    maj_le                  TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE UNIQUE INDEX idx_textes_numero ON textes (numero_officiel)
    WHERE numero_officiel IS NOT NULL;
CREATE INDEX idx_textes_statut       ON textes (statut);
CREATE INDEX idx_textes_type         ON textes (type);
CREATE INDEX idx_textes_gpe          ON textes (guadeloupe_pertinence);
CREATE INDEX idx_textes_promulgation ON textes (date_promulgation);
CREATE INDEX idx_textes_echeance     ON textes (prochaine_echeance);

-- ─────────────────────────────────────────────────────────────────────────────
-- Sources : une citation vérifiable par fait important
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE sources (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    texte_id            TEXT NOT NULL REFERENCES textes (id) ON DELETE CASCADE,

    url                 TEXT NOT NULL,
    titre               TEXT,
    editeur             TEXT,
    date_publication    TEXT,

    -- T1 = source primaire (Légifrance/JO, AN, Sénat, Conseil constitutionnel,
    -- Conseil d'État, vie-publique, préfectures) ; T2 = presse et analyses.
    tier                TEXT NOT NULL DEFAULT 'T2' CHECK (tier IN ('T1', 'T2')),

    extrait_verbatim    TEXT,
    contexte            TEXT,
    confiance           TEXT CHECK (confiance IN ('high', 'medium', 'low')),

    -- Marqueur d'origine dans le corpus : « dim07-24 », « phase5-3.1 », « ref-481 ».
    marqueur            TEXT,
    fichier_origine     TEXT,

    cree_le             TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_sources_texte ON sources (texte_id);
CREATE UNIQUE INDEX idx_sources_unicite ON sources (texte_id, url, COALESCE(marqueur, ''));

-- ─────────────────────────────────────────────────────────────────────────────
-- Événements : la timeline parlementaire d'un texte
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE evenements (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    texte_id        TEXT NOT NULL REFERENCES textes (id) ON DELETE CASCADE,

    date_evenement  TEXT NOT NULL,
    type_etape      TEXT NOT NULL CHECK (type_etape IN (
                        'depot', 'adoption_1re_lecture', 'adoption_definitive',
                        'commission_mixte_paritaire', 'transmission', 'saisine_cc',
                        'decision_cc', 'promulgation', 'publication_jo',
                        'entree_vigueur', 'rejet', 'annonce', 'autre')),
    description     TEXT NOT NULL,
    source_url      TEXT,

    -- Vrai lorsque l'événement est postérieur à la date d'arrêté des données :
    -- il s'agit alors d'une échéance prévisionnelle, pas d'un fait acquis.
    previsionnel    INTEGER NOT NULL DEFAULT 0 CHECK (previsionnel IN (0, 1)),

    cree_le         TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_evenements_texte ON evenements (texte_id, date_evenement);
CREATE INDEX idx_evenements_date  ON evenements (date_evenement);
CREATE UNIQUE INDEX idx_evenements_unicite
    ON evenements (texte_id, date_evenement, type_etape, description);

-- ─────────────────────────────────────────────────────────────────────────────
-- Décisions du Conseil constitutionnel
--
-- `date_decision IS NULL` signifie « affaire en instance » : c'est ce qui
-- alimente la facette « saisi du Conseil constitutionnel » de l'interface.
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE decisions_cc (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    texte_id        TEXT REFERENCES textes (id) ON DELETE SET NULL,

    numero_affaire  TEXT NOT NULL UNIQUE,   -- « 2026-915 DC »
    date_saisine    TEXT,
    date_decision   TEXT,
    resultat        TEXT CHECK (resultat IN (
                        'conforme', 'conforme_avec_reserves', 'non_conformite_partielle',
                        'non_conformite_totale', 'en_instance')),
    saisissants     TEXT,
    resume          TEXT,
    url             TEXT,

    date_decision_attendue TEXT,   -- prévisionnel, tant que la décision n'est pas rendue
    cree_le         TEXT NOT NULL DEFAULT (datetime('now')),
    maj_le          TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_cc_texte    ON decisions_cc (texte_id);
CREATE INDEX idx_cc_instance ON decisions_cc (date_decision);

-- ─────────────────────────────────────────────────────────────────────────────
-- Analyses transversales issues de `lois-2026_insight.md`
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE insights (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    numero          INTEGER NOT NULL UNIQUE,
    titre           TEXT NOT NULL,
    corps           TEXT NOT NULL,
    implications    TEXT,
    confiance       TEXT NOT NULL DEFAULT 'medium',
    derive_de       TEXT NOT NULL DEFAULT '[]',   -- JSON : ["dim02", "dim03", …]
    fichier_origine TEXT
);

-- ─────────────────────────────────────────────────────────────────────────────
-- Journal des exécutions du pipeline
-- ─────────────────────────────────────────────────────────────────────────────
CREATE TABLE veille_log (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    horodatage      TEXT NOT NULL DEFAULT (datetime('now')),
    mode            TEXT NOT NULL CHECK (mode IN ('seed', 'update', 'dry-run')),
    duree_s         REAL,
    ajouts          INTEGER NOT NULL DEFAULT 0,
    modifications   INTEGER NOT NULL DEFAULT 0,
    echecs          INTEGER NOT NULL DEFAULT 0,
    alertes         TEXT NOT NULL DEFAULT '[]',   -- JSON
    rapport         TEXT                          -- compte-rendu lisible
);

CREATE INDEX idx_veille_log_date ON veille_log (horodatage DESC);

-- Détail des changements d'un run, pour l'encart « derniers changements ».
CREATE TABLE veille_changements (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    run_id          INTEGER NOT NULL REFERENCES veille_log (id) ON DELETE CASCADE,
    texte_id        TEXT,
    nature          TEXT NOT NULL CHECK (nature IN ('ajout', 'statut', 'date', 'source', 'autre')),
    champ           TEXT,
    ancienne_valeur TEXT,
    nouvelle_valeur TEXT,
    description     TEXT
);

CREATE INDEX idx_changements_run ON veille_changements (run_id);

-- ─────────────────────────────────────────────────────────────────────────────
-- Recherche plein texte
--
-- Table FTS5 à contenu externe : l'index ne duplique pas les données, il est
-- tenu à jour par les déclencheurs ci-dessous. `remove_diacritics 2` permet de
-- trouver « chlordécone » en tapant « chlordecone ».
-- ─────────────────────────────────────────────────────────────────────────────
CREATE VIRTUAL TABLE textes_fts USING fts5 (
    titre_court,
    titre_officiel,
    resume,
    points_cles,
    guadeloupe_note,
    numero_officiel,
    content     = 'textes',
    content_rowid = 'rowid',
    tokenize    = "unicode61 remove_diacritics 2"
);

CREATE TRIGGER textes_fts_ai AFTER INSERT ON textes BEGIN
    INSERT INTO textes_fts (rowid, titre_court, titre_officiel, resume, points_cles,
                            guadeloupe_note, numero_officiel)
    VALUES (new.rowid, new.titre_court, new.titre_officiel, new.resume, new.points_cles,
            new.guadeloupe_note, new.numero_officiel);
END;

CREATE TRIGGER textes_fts_ad AFTER DELETE ON textes BEGIN
    INSERT INTO textes_fts (textes_fts, rowid, titre_court, titre_officiel, resume,
                            points_cles, guadeloupe_note, numero_officiel)
    VALUES ('delete', old.rowid, old.titre_court, old.titre_officiel, old.resume,
            old.points_cles, old.guadeloupe_note, old.numero_officiel);
END;

CREATE TRIGGER textes_fts_au AFTER UPDATE ON textes BEGIN
    INSERT INTO textes_fts (textes_fts, rowid, titre_court, titre_officiel, resume,
                            points_cles, guadeloupe_note, numero_officiel)
    VALUES ('delete', old.rowid, old.titre_court, old.titre_officiel, old.resume,
            old.points_cles, old.guadeloupe_note, old.numero_officiel);
    INSERT INTO textes_fts (rowid, titre_court, titre_officiel, resume, points_cles,
                            guadeloupe_note, numero_officiel)
    VALUES (new.rowid, new.titre_court, new.titre_officiel, new.resume, new.points_cles,
            new.guadeloupe_note, new.numero_officiel);
END;

-- ─────────────────────────────────────────────────────────────────────────────
-- Vue de consultation : ajoute les informations dérivées dont l'interface a
-- besoin sans dupliquer d'état en base.
-- ─────────────────────────────────────────────────────────────────────────────
CREATE VIEW v_textes AS
SELECT
    t.*,
    (SELECT COUNT(*) FROM sources s WHERE s.texte_id = t.id)      AS nb_sources,
    (SELECT COUNT(*) FROM evenements e WHERE e.texte_id = t.id)   AS nb_evenements,
    -- Un texte est « devant le Conseil constitutionnel » soit par son statut,
    -- soit parce qu'une affaire le concernant est en instance. Les deux voies
    -- sont nécessaires : le §7 du prompt impose que la loi « Riposte » garde le
    -- statut `adoptee_non_promulguee`, alors qu'elle doit apparaître dans la
    -- facette « saisi du Conseil constitutionnel ».
    CASE WHEN t.statut = 'saisie_cc'
              OR EXISTS (SELECT 1 FROM decisions_cc d
                         WHERE d.texte_id = t.id AND d.date_decision IS NULL)
         THEN 1 ELSE 0 END                                        AS devant_cc
FROM textes t;
