<?php

declare(strict_types=1);

trait AnimalRecordActions
{
    use AnimalRelationshipActions;
    public static function pdf(): void
    {
        $id = (int) ($_GET['id'] ?? 0);
        if ($id <= 0) {
            http_response_code(400);
            echo h(t('error.bad_request'));
            return;
        }

        $db = DB::pdo();

        // Animal
        $stmt = $db->prepare('SELECT * FROM animals WHERE id=:id');
        $stmt->execute([':id' => $id]);
        $animal = $stmt->fetch();
        if (!$animal) {
            http_response_code(404);
            echo h(t('error.not_found'));
            return;
        }

        $stmt = $db->prepare(
            'SELECT ad.*,vet.name veterinarian_name,crem.name crematorium_name FROM animal_deaths ad LEFT JOIN directory_contacts vet ON vet.id=ad.veterinarian_contact_id LEFT JOIN directory_contacts crem ON crem.id=ad.crematorium_contact_id WHERE ad.animal_id=:id',
        );
        $stmt->execute([':id' => $id]);
        $death = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;

        // Notes médicales + clinique
        $stmt = $db->prepare('
        SELECT mn.*, COALESCE(dc.name, c.name) AS clinic_name
        FROM medical_notes mn
        LEFT JOIN clinics c ON c.id = mn.clinic_id
        LEFT JOIN directory_contacts dc ON dc.id = mn.clinic_contact_id
        WHERE mn.animal_id=:id
        ORDER BY mn.noted_at DESC, mn.id DESC
        ');
        $stmt->execute([':id' => $id]);
        $medical_notes = $stmt->fetchAll();

        // Photos médicales indexées par note
        $stmt = $db->prepare('
        SELECT id, medical_note_id, filename
        FROM medical_photos
        WHERE medical_note_id IN (
            SELECT id FROM medical_notes WHERE animal_id = :id
        )
        ORDER BY medical_note_id ASC, id ASC
        ');
        $stmt->execute([':id' => $id]);
        $allMedicalPhotos = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Regroupe par note
        $medicalPhotosByNote = [];
        foreach ($allMedicalPhotos as $p) {
            $medicalPhotosByNote[$p['medical_note_id']][] = $p;
        }

        // Traitements (tous, y compris terminés)
        $stmt = $db->prepare('
        SELECT t.*, m.name AS medication_name, m.molecule, m.form, COALESCE(dc.name, c.name) AS clinic_name
        FROM treatments t
        JOIN ref_medications m ON m.id = t.medication_id
        LEFT JOIN clinics c ON c.id = t.clinic_id
        LEFT JOIN directory_contacts dc ON dc.id = t.clinic_contact_id
        WHERE t.animal_id=:id
        ORDER BY t.ongoing DESC, t.start_date DESC, t.id DESC
        ');
        $stmt->execute([':id' => $id]);
        $treatments = $stmt->fetchAll();

        // Vaccins
        $stmt = $db->prepare('
        SELECT v.*, rv.name AS vaccine_name, COALESCE(dc.name, c.name) AS clinic_name
        FROM vaccinations v
        JOIN ref_vaccines rv ON rv.id = v.vaccine_id
        LEFT JOIN clinics c ON c.id = v.clinic_id
        LEFT JOIN directory_contacts dc ON dc.id = v.clinic_contact_id
        WHERE v.animal_id=:id
        ORDER BY v.done_date DESC, v.id DESC
        ');
        $stmt->execute([':id' => $id]);
        $vaccinations = $stmt->fetchAll();

        // Photos
        $stmt = $db->prepare('SELECT * FROM animal_photos WHERE animal_id=:id ORDER BY is_primary DESC, id DESC');
        $stmt->execute([':id' => $id]);
        $photos = $stmt->fetchAll();

        // Génère le HTML PDF via une vue dédiée
        //$viewFile = __DIR__ . '/../Views/animal_pdf.php';
        $viewFile = __DIR__ . '/../Views/animal_pdf_v2.php';
        if (!file_exists($viewFile)) {
            http_response_code(500);
            echo h(t('error.missing_pdf_view'));
            return;
        }

        // variables pour la vue
        $title = 'Dossier - ' . ($animal['name'] ?? 'Animal #' . $id);

        // Photo principale
        $stmt = $db->prepare('
        SELECT filename
        FROM animal_photos
        WHERE animal_id=:id AND is_primary=1
        ORDER BY id DESC
        LIMIT 1
        ');
        $stmt->execute([':id' => $id]);
        $primary_photo = $stmt->fetchColumn() ?: null;

        // Dernière note médicale (résumé)
        $stmt = $db->prepare('
        SELECT noted_at, kind, reason, weight_kg, temperature_c, diagnosis, plan
        FROM medical_notes
        WHERE animal_id=:id
        ORDER BY datetime(noted_at) DESC, id DESC
        LIMIT 1
        ');
        $stmt->execute([':id' => $id]);
        $last_med = $stmt->fetch() ?: null;

        // Prochain vaccin (due_date la plus proche)
        $stmt = $db->prepare('
        SELECT rv.name AS vaccine_name, v.due_date
        FROM vaccinations v
        JOIN ref_vaccines rv ON rv.id = v.vaccine_id
        WHERE v.animal_id=:id AND v.due_date IS NOT NULL
        ORDER BY date(v.due_date) ASC, v.id ASC
        LIMIT 1
        ');
        $stmt->execute([':id' => $id]);
        $next_vaccine = $stmt->fetch() ?: null;

        $generatedAt = new DateTimeImmutable('now', new DateTimeZone('Europe/Paris'));
        $footerText =
            'Généré par ' .
            AppSettings::get('association_name') .
            ', le ' .
            $generatedAt->format('d/m/Y') .
            ' à ' .
            $generatedAt->format('H\\hi');
        $internalCode = trim((string) ($animal['internal_code'] ?? ''));
        if ($internalCode !== '') {
            $footerText .= ' · Dossier ' . $internalCode;
        }
        header('Content-Type: text/html; charset=UTF-8');
        require $viewFile;
    }

    public static function form(): void
    {
        $db = DB::pdo();
        $id = (int) ($_GET['id'] ?? 0);

        $animal = [
            'id' => 0,
            'name' => '',
            'internal_code' => '',
            'status' => 'refuge',
            'species' => 'chat',
            'sex' => 'U',
            'sterilized' => 0,
            'sterilization_status' => 'unknown',
            'sterilization_date' => '',
            'sterilization_vet_contact_id' => '',
            'sterilization_clinic_contact_id' => '',
            'refuge_room' => '',
            'chip_id' => '',
            'identification_type' => 'unknown',
            'identification_date' => '',
            'identification_registration_status' => 'unknown',
            'adoption_availability' => 'unknown',
            'adoption_available_from' => '',
            'adoption_unavailability_reason' => '',
            'website_published' => 0,
            'website_description' => '',
            'color' => '',
            'hair_type' => '',
            'birth_date' => '',
            'notes' => '',
            'intake_date' => '',
            'intake_type' => 'unknown',
            'intake_reason' => 'unknown',
            'depositor_contact_id' => '',
            'intake_circumstances' => '',
            'intake_owner_care_home' => 0,
            'quarantine_until' => '',
            'compatibility_dogs' => 'unknown',
            'compatibility_cats' => 'unknown',
            'compatibility_children' => 'unknown',
            'house_trained' => 'unknown',
            'free_cat_site_name' => '',
            'free_cat_city' => '',
            'free_cat_address' => '',
            'free_cat_caretaker_contact_id' => '',
            'free_cat_captured_at' => '',
            'free_cat_released_at' => '',
            'free_cat_tracking_status' => 'present',
            // adapte si tu as fiv/felv en colonnes
            'fiv' => '',
            'felv' => '',
        ];

        if ($id > 0) {
            $stmt = $db->prepare('SELECT * FROM animals WHERE id=:id');
            $stmt->execute([':id' => $id]);
            $row = $stmt->fetch();
            if (!$row) {
                http_response_code(404);
                echo h(t('error.not_found'));
                return;
            }
            $animal = array_merge($animal, $row);
            if (!empty($animal['intake_owner_care_home'])) {
                $animal['intake_reason'] = 'owner_care_home';
            }
        }

        $fosterContacts = $db
            ->query(
                "SELECT DISTINCT dc.id,dc.name,dc.address,dc.postal_code,dc.city FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id AND r.role='fa' WHERE dc.deleted_at IS NULL ORDER BY dc.name COLLATE NOCASE",
            )
            ->fetchAll(PDO::FETCH_ASSOC);
        $currentFosterId = 0;
        if ($id > 0) {
            $stmt = $db->prepare(
                "SELECT contact_id FROM animal_placements WHERE animal_id=:id AND event_type='foster' ORDER BY date(event_date) DESC,id DESC LIMIT 1",
            );
            $stmt->execute([':id' => $id]);
            $currentFosterId = (int) $stmt->fetchColumn();
        }

        $depositors = $db
            ->query(
                "SELECT dc.id,dc.name,dc.phone,dc.city FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id AND r.role='deposant' WHERE dc.deleted_at IS NULL ORDER BY dc.name COLLATE NOCASE",
            )
            ->fetchAll(PDO::FETCH_ASSOC);
        $sterilizationVets = $db
            ->query(
                "SELECT dc.id,dc.name FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id AND r.role='veterinaire' WHERE dc.deleted_at IS NULL ORDER BY dc.name COLLATE NOCASE",
            )
            ->fetchAll(PDO::FETCH_ASSOC);
        $sterilizationClinics = $db
            ->query(
                "SELECT dc.id,dc.name FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id AND r.role='cabinet' WHERE dc.deleted_at IS NULL ORDER BY dc.name COLLATE NOCASE",
            )
            ->fetchAll(PDO::FETCH_ASSOC);
        $freeCatCaretakers = $db
            ->query(
                "SELECT id,name,city FROM directory_contacts WHERE deleted_at IS NULL AND kind='person' ORDER BY name COLLATE NOCASE",
            )
            ->fetchAll(PDO::FETCH_ASSOC);
        render('animal_form.php', [
            'title' => $id > 0 ? 'Modifier ' . $animal['name'] : 'Ajouter un animal',
            'pageDescription' =>
                $id > 0
                    ? 'Modification de la fiche de ' . $animal['name'] . '.'
                    : 'Création d’une nouvelle fiche animal dans Globinours.',
            'animal' => $animal,
            'intakeTypes' => self::intakeTypes(),
            'intakeReasons' => self::intakeReasons(),
            'depositors' => $depositors,
            'sterilizationVets' => $sterilizationVets,
            'sterilizationClinics' => $sterilizationClinics,
            'fosterContacts' => $fosterContacts,
            'currentFosterId' => $currentFosterId,
            'freeCatCaretakers' => $freeCatCaretakers,
            'speciesOptions' => SpeciesService::activeWithCurrent((string) ($animal['species'] ?? 'chat')),
            'shelterRooms' => ShelterRoomService::all(true),
        ]);
    }

    private static function nextInternalCode(string $name): string
    {
        $year = date('Y');

        $base = strtoupper(trim($name));
        $base = preg_replace('/[^A-Z0-9]+/u', '_', $base);
        $base = trim($base, '_');
        if ($base === '') {
            $base = 'ANIMAL';
        }

        $db = DB::pdo();
        $stmt = $db->prepare('
        SELECT COUNT(*) FROM animals
        WHERE internal_code LIKE :p
        ');
        $stmt->execute([':p' => $year . '-%']);
        $n = (int) $stmt->fetchColumn();

        $seq = str_pad((string) ($n + 1), 3, '0', STR_PAD_LEFT);
        return $year . '-' . $seq . '-' . $base;
    }

    public static function save(): void
    {
        require_once __DIR__ . '/../Services/GeocodeService.php';
        require_once __DIR__ . '/../Services/GeoService.php';

        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            http_response_code(405);
            return;
        }

        $db = DB::pdo();

        $id = (int) ($_POST['id'] ?? 0);
        $old = null;
        if ($id > 0) {
            $st = $db->prepare('SELECT * FROM animals WHERE id=:id');
            $st->execute([':id' => $id]);
            $old = $st->fetch(PDO::FETCH_ASSOC) ?: null;
        }
        $isArchivedCorrection = $old && !empty($old['archived_at']);

        $name = trim((string) ($_POST['name'] ?? ''));
        $status = trim((string) ($_POST['status'] ?? 'refuge'));
        if ($isArchivedCorrection) {
            $status = (string) $old['status'];
        }
        if (in_array(strtolower($status), ['decede', 'décédé'], true)) {
            $deathCheck = $db->prepare('SELECT 1 FROM animal_deaths WHERE animal_id=:id');
            $deathCheck->execute([':id' => $id]);
            if (!$deathCheck->fetchColumn()) {
                http_response_code(400);
                echo h(t('error.use_death_action'));
                return;
            }
        }
        $refugeRoom = trim((string) ($_POST['refuge_room'] ?? ''));
        $locationChangeReason = trim((string) ($_POST['location_change_reason'] ?? ''));
        $availableRooms = ShelterRoomService::byCode(true);
        if (!isset($availableRooms[$refugeRoom]) || (string) $availableRooms[$refugeRoom]['status_code'] !== $status) {
            $refugeRoom = null;
        }

        $species = SpeciesService::normalize((string) ($_POST['species'] ?? 'chat'));
        if (!SpeciesService::exists($species, $id <= 0)) {
            http_response_code(400);
            echo h(t('error.valid_species'));
            return;
        }
        if ($status === 'chat_libre' && !SpeciesService::isCat($species)) {
            http_response_code(400);
            echo h(t('error.freecat_cats_only'));
            return;
        }

        $sex = trim((string) ($_POST['sex'] ?? 'U'));
        $sterilizationStatus = (string) ($_POST['sterilization_status'] ?? 'unknown');
        if (!in_array($sterilizationStatus, ['unknown', 'no', 'yes'], true)) {
            $sterilizationStatus = 'unknown';
        }
        $sterilized = $sterilizationStatus === 'yes' ? 1 : 0;
        $sterilizationDate = trim((string) ($_POST['sterilization_date'] ?? ''));
        if ($sterilizationDate !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $sterilizationDate)) {
            http_response_code(400);
            echo h(t('error.invalid_sterilization_date'));
            return;
        }
        $sterilizationVetId = (int) ($_POST['sterilization_vet_contact_id'] ?? 0) ?: null;
        $sterilizationClinicId = (int) ($_POST['sterilization_clinic_contact_id'] ?? 0) ?: null;
        if ($sterilizationStatus !== 'yes') {
            $sterilizationDate = '';
            $sterilizationVetId = null;
            $sterilizationClinicId = null;
        }
        $chip_id = trim((string) ($_POST['chip_id'] ?? ''));
        $identificationType = (string) ($_POST['identification_type'] ?? 'unknown');
        if (!in_array($identificationType, ['unknown', 'microchip', 'tattoo'], true)) {
            $identificationType = 'unknown';
        }
        $identificationDate = trim((string) ($_POST['identification_date'] ?? ''));
        $identificationRegistration = (string) ($_POST['identification_registration_status'] ?? 'unknown');
        if (!in_array($identificationRegistration, ['unknown', 'pending', 'registered'], true)) {
            $identificationRegistration = 'unknown';
        }
        if ($chip_id === '') {
            $identificationType = 'unknown';
            $identificationDate = '';
            $identificationRegistration = 'unknown';
        }
        $adoptionAvailability = (string) ($_POST['adoption_availability'] ?? 'unknown');
        if (!in_array($adoptionAvailability, ['unknown', 'not_available', 'available'], true)) {
            $adoptionAvailability = 'unknown';
        }
        $adoptionAvailableFrom = trim((string) ($_POST['adoption_available_from'] ?? ''));
        $adoptionUnavailabilityReason = trim((string) ($_POST['adoption_unavailability_reason'] ?? ''));
        foreach ([$identificationDate, $adoptionAvailableFrom] as $dateValue) {
            if ($dateValue !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateValue)) {
                http_response_code(400);
                echo h(t('error.invalid_date'));
                return;
            }
        }
        if ($adoptionAvailability !== 'available') {
            $adoptionAvailableFrom = '';
        }
        if ($adoptionAvailability !== 'not_available') {
            $adoptionUnavailabilityReason = '';
        }
        $websitePublished = isset($_POST['website_published']) ? 1 : 0;
        $websiteDescription = mb_substr(trim((string) ($_POST['website_description'] ?? '')), 0, 5000);
        $breed = trim((string) ($_POST['breed'] ?? ''));
        $color = trim((string) ($_POST['color'] ?? ''));
        $hairType = trim((string) ($_POST['hair_type'] ?? ''));
        $profileValue = static function (string $field): string {
            $value = (string) ($_POST[$field] ?? 'unknown');
            return in_array($value, ['unknown', 'yes', 'no'], true) ? $value : 'unknown';
        };
        $compatibilityDogs = $profileValue('compatibility_dogs');
        $compatibilityCats = $profileValue('compatibility_cats');
        $compatibilityChildren = $profileValue('compatibility_children');
        $houseTrained = $profileValue('house_trained');
        $freeCatSiteName = trim((string) ($_POST['free_cat_site_name'] ?? ''));
        $freeCatCity = trim((string) ($_POST['free_cat_city'] ?? ''));
        $freeCatAddress = trim((string) ($_POST['free_cat_address'] ?? ''));
        $freeCatCaretakerId = (int) ($_POST['free_cat_caretaker_contact_id'] ?? 0) ?: null;
        $freeCatCapturedAt = trim((string) ($_POST['free_cat_captured_at'] ?? ''));
        $freeCatReleasedAt = trim((string) ($_POST['free_cat_released_at'] ?? ''));
        $freeCatTrackingStatus = (string) ($_POST['free_cat_tracking_status'] ?? 'present');
        if (!in_array($freeCatTrackingStatus, ['present', 'missing', 'moved', 'deceased'], true)) {
            $freeCatTrackingStatus = 'present';
        }
        foreach ([$freeCatCapturedAt, $freeCatReleasedAt] as $freeCatDate) {
            if ($freeCatDate !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $freeCatDate)) {
                http_response_code(400);
                echo h(t('error.invalid_freecat_date'));
                return;
            }
        }
        if ($status !== 'chat_libre') {
            $freeCatSiteName = '';
            $freeCatCity = '';
            $freeCatAddress = '';
            $freeCatCaretakerId = null;
            $freeCatCapturedAt = '';
            $freeCatReleasedAt = '';
            $freeCatTrackingStatus = 'present';
        } elseif ($freeCatCaretakerId) {
            $check = $db->prepare(
                "SELECT 1 FROM directory_contacts WHERE id=:id AND deleted_at IS NULL AND kind='person'",
            );
            $check->execute([':id' => $freeCatCaretakerId]);
            if (!$check->fetchColumn()) {
                $freeCatCaretakerId = null;
            }
        }

        $notes = trim((string) ($_POST['notes'] ?? ''));

        $intake_date = trim((string) ($_POST['intake_date'] ?? '')); // YYYY-MM-DD
        $intakeType = (string) ($_POST['intake_type'] ?? 'unknown');
        if (!isset(self::INTAKE_TYPES[$intakeType])) {
            $intakeType = 'unknown';
        }
        $submittedIntakeReason = (string) ($_POST['intake_reason'] ?? 'unknown');
        if (!isset(self::INTAKE_REASONS[$submittedIntakeReason])) {
            $submittedIntakeReason = 'unknown';
        }
        $intakeOwnerCareHome = $submittedIntakeReason === 'owner_care_home' ? 1 : 0;
        $intakeReason = $intakeOwnerCareHome ? 'owner_health' : $submittedIntakeReason;
        $intakeCircumstances = trim((string) ($_POST['intake_circumstances'] ?? ''));

        // Naissance (date + estimation)
        $birth_date = trim((string) ($_POST['birth_date'] ?? ''));
        $birth_is_estimated = (int) ($_POST['birth_is_estimated'] ?? 0 ? 1 : 0);

        $birth_est_years = trim((string) ($_POST['birth_est_years'] ?? ''));
        $birth_est_months = trim((string) ($_POST['birth_est_months'] ?? ''));

        $birth_est_years = $birth_est_years === '' ? 0 : (int) $birth_est_years;
        $birth_est_months = $birth_est_months === '' ? 0 : (int) $birth_est_months;

        $birth_estimated_months = $birth_est_years * 12 + $birth_est_months;
        if ($birth_estimated_months === 0) {
            $birth_estimated_months = null;
        }

        // FIV/FELV (selon ton stockage actuel)
        $fiv = array_key_exists('fiv', $_POST) ? (isset($_POST['fiv']) ? 1 : 0) : null;
        $felv = array_key_exists('felv', $_POST) ? (isset($_POST['felv']) ? 1 : 0) : null;

        // Validations minimales
        if ($name === '') {
            http_response_code(400);
            echo h(t('error.name_required'));
            return;
        }
        if ($breed === '' && SpeciesService::isCat($species)) {
            $breed = 'Chat Européen';
        }
        if ($sex === '') {
            $sex = 'U';
        }

        // Si naissance estimée et date vide => calcul auto depuis "mois"
        if ($birth_is_estimated === 1 && $birth_date === '' && $birth_estimated_months !== null) {
            $dt = new DateTime('now');
            $dt->modify('-' . $birth_estimated_months . ' months');
            $birth_date = $dt->format('Y-m-d');
        }

        // Normaliser dates vides en NULL (SQLite aime mieux)
        $birth_date = $birth_date === '' ? null : $birth_date;
        $intake_date = $intake_date === '' ? null : $intake_date;

        // Code interne : auto si vide (et idéalement readonly en edit)
        $internal_code = trim((string) ($_POST['internal_code'] ?? ''));
        if ($internal_code === '') {
            $internal_code = self::nextInternalCode($name);
        }

        // Quarantaine : durée issue des paramètres généraux.
        $quarantine_until = null;
        if (strtolower($status) === 'quarantaine' && $intake_date !== null) {
            $ts = strtotime($intake_date);
            if ($ts !== false) {
                $quarantine_until = date('Y-m-d', $ts + AppSettings::int('quarantine_days', 1, 90) * 86400);
            }
        } else {
            // si pas en quarantaine, on vide la sortie (optionnel, mais logique)
            $quarantine_until = null;
        }

        $rescueAddress = trim((string) ($_POST['rescue_address'] ?? ''));
        $rescueLocationName = trim((string) ($_POST['rescue_location_name'] ?? ''));
        $currentAddress = trim((string) ($_POST['current_address'] ?? ''));
        $fosterContactId = (int) ($_POST['foster_contact_id'] ?? 0) ?: null;
        $fosterContact = null;
        if (!$isArchivedCorrection && in_array($status, ['fa', 'fa_permanente'], true) && $fosterContactId) {
            $st = $db->prepare(
                "SELECT dc.* FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id AND r.role='fa' WHERE dc.id=:id AND dc.deleted_at IS NULL",
            );
            $st->execute([':id' => $fosterContactId]);
            $fosterContact = $st->fetch(PDO::FETCH_ASSOC) ?: null;
            if (!$fosterContact) {
                http_response_code(400);
                echo h(t('error.invalid_foster'));
                return;
            }
            $currentAddress =
                implode(
                    ' ',
                    array_filter([
                        $fosterContact['address'] ?? '',
                        $fosterContact['postal_code'] ?? '',
                        $fosterContact['city'] ?? '',
                    ]),
                ) ?:
                'Chez ' . $fosterContact['name'];
        }
        if ($status === 'chat_libre') {
            $currentAddress =
                trim(implode(' ', array_filter([$freeCatAddress, $freeCatCity]))) ?:
                ($freeCatSiteName ?:
                $currentAddress);
        }

        $depositor = self::resolveDirectoryContact(
            $db,
            (int) ($_POST['depositor_contact_id'] ?? 0),
            trim((string) ($_POST['new_depositor_name'] ?? '')),
            'person',
            'deposant',
        );
        $depositorId = $depositor ? (int) $depositor['id'] : null;

        // Pour l’instant, tu bloques le lieu actuel
        if ($isArchivedCorrection) {
            $currentAddress = (string) ($old['current_address'] ?? '');
        }
        if ($currentAddress === '' && !$isArchivedCorrection) {
            $currentAddress = '5 RUE NOTRE DAME 29260 LESNEVEN';
        }

        // Géocode rescue si adresse présente
        $rescueLat = null;
        $rescueLng = null;
        if ($rescueAddress !== '') {
            $geo = GeoService::geocode($db, $rescueAddress);
            if ($geo) {
                $rescueLat = $geo['lat'];
                $rescueLng = $geo['lng'];
            }
        }

        // Géocode current (toujours)
        $currentLat = null;
        $currentLng = null;
        if ($currentAddress !== '' && !$isArchivedCorrection) {
            $geo2 = GeoService::geocode($db, $currentAddress);
            if ($geo2) {
                $currentLat = $geo2['lat'];
                $currentLng = $geo2['lng'];
            }
        }

        $fiv = $fiv ?? (int) ($old['fiv'] ?? 0);
        $felv = $felv ?? (int) ($old['felv'] ?? 0);
        $careBox = in_array($status, ['soin', 'quarantaine'], true) ? $old['care_box_key'] ?? null : null;
        if ($isArchivedCorrection) {
            $refugeRoom = $old['refuge_room'] ?? null;
            $careBox = $old['care_box_key'] ?? null;
            $quarantine_until = $old['quarantine_until'] ?? null;
            $currentLat = $old['current_lat'] ?? null;
            $currentLng = $old['current_lng'] ?? null;
        }

        if ($id > 0) {
            $stmt = $db->prepare("
            UPDATE animals SET
            name=:name,
            internal_code=:internal_code,
            status=:status,
            refuge_room=:refuge_room,
            care_box_key=:care_box_key,
            species=:species,
            sex=:sex,
            sterilized=:sterilized,
            sterilization_status=:sterilization_status,sterilization_date=:sterilization_date,sterilization_vet_contact_id=:sterilization_vet,sterilization_clinic_contact_id=:sterilization_clinic,
            chip_id=:chip_id,
            identification_type=:identification_type,identification_date=:identification_date,identification_registration_status=:identification_registration_status,
            adoption_availability=:adoption_availability,adoption_available_from=:adoption_available_from,adoption_unavailability_reason=:adoption_unavailability_reason,
            website_published=:website_published,website_description=:website_description,
            breed=:breed,
            color=:color,
            hair_type=:hair_type,
            compatibility_dogs=:compatibility_dogs,
            compatibility_cats=:compatibility_cats,
            compatibility_children=:compatibility_children,
            house_trained=:house_trained,
            free_cat_site_name=:free_cat_site_name,free_cat_city=:free_cat_city,free_cat_address=:free_cat_address,
            free_cat_caretaker_contact_id=:free_cat_caretaker_contact_id,free_cat_captured_at=:free_cat_captured_at,
            free_cat_released_at=:free_cat_released_at,free_cat_tracking_status=:free_cat_tracking_status,
            birth_date=:birth_date,
            birth_is_estimated=:birth_is_estimated,
            birth_estimated_months=:birth_estimated_months,
            notes=:notes,
            intake_date=:intake_date,
            intake_type=:intake_type,
            intake_reason=:intake_reason,
            intake_owner_care_home=:intake_owner_care_home,
            depositor_contact_id=:depositor_contact_id,
            intake_circumstances=:intake_circumstances,
            quarantine_until=:quarantine_until,
            fiv=:fiv,
            felv=:felv,
            rescue_address=:rescue_address,rescue_location_name=:rescue_location_name,
            rescue_location_confidence=CASE WHEN COALESCE(:rescue_location_name,'')=COALESCE(rescue_location_name,'') AND COALESCE(:rescue_address,'')=COALESCE(rescue_address,'') THEN rescue_location_confidence WHEN TRIM(COALESCE(:rescue_location_name,''))<>'' OR TRIM(COALESCE(:rescue_address,''))<>'' THEN 'documented' ELSE NULL END,
            rescue_location_evidence=CASE WHEN COALESCE(:rescue_location_name,'')=COALESCE(rescue_location_name,'') AND COALESCE(:rescue_address,'')=COALESCE(rescue_address,'') THEN rescue_location_evidence WHEN TRIM(COALESCE(:rescue_location_name,''))<>'' OR TRIM(COALESCE(:rescue_address,''))<>'' THEN 'Lieu renseigné depuis la fiche animale' ELSE NULL END,
            rescue_lat=:rescue_lat, rescue_lng=:rescue_lng,
            current_address=:current_address, current_lat=:current_lat, current_lng=:current_lng,
            updated_at=datetime('now')
            WHERE id=:id
            ");

            $stmt->execute([
                ':id' => $id,
                ':name' => $name,
                ':internal_code' => $internal_code,
                ':status' => $status,
                ':refuge_room' => $refugeRoom,
                ':care_box_key' => $careBox,
                ':species' => $species,
                ':sex' => $sex,
                ':sterilized' => $sterilized,
                ':sterilization_status' => $sterilizationStatus,
                ':sterilization_date' => $sterilizationDate ?: null,
                ':sterilization_vet' => $sterilizationVetId,
                ':sterilization_clinic' => $sterilizationClinicId,
                ':chip_id' => $chip_id,
                ':identification_type' => $identificationType,
                ':identification_date' => $identificationDate ?: null,
                ':identification_registration_status' => $identificationRegistration,
                ':adoption_availability' => $adoptionAvailability,
                ':adoption_available_from' => $adoptionAvailableFrom ?: null,
                ':adoption_unavailability_reason' => $adoptionUnavailabilityReason ?: null,
                ':website_published' => $websitePublished,
                ':website_description' => $websiteDescription ?: null,
                ':breed' => $breed,
                ':color' => $color !== '' ? $color : null,
                ':hair_type' => $hairType !== '' ? $hairType : null,
                ':compatibility_dogs' => $compatibilityDogs,
                ':compatibility_cats' => $compatibilityCats,
                ':compatibility_children' => $compatibilityChildren,
                ':house_trained' => $houseTrained,
                ':free_cat_site_name' => $freeCatSiteName ?: null,
                ':free_cat_city' => $freeCatCity ?: null,
                ':free_cat_address' => $freeCatAddress ?: null,
                ':free_cat_caretaker_contact_id' => $freeCatCaretakerId,
                ':free_cat_captured_at' => $freeCatCapturedAt ?: null,
                ':free_cat_released_at' => $freeCatReleasedAt ?: null,
                ':free_cat_tracking_status' => $freeCatTrackingStatus,
                ':birth_date' => $birth_date,
                ':birth_is_estimated' => $birth_is_estimated,
                ':birth_estimated_months' => $birth_estimated_months,
                ':notes' => $notes,
                ':intake_date' => $intake_date,
                ':intake_type' => $intakeType,
                ':intake_reason' => $intakeReason,
                ':intake_owner_care_home' => $intakeOwnerCareHome,
                ':depositor_contact_id' => $depositorId,
                ':intake_circumstances' => $intakeCircumstances ?: null,
                ':quarantine_until' => $quarantine_until,
                ':fiv' => $fiv,
                ':felv' => $felv,
                ':rescue_address' => $rescueAddress,
                ':rescue_location_name' => $rescueLocationName ?: null,
                ':rescue_lat' => $rescueLat,
                ':rescue_lng' => $rescueLng,
                ':current_address' => $currentAddress,
                ':current_lat' => $currentLat,
                ':current_lng' => $currentLng,
            ]);

            if ($old) {
                LocationHistoryService::record(
                    $db,
                    $id,
                    $old,
                    [
                        'status' => $status,
                        'refuge_room' => $refugeRoom,
                        'care_box_key' => $careBox,
                        'current_address' => $currentAddress,
                    ],
                    'animal_form',
                    $locationChangeReason ?: 'Emplacement modifié depuis la fiche animale',
                );
            }

            //self::logHistory(
            //    $id,
            //    'update',
            //    'Fiche modifiée'
            //);

            // Code interne : en édition, on ne le regénère PAS
            $internal_code = trim((string) ($_POST['internal_code'] ?? ''));
            if ($id > 0) {
                if ($internal_code === '' && $old && !empty($old['internal_code'])) {
                    $internal_code = (string) $old['internal_code'];
                }
            } else {
                if ($internal_code === '') {
                    $internal_code = self::nextInternalCode($name);
                }
            }

            if ($old) {
                // Les nouvelles valeurs “connues” (ce que tu viens de sauver)
                // Astuce: tu mets TOUT ce que tu passes à l’UPDATE ici, mais sans maintenance lourde :
                // si tu ajoutes un champ au formulaire + UPDATE, tu l’ajoutes déjà ici de toute façon.
                $new = [
                    'name' => $name,
                    'internal_code' => $internal_code,
                    'status' => $status,
                    'refuge_room' => $refugeRoom,
                    'species' => $species,
                    'sex' => $sex,
                    'sterilized' => $sterilized,
                    'sterilization_status' => $sterilizationStatus,
                    'sterilization_date' => $sterilizationDate,
                    'sterilization_vet_contact_id' => $sterilizationVetId,
                    'sterilization_clinic_contact_id' => $sterilizationClinicId,
                    'chip_id' => $chip_id,
                    'identification_type' => $identificationType,
                    'identification_date' => $identificationDate,
                    'identification_registration_status' => $identificationRegistration,
                    'adoption_availability' => $adoptionAvailability,
                    'adoption_available_from' => $adoptionAvailableFrom,
                    'adoption_unavailability_reason' => $adoptionUnavailabilityReason,
                    'website_published' => $websitePublished,
                    'website_description' => $websiteDescription,
                    'breed' => $breed,
                    'color' => $color,
                    'hair_type' => $hairType,
                    'compatibility_dogs' => $compatibilityDogs,
                    'compatibility_cats' => $compatibilityCats,
                    'compatibility_children' => $compatibilityChildren,
                    'house_trained' => $houseTrained,
                    'birth_date' => $birth_date,
                    'notes' => $notes,
                    'intake_date' => $intake_date,
                    'intake_type' => $intakeType,
                    'intake_reason' => $intakeReason,
                    'intake_owner_care_home' => $intakeOwnerCareHome,
                    'depositor_contact_id' => $depositorId,
                    'intake_circumstances' => $intakeCircumstances,
                    'rescue_location_name' => $rescueLocationName,
                    'quarantine_until' => $quarantine_until,
                    'fiv' => $fiv,
                    'felv' => $felv,
                    // ajoute ici uniquement les champs que tu SAUVEGARDES dans UPDATE
                    // (si demain tu ajoutes 3 champs au form, tu les ajouteras déjà à l’UPDATE -> copie/colle aussi ici)
                ];

                // Champs à ignorer (bruit)
                $ignore = ['id', 'created_at', 'updated_at', 'internal_code'];

                $changes = [];
                foreach ($new as $field => $newVal) {
                    if (in_array($field, $ignore, true)) {
                        continue;
                    }

                    $oldVal = $old[$field] ?? null;

                    // Normalisation légère pour éviter les faux positifs
                    $o = is_null($oldVal) ? '' : (string) $oldVal;
                    $n = is_null($newVal) ? '' : (string) $newVal;

                    if ($o !== $n) {
                        $label = match ($field) {
                            'name' => 'Nom',
                            'status' => 'Statut',
                            'refuge_room' => 'Salle au refuge',
                            'species' => 'Espèce',
                            'sex' => 'Sexe',
                            'sterilized' => 'Stérilisation',
                            'sterilization_status' => 'État de stérilisation',
                            'sterilization_date' => 'Date de stérilisation',
                            'sterilization_vet_contact_id' => 'Vétérinaire de stérilisation',
                            'sterilization_clinic_contact_id' => 'Cabinet de stérilisation',
                            'chip_id' => 'Puce',
                            'identification_type' => 'Type d’identification',
                            'identification_date' => 'Date d’identification',
                            'identification_registration_status' => 'Enregistrement I-CAD',
                            'adoption_availability' => 'Disponibilité à l’adoption',
                            'adoption_available_from' => 'Disponible à partir du',
                            'adoption_unavailability_reason' => 'Motif d’indisponibilité',
                            'website_published' => 'Publication sur le site',
                            'website_description' => 'Présentation publique',
                            'breed' => 'Race',
                            'color' => 'Couleur / robe',
                            'hair_type' => 'Type de poil',
                            'compatibility_dogs' => 'Compatibilité chiens',
                            'compatibility_cats' => 'Compatibilité chats',
                            'compatibility_children' => 'Compatibilité enfants',
                            'house_trained' => 'Propreté',
                            'birth_date' => 'Naissance',
                            'intake_date' => 'Arrivée',
                            'intake_type' => 'Mode d’entrée',
                            'intake_reason' => 'Motif d’entrée',
                            'intake_owner_care_home' => 'Entrée du propriétaire en établissement',
                            'depositor_contact_id' => 'Déposant',
                            'intake_circumstances' => 'Circonstances de l’entrée',
                            'rescue_location_name' => 'Commune d’origine',
                            'quarantine_until' => 'Fin quarantaine',
                            'fiv' => 'FIV',
                            'felv' => 'FELV',
                            'notes' => 'Notes',
                            default => $field,
                        };

                        // Evite d’exploser le log sur un gros champ notes
                        if (in_array($field, ['notes', 'website_description'], true)) {
                            $changes[] = "$label : (modifié)";
                        } else {
                            if (
                                in_array(
                                    $field,
                                    [
                                        'compatibility_dogs',
                                        'compatibility_cats',
                                        'compatibility_children',
                                        'house_trained',
                                    ],
                                    true,
                                )
                            ) {
                                $profileLabels = ['unknown' => 'Inconnu', 'yes' => 'Oui', 'no' => 'Non'];
                                $o = $profileLabels[$o] ?? $o;
                                $n = $profileLabels[$n] ?? $n;
                            }
                            $changes[] = "$label : " . ($o === '' ? '∅' : $o) . ' → ' . ($n === '' ? '∅' : $n);
                        }
                    }
                }

                if (!empty($changes)) {
                    $statusChanged = (string) ($old['status'] ?? '') !== (string) $status;
                    $type = $statusChanged ? 'status' : 'update';
                    $label = $statusChanged ? 'Changement de statut' : 'Fiche modifiée';

                    self::logHistory($id, $type, $label, implode("\n", $changes));
                }
            }
        } else {
            $stmt = $db->prepare("
            INSERT INTO animals
            (name, internal_code, status, refuge_room, species, sex, sterilized,sterilization_status,sterilization_date,sterilization_vet_contact_id,sterilization_clinic_contact_id, chip_id,identification_type,identification_date,identification_registration_status,adoption_availability,adoption_available_from,adoption_unavailability_reason,website_published,website_description, breed, color, hair_type,
                                 compatibility_dogs,compatibility_cats,compatibility_children,house_trained,
                                 free_cat_site_name,free_cat_city,free_cat_address,free_cat_caretaker_contact_id,free_cat_captured_at,free_cat_released_at,free_cat_tracking_status,
                                 birth_date, birth_is_estimated, birth_estimated_months,
                                 notes,intake_date,intake_type,intake_reason,intake_owner_care_home,depositor_contact_id,intake_circumstances,quarantine_until,fiv,felv,created_at,updated_at,rescue_address,rescue_location_name,rescue_location_confidence,rescue_location_evidence,rescue_lat,rescue_lng,current_address,current_lat,current_lng)
            VALUES
            (:name,:internal_code,:status,:refuge_room,:species,:sex,:sterilized,:sterilization_status,:sterilization_date,:sterilization_vet,:sterilization_clinic,:chip_id,:identification_type,:identification_date,:identification_registration_status,:adoption_availability,:adoption_available_from,:adoption_unavailability_reason,:website_published,:website_description,:breed,:color,:hair_type,
                                 :compatibility_dogs,:compatibility_cats,:compatibility_children,:house_trained,
                                 :free_cat_site_name,:free_cat_city,:free_cat_address,:free_cat_caretaker_contact_id,:free_cat_captured_at,:free_cat_released_at,:free_cat_tracking_status,
                                 :birth_date,:birth_is_estimated,:birth_estimated_months,
                                 :notes,:intake_date,:intake_type,:intake_reason,:intake_owner_care_home,:depositor_contact_id,:intake_circumstances,:quarantine_until,:fiv,:felv,datetime('now'),datetime('now'),
                                 :rescue_address,:rescue_location_name,CASE WHEN TRIM(COALESCE(:rescue_location_name,''))<>'' OR TRIM(COALESCE(:rescue_address,''))<>'' THEN 'documented' ELSE NULL END,CASE WHEN TRIM(COALESCE(:rescue_location_name,''))<>'' OR TRIM(COALESCE(:rescue_address,''))<>'' THEN 'Lieu renseigné à la création de la fiche animale' ELSE NULL END,:rescue_lat,:rescue_lng,:current_address,:current_lat,:current_lng)
            ");

            $stmt->execute([
                ':name' => $name,
                ':internal_code' => $internal_code,
                ':status' => $status,
                ':refuge_room' => $refugeRoom,
                ':species' => $species,
                ':sex' => $sex,
                ':sterilized' => $sterilized,
                ':sterilization_status' => $sterilizationStatus,
                ':sterilization_date' => $sterilizationDate ?: null,
                ':sterilization_vet' => $sterilizationVetId,
                ':sterilization_clinic' => $sterilizationClinicId,
                ':chip_id' => $chip_id,
                ':identification_type' => $identificationType,
                ':identification_date' => $identificationDate ?: null,
                ':identification_registration_status' => $identificationRegistration,
                ':adoption_availability' => $adoptionAvailability,
                ':adoption_available_from' => $adoptionAvailableFrom ?: null,
                ':adoption_unavailability_reason' => $adoptionUnavailabilityReason ?: null,
                ':website_published' => $websitePublished,
                ':website_description' => $websiteDescription ?: null,
                ':breed' => $breed,
                ':color' => $color !== '' ? $color : null,
                ':hair_type' => $hairType !== '' ? $hairType : null,
                ':compatibility_dogs' => $compatibilityDogs,
                ':compatibility_cats' => $compatibilityCats,
                ':compatibility_children' => $compatibilityChildren,
                ':house_trained' => $houseTrained,
                ':free_cat_site_name' => $freeCatSiteName ?: null,
                ':free_cat_city' => $freeCatCity ?: null,
                ':free_cat_address' => $freeCatAddress ?: null,
                ':free_cat_caretaker_contact_id' => $freeCatCaretakerId,
                ':free_cat_captured_at' => $freeCatCapturedAt ?: null,
                ':free_cat_released_at' => $freeCatReleasedAt ?: null,
                ':free_cat_tracking_status' => $freeCatTrackingStatus,
                ':birth_date' => $birth_date,
                ':birth_is_estimated' => $birth_is_estimated,
                ':birth_estimated_months' => $birth_estimated_months,
                ':notes' => $notes,
                ':intake_date' => $intake_date,
                ':intake_type' => $intakeType,
                ':intake_reason' => $intakeReason,
                ':intake_owner_care_home' => $intakeOwnerCareHome,
                ':depositor_contact_id' => $depositorId,
                ':intake_circumstances' => $intakeCircumstances ?: null,
                ':quarantine_until' => $quarantine_until,
                ':fiv' => $fiv,
                ':felv' => $felv,
                ':rescue_address' => $rescueAddress,
                ':rescue_location_name' => $rescueLocationName ?: null,
                ':rescue_lat' => $rescueLat,
                ':rescue_lng' => $rescueLng,
                ':current_address' => $currentAddress,
                ':current_lat' => $currentLat,
                ':current_lng' => $currentLng,
            ]);

            $id = (int) $db->lastInsertId();

            LocationHistoryService::record(
                $db,
                $id,
                null,
                [
                    'status' => $status,
                    'refuge_room' => $refugeRoom,
                    'care_box_key' => null,
                    'current_address' => $currentAddress,
                ],
                'animal_creation',
                'Emplacement lors de la création de la fiche',
            );

            self::logHistory($id, 'create', 'Animal ajouté', "Nom : $name\nStatut : $status");
        }

        self::syncIntakeMovement(
            $db,
            $id,
            $intake_date,
            $intakeType,
            $submittedIntakeReason,
            $depositorId,
            $rescueLocationName,
            $rescueAddress,
            $intakeCircumstances,
        );

        if (!$isArchivedCorrection && in_array($status, ['fa', 'fa_permanente'], true) && $fosterContactId) {
            $latest = $db->prepare(
                "SELECT contact_id FROM animal_placements WHERE animal_id=:id AND event_type='foster' ORDER BY date(event_date) DESC,id DESC LIMIT 1",
            );
            $latest->execute([':id' => $id]);
            $latestFosterId = (int) $latest->fetchColumn();
            $enteredFoster = !$old || !in_array((string) ($old['status'] ?? ''), ['fa', 'fa_permanente'], true);
            if ($enteredFoster || $latestFosterId !== $fosterContactId) {
                $db->prepare(
                    "INSERT INTO animal_placements(animal_id,event_type,event_date,contact_id,reason,notes,created_by) VALUES(:animal,'foster',date('now'),:contact,:reason,:notes,:user)",
                )->execute([
                    ':animal' => $id,
                    ':contact' => $fosterContactId,
                    ':reason' => 'Changement d’emplacement depuis la fiche',
                    ':notes' => $status === 'fa_permanente' ? 'Famille d’accueil permanente' : null,
                    ':user' => Auth::id(),
                ]);
            }
        }

        $initial_weight = trim((string) ($_POST['initial_weight'] ?? ''));
        $initial_weight = $initial_weight === '' ? null : (float) $initial_weight;

        if ($initial_weight !== null && $initial_weight > 0) {
            $stmt = $db->prepare("
            INSERT INTO measurements(animal_id, type, value, unit, measured_at)
            VALUES(:aid,'weight',:v,'kg',datetime('now'))
            ");
            $stmt->execute([
                ':aid' => $id,
                ':v' => $initial_weight,
            ]);
        }

        header('Location: /animal?id=' . $id);
        exit();
    }
}
