<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ApplicationController;
use App\Http\Controllers\ParcelController;
use App\Http\Controllers\ParcelUserController;
use App\Http\Controllers\ProfilController;
use App\Http\Controllers\FilterController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\SettingController;
use App\Http\Controllers\ActionController;
use App\Http\Controllers\ScrapController;
use App\Http\Controllers\AdvertController;
use App\Http\Controllers\OpportunityController;
use App\Http\Controllers\MarketController;
use App\Http\Controllers\LoadController;
use App\Http\Controllers\EstimationController;
use App\Http\Controllers\ContactController;
use App\Http\Controllers\SavedSearchController;
// RequestController removed - leads replace requests
use App\Http\Controllers\WidgetScriptController;
use App\Http\Controllers\WidgetController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\AcquisitionController;
use App\Http\Controllers\AgendaController;
use App\Http\Controllers\DocumentController;
use App\Http\Controllers\ContentBarSearchController;
use App\Http\Controllers\SearchSuggestionController;
use App\Http\Controllers\FeedbackController;
use App\Http\Controllers\FeeController;

use App\Http\Controllers\Parcel\TabInfoController;
use App\Http\Controllers\Parcel\TabEstimController;
use App\Http\Controllers\Parcel\TabComparablesController;
use App\Http\Controllers\Parcel\TabFoncierController;
use App\Http\Controllers\Parcel\TabOwnerController;
use App\Http\Controllers\Parcel\TabEnergieController;
use App\Http\Controllers\Parcel\TabPluController;
use App\Http\Controllers\Parcel\TabPermitController;
use App\Http\Controllers\Parcel\TabFiliationController;
use App\Http\Controllers\Parcel\TabFiscaliteController;
use App\Http\Controllers\Parcel\TabAdvertController;
use App\Http\Controllers\Parcel\TabLeadsController;
use App\Http\Controllers\Parcel\ParcelNoteController;
use App\Http\Controllers\Parcel\SwpController;

// App Links de l'application Android (audit mobile 2026-11-01, C3) : Android vérifie ici que l'application peut ouvrir
// les liens de ce site. 404 tant qu'aucune empreinte n'est configurée (config/android_app.php).
Route::get('/.well-known/assetlinks.json', function () {
    $fingerprints = config('android_app.sha256_cert_fingerprints', []);
    abort_if(empty($fingerprints), 404);

    return response()->json([[
        'relation' => ['delegate_permission/common.handle_all_urls'],
        'target'   => [
            'namespace'                => 'android_app',
            'package_name'             => config('android_app.package'),
            'sha256_cert_fingerprints' => $fingerprints,
        ],
    ]], 200, ['Cache-Control' => 'public, max-age=3600'], JSON_UNESCAPED_SLASHES);
})->name('android.assetlinks')
    // Lu par Android à chaque installation : ni session, ni cookie, ni ligne dans core.sessions.
    ->withoutMiddleware([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Rootsud\Core\Http\Middlewares\CaptureSignupAttribution::class,
        \Rootsud\Core\Http\Middlewares\CheckCancellationToken::class,
    ]);

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

// Widget JS - public, no auth
// Sprint 2026-05-22 - Multi-instance : route canon `/widget/{uuid}.js` (le JS
// bundle est servi selon le type identifié via uuid). Routes legacy par type
// préservées (rétro-compat clients existants : sert le bundle générique du type).
Route::get('/widget/estimation.js', [WidgetScriptController::class, 'script'])->name('widget.script');
Route::get('/widget/dpe.js', [WidgetScriptController::class, 'dpe'])->name('widget.dpe.script');
Route::get('/widget/opportunite.js', [WidgetScriptController::class, 'opportunite'])->name('widget.opportunite.script');
Route::get('/widget/recherche.js', [WidgetScriptController::class, 'recherche'])->name('widget.recherche.script');
Route::get('/widget/devis.js', [WidgetScriptController::class, 'devis'])->name('widget.devis.script');
Route::get('/widget/reviews.js', [WidgetScriptController::class, 'reviews'])->name('widget.reviews.script');
Route::get('/widget/{uuid}.js', [WidgetScriptController::class, 'byUuid'])
    ->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
    ->name('widget.byUuid.script');
Route::get('/widget/test', fn () => view('widget.test'))->name('widget.test');

// Sprint 2026-05-23 - Page publique standalone hébergée (mode hosting_mode='hosted')
// Format slug : `{brand}-{token8}` ex `/w/dupont-immobilier-h5n8m1q4` (cf User_widget::generateHostedSlug).
// throttle 60/min + 404 silencieux + 36^8 combinaisons → impossible à énumérer.
// Pointe sur WidgetController (qui a $this->site via HasPage) - public, hors groupe auth.
Route::get('/w/{slug}', [WidgetController::class, 'hostedPage'])
    ->where('slug', '[a-z0-9][a-z0-9-]{5,79}')
    ->middleware('throttle:60,1')
    ->name('widget.hosted.page');

// Sprint 2026-06-28 - Redirection traçante des QR codes de widget (publique).
// Le QR encode `/q/{uuid}` (uuid immutable) : on logge un scan puis on redirige
// vers l'URL finale (page hostée ou embed_url). 302 → vitrine si inconnu/désactivé.
Route::get('/q/{uuid}', [WidgetController::class, 'trackScan'])
    ->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
    ->middleware('throttle:120,1')
    ->name('widget.qr.redirect');

// Agenda - RSVP public via lien signé (email d'invitation, réponse sans connexion).
// Signature vérifiée dans la méthode (hasValidSignature) ; multi-segments donc
// jamais capturé par le catch-all /agenda/{map?}.
Route::get('/agenda/rsvp/{event}/{user}/{status}', [\App\Http\Controllers\AgendaController::class, 'rsvpSigned'])
    ->where(['event' => '[0-9]+', 'user' => '[0-9]+', 'status' => '[0-9]+'])
    ->middleware('throttle:30,1')
    ->name('agenda.rsvp.signed');

// Phase 4 - N8 / Sprint 2026-05-22 : NPS post-vente (routes publiques signed)
// IMPORTANT : `/nps/{lead}/form` doit être avant `/nps/{lead}/{score}` car
// {score} pourrait théoriquement matcher "form" si la regex changeait.
Route::get('/nps/{lead}/form', [\App\Http\Controllers\NpsController::class, 'showForm'])
    ->where('lead', '[0-9]+')
    ->name('nps.form');
Route::post('/nps/{lead}/submit', [\App\Http\Controllers\NpsController::class, 'submitForm'])
    ->where('lead', '[0-9]+')
    ->middleware('throttle:10,1') // anti-fraude : 10 soumissions/min/IP
    ->name('nps.submit');
Route::get('/nps/{lead}/{score}', [\App\Http\Controllers\NpsController::class, 'recordScore'])
    ->where(['lead' => '[0-9]+', 'score' => '[0-9]|10'])
    ->name('nps.score');
Route::get('/nps/{lead}/thankyou/{category}', [\App\Http\Controllers\NpsController::class, 'thankYou'])
    ->where(['lead' => '[0-9]+', 'category' => 'detractor|passive|promoter'])
    ->name('nps.thankyou');
Route::post('/nps/{lead}/feedback', [\App\Http\Controllers\NpsController::class, 'storeFeedback'])
    ->where('lead', '[0-9]+')
    ->name('nps.feedback');

// Sprint Avis certifiable - pages publiques avis (méthodologie légale, profil agent, signalement)
// IMPORTANT : les routes littérales (/avis/methodologie) AVANT le catch-all /avis/{slug}.
Route::get('/avis/methodologie', [\App\Http\Controllers\ReviewPublicController::class, 'methodologie'])
    ->name('reviews.methodologie');
// Page profil public d'avis (JSON-LD / étoiles Google) - slug littéral, ne matche pas {review} numérique
Route::get('/avis/p/{slug}/data', [\App\Http\Controllers\ReviewPublicController::class, 'profileData'])
    ->where('slug', '[a-z0-9-]+')
    ->middleware('throttle:240,1')
    ->name('reviews.profileData');
Route::get('/avis/p/{slug}', [\App\Http\Controllers\ReviewPublicController::class, 'profile'])
    ->where('slug', '[a-z0-9-]+')
    ->middleware('throttle:120,1')
    ->name('reviews.profile');
// Signalement public d'un avis abusif (throttle anti-spam)
Route::post('/avis/{review}/signaler', [\App\Http\Controllers\ReviewPublicController::class, 'report'])
    ->where('review', '[0-9]+')
    ->middleware('throttle:5,1')
    ->name('reviews.report');

// Grille V2 (2026-07-10) - Webhook Yousign (API v3) : notifie la fin de signature
// (done/declined/expired). Public, hors CSRF (VerifyCsrfToken $except webhooks/*),
// authentifié par HMAC X-Yousign-Signature-256 (YOUSIGN_WEBHOOK_SECRET).
Route::post('/webhooks/yousign', [\App\Http\Controllers\YousignWebhookController::class, 'handle'])
    ->middleware('throttle:120,1')
    ->name('webhooks.yousign');

// Outil gratuit « Localiser une annonce » de la vitrine (2026-11-02) : calcul en direct du top 10 d'une annonce
// pas encore analysée. Appel serveur à serveur signé (HMAC), sans session, cookie ni CSRF.
Route::post('/interne/outils/localiser-annonce', [\App\Http\Controllers\VitrineAdvertLocateController::class, 'compute'])
    ->middleware('throttle:30,1')
    ->name('internal.outils.locate')
    ->withoutMiddleware([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Rootsud\Core\Http\Middlewares\CaptureSignupAttribution::class,
        \Rootsud\Core\Http\Middlewares\CheckCancellationToken::class,
    ]);

// Sprint B - D3 : pixel tracking match seen (anonyme, hors auth)
Route::get('/m/{trackingToken}', [\App\Http\Controllers\AcquisitionController::class, 'trackMatchSeen'])
    ->where('trackingToken', '[A-Za-z0-9_\\-=]+')
    ->name('m.seen');

// Sprint C1 #2 - Tracking email/SMS (pixel + short-link + opt-out) - PUBLIQUES, hors auth
Route::get('/t/o/{token}.png', [\App\Http\Controllers\TrackingController::class, 'pixel'])
    ->where('token', '[A-Za-z0-9]{8,16}')
    ->middleware('throttle:120,1')
    ->name('tracking.pixel');
Route::get('/t/{token}', [\App\Http\Controllers\TrackingController::class, 'redirect'])
    ->where('token', '[A-Za-z0-9]{8,16}')
    ->middleware('throttle:240,1')
    ->name('tracking.redirect');
Route::get('/optout/{token}', [\App\Http\Controllers\TrackingOptoutController::class, 'show'])
    ->where('token', '[A-Za-z0-9]{8,16}')
    ->name('tracking.optout');
Route::post('/optout/{token}', [\App\Http\Controllers\TrackingOptoutController::class, 'confirm'])
    ->where('token', '[A-Za-z0-9]{8,16}')
    ->name('tracking.optout.confirm');

Route::middleware(['web', 'checkblocked', 'auth', 'verified'])->group(function () {

	// Omnibar CRM search (DEPRECATED - CRM scopes now in /city/search/json via core-city SearchController)
	Route::post('/search/crm', [ContentBarSearchController::class, 'search'])->name('search.crm');

	// Omnibar suggestions (empty state dropdown)
	Route::post('/search/suggestions', [SearchSuggestionController::class, 'suggestions'])->name('search.suggestions');

	// Banc de mesure du top 10 de la fiche annonce (phase 2, 2026-09-25). Page interne,
	// 404 hors config('advert_locate.admin_user_ids') - controle dans le controleur.
	Route::prefix('outils/benchmark-localisation')->group(function () {
		Route::get('/', [\App\Http\Controllers\AdvertLocateBenchmarkController::class, 'index'])->name('advert_locate.index');
		Route::post('/references/{id}/status', [\App\Http\Controllers\AdvertLocateBenchmarkController::class, 'updateStatus'])->whereNumber('id')->name('advert_locate.status');
		Route::post('/references/{id}/adopt-dpe', [\App\Http\Controllers\AdvertLocateBenchmarkController::class, 'adoptDpe'])->whereNumber('id')->name('advert_locate.adopt');
	});

	// Widgets - module multi-instance (Sprint 2026-05-22).
	// Pattern split-panel : POST = contenu panel (#colMenu), GET = layout complet (LoadController).
	// Les routes par uuid sont déclarées AVANT les legacy (estimation/dpe/opportunite) pour
	// éviter qu'un uuid matche par accident sur la regex {type}.
	Route::prefix('widgets')->group(function () {
		// Le fragment liste reste hors gate : il rend le teaser upgrade quand le
		// plan n'inclut pas ACCESS_WIDGET (grille V2 - pattern MarketController).
		Route::post('/', [WidgetController::class, 'list'])->name('widgets.list');

		// Grille tarifaire V2 : toutes les mutations/configs widgets = Premium+.
		Route::middleware('planaccess:ACCESS_WIDGET')->group(function () {

		// Création - POST /widgets/create/{type}
		// La liste blanche est DÉRIVÉE de User_widget::TYPES : un nouveau type de
		// widget est ainsi créable sans qu'on ait à penser à cette regex (le widget
		// « devis » y avait été oublié en 2026-08-05, création en 405 silencieux).
		Route::post('/create/{type}', [WidgetController::class, 'create'])
			->where('type', implode('|', \Rootsud\Core\Models\User_widget::TYPES))
			->name('widgets.create');

		// Legacy "type" routes (rétro-compat /widgets/estimation etc.) - gardées en premier
		// pour matcher quand l'user clique sur une "section type" depuis l'UI legacy.
		Route::post('/estimation', [WidgetController::class, 'configLegacy'])->defaults('type', 'estimation')->name('widgets.estimation');
		Route::post('/dpe',        [WidgetController::class, 'configLegacy'])->defaults('type', 'dpe')->name('widgets.dpe');
		Route::post('/opportunite',[WidgetController::class, 'configLegacy'])->defaults('type', 'opportunite')->name('widgets.opportunite');
		Route::put('/estimation',  [WidgetController::class, 'updateLegacy'])->defaults('type', 'estimation')->name('widgets.estimation.update');
		Route::put('/dpe',         [WidgetController::class, 'updateLegacy'])->defaults('type', 'dpe')->name('widgets.dpe.update');
		Route::put('/opportunite', [WidgetController::class, 'updateLegacy'])->defaults('type', 'opportunite')->name('widgets.opportunite.update');
		Route::post('/toggle-active', [WidgetController::class, 'toggleActiveLegacy'])->name('widgets.toggle');

		// Nouvelles routes par uuid (canon Sprint 2026-05-22)
		// La regex `{uuid}` accepte le format UUID v4 standard pour éviter les collisions.
		Route::post('/{uuid}',                [WidgetController::class, 'config'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.config');
		Route::put('/{uuid}',                 [WidgetController::class, 'update'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.update');
		Route::post('/{uuid}/toggle-active',  [WidgetController::class, 'toggleActive'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.toggleActive');
		Route::delete('/{uuid}',              [WidgetController::class, 'destroy'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.destroy');

		// Sprint 2026-05-23 - QR code du widget (SVG/PNG, téléchargeable ou inline).
		// Renvoie le QR de l'URL publique du widget (page hostée si mode `hosted`,
		// embed_url si mode `embed`). 422 `no_url` si pas d'URL disponible.
		Route::get('/{uuid}/qr/{format}', [WidgetController::class, 'qrCode'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->where('format', 'svg|png')
			->name('widgets.qr');

		// Aperçu plein écran du widget (bouton « Prévisualiser »), mode aperçu des
		// bundles : réponses fictives, aucun appel à l'API, aucun lead. Déclarée avant
		// le catch-all GET /widgets/{slug}/{map?} (un uuid y correspondrait).
		Route::get('/{uuid}/apercu', [WidgetController::class, 'preview'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.preview');

		// Sprint 2026-05-23 - Régénère le hosted_slug (casse les QR imprimés sur l'ancien).
		// À déclencher après confirm explicite côté UI.
		Route::post('/{uuid}/regenerate-slug', [WidgetController::class, 'regenerateSlug'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.regenerateSlug');

		// Sprint 2026-05-23 - Auto-save du name uniquement (PATCH partiel).
		Route::patch('/{uuid}/name', [WidgetController::class, 'updateName'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.updateName');

		// Sprint 2026-05-23 - Auto-save de embed_url (PATCH partiel + refresh QR au reload du fragment).
		Route::patch('/{uuid}/embed-url', [WidgetController::class, 'updateEmbedUrl'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.updateEmbedUrl');

		// Sprint 2026-05-23 - Auto-save du hosting_mode (avec génération automatique du slug si bascule vers hosted).
		Route::patch('/{uuid}/hosting-mode', [WidgetController::class, 'updateHostingMode'])
			->where('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
			->name('widgets.updateHostingMode');

		}); // fin planaccess:ACCESS_WIDGET
	});
	// Slug-aware avant la route générique. **Sprint 2026-05-23** : la regex
	// accepte aussi les `hosted_slug` (format `{brand}-{token8}`) - chaque widget a
	// un slug unique servant aussi d'identifiant dans l'URL admin (au lieu du type).
	// L'ancien matching `estimation|dpe|opportunite` reste pour rétro-compat des
	// URLs legacy.
	Route::get('/widgets/{slug}/{map?}', [LoadController::class, 'widgets'])
		->where('slug', 'estimation|dpe|opportunite|[a-z0-9][a-z0-9-]{5,79}')
		->name('home.widgets.show.slug');
	Route::get('/widgets/{map?}', [LoadController::class, 'widgets'])->name('home.widgets.show');

	// Phase 4 - N4 : Web Push pro (PWA browser)
	Route::post('/compte/push-subscribe', [\App\Http\Controllers\PushSubscriptionController::class, 'store'])->name('compte.push.subscribe');
	Route::post('/compte/push-unsubscribe', [\App\Http\Controllers\PushSubscriptionController::class, 'destroy'])->name('compte.push.unsubscribe');
	// Sprint 2026-05-21 - Sélecteur "casquette agence" multi-agences (sidebar switcher).
	// Membership vérifiée côté backend (anti-bypass via agency_id forgé en JS).
	Route::post('/compte/agence-active', [\App\Http\Controllers\AgencyController::class, 'setActiveAgency'])->name('agency.active.set');
	// Phase 2 - V3 : Registre des mandats (loi Hoguet)
	// Pattern split-panel : GET = layout complet (LoadController), POST = contenu panel (MandateRegistryController)
	// Sprint prestataire_services (2026-05-19) : registre Hoguet protégé par middleware hoguet
	Route::get('/registre-mandats/verify', [\App\Http\Controllers\MandateRegistryController::class, 'verify'])->middleware(['hoguet', 'planaccess:ACCESS_CRM'])->name('compte.mandats.verify');
	Route::get('/registre-mandats/export/{year}', [\App\Http\Controllers\MandateRegistryController::class, 'exportPdf'])->middleware(['hoguet', 'planaccess:ACCESS_CRM'])->name('compte.mandats.export');
	Route::post('/registre-mandats/{id}/rectify', [\App\Http\Controllers\MandateRegistryController::class, 'rectify'])->middleware(['hoguet', 'planaccess:ACCESS_CRM'])->name('compte.mandats.rectify');
	// Option A : abandon d'un mandat en attente de signature (n° conservé pour la continuité, hors chaîne)
	Route::post('/registre-mandats/{id}/sans-suite', [\App\Http\Controllers\MandateRegistryController::class, 'markSansSuite'])->middleware(['hoguet', 'planaccess:ACCESS_CRM'])->name('compte.mandats.sansSuite');
	Route::post('/registre-mandats', [\App\Http\Controllers\MandateRegistryController::class, 'index'])->middleware(['hoguet', 'planaccess:ACCESS_CRM'])->name('compte.mandats.list');
	Route::get('/registre-mandats/{map?}', [LoadController::class, 'mandats'])->middleware('hoguet')->name('compte.mandats.index');

	// Phase Agence : routes /parametres/agence/* + conformite + apparence + mode
	// IMPORTANT : declarees AVANT la catch-all `/parametres/{map?}` (ligne 705+).
	Route::prefix('parametres')->group(function () {
		// Equipe simple (Group_GROUP)
		Route::post('/equipe/creer', [\App\Http\Controllers\AgencyController::class, 'createGroup'])->middleware('planaccess:ACCESS_TEAM')->name('agency.group.create');
		// Transformer une équipe simple existante en agence (registre commun + paramètres partagés).
		Route::post('/equipe/{groupId}/transformer-agence', [\App\Http\Controllers\AgencyController::class, 'convertTeamToAgency'])->middleware('planaccess:ACCESS_TEAM')->name('agency.group.convert')->whereNumber('groupId');
		Route::get('/agence/{groupId}/membres', [\App\Http\Controllers\AgencyController::class, 'members'])->middleware('planaccess:ACCESS_TEAM')->name('agency.members')->whereNumber('groupId');
		Route::get('/agence/{groupId}/membres-datatable', [\App\Http\Controllers\AgencyController::class, 'membersDatatable'])->middleware('planaccess:ACCESS_TEAM')->name('agency.members.datatable')->whereNumber('groupId');

		// Equipe / Agence
		Route::get('/agence', [\App\Http\Controllers\AgencyController::class, 'show'])->name('agency.show');
		Route::post('/agence/creer', [\App\Http\Controllers\AgencyController::class, 'create'])->middleware('planaccess:ACCESS_TEAM')->name('agency.create');
		Route::post('/agence/{groupId}', [\App\Http\Controllers\AgencyController::class, 'update'])->middleware('planaccess:ACCESS_TEAM')->name('agency.update')->whereNumber('groupId');
		// Couverture des documents : geree via /parametres/style/cover (onglet Apparence, entity-aware).
		Route::post('/agence/{groupId}/inviter', [\App\Http\Controllers\AgencyController::class, 'invite'])->middleware('planaccess:ACCESS_TEAM')->name('agency.invite')->whereNumber('groupId');
		Route::post('/agence/{groupId}/inviter-csv/{role?}', [\App\Http\Controllers\AgencyController::class, 'inviteCsv'])->middleware('planaccess:ACCESS_TEAM')->name('agency.invite.csv')->whereNumber('groupId');
		Route::post('/agence/{groupId}/quitter', [\App\Http\Controllers\AgencyController::class, 'leave'])->name('agency.leave')->whereNumber('groupId');
		Route::post('/agence/{groupId}/transfer-ownership', [\App\Http\Controllers\AgencyController::class, 'transferOwnership'])->middleware('planaccess:ACCESS_TEAM')->name('agency.transfer-ownership')->whereNumber('groupId');
		Route::post('/agence/{groupId}/archiver', [\App\Http\Controllers\AgencyController::class, 'archive'])->middleware('planaccess:ACCESS_TEAM')->name('agency.archive')->whereNumber('groupId');
		Route::post('/agence/{groupId}/reactiver', [\App\Http\Controllers\AgencyController::class, 'reactivate'])->middleware('planaccess:ACCESS_TEAM')->name('agency.reactivate')->whereNumber('groupId');
		Route::post('/agence/membre/{groupUserId}/role', [\App\Http\Controllers\AgencyController::class, 'updateRole'])->middleware('planaccess:ACCESS_TEAM')->name('agency.member.role')->whereNumber('groupUserId');
		Route::delete('/agence/membre/{groupUserId}', [\App\Http\Controllers\AgencyController::class, 'removeMember'])->middleware('planaccess:ACCESS_TEAM')->name('agency.member.remove')->whereNumber('groupUserId');
		// Sprint 2026-05-20 - Rate limit sur les routes join (token long OU short_code 8 chars)
		// pour éviter le brute-force du code court (32^8 = ~1.1T combinaisons → ~300 000 ans avec ce rate limit).
		Route::get('/agence/rejoindre/{token}', [\App\Http\Controllers\AgencyController::class, 'showJoin'])->middleware('throttle:20,15')->name('agency.join.show');
		Route::post('/agence/rejoindre/{token}', [\App\Http\Controllers\AgencyController::class, 'acceptJoin'])->middleware('throttle:5,15')->name('agency.join.accept');
		// Lien d'invitation générique réutilisable (rôle par défaut configurable) - manager+
		Route::get('/agence/{groupId}/lien', [\App\Http\Controllers\AgencyController::class, 'inviteLink'])->middleware('planaccess:ACCESS_TEAM')->name('agency.invitelink.get')->whereNumber('groupId');
		Route::post('/agence/{groupId}/lien', [\App\Http\Controllers\AgencyController::class, 'updateInviteLink'])->middleware('planaccess:ACCESS_TEAM')->name('agency.invitelink.update')->whereNumber('groupId');
		Route::post('/agence/{groupId}/lien/renew', [\App\Http\Controllers\AgencyController::class, 'renewInviteLink'])->middleware('planaccess:ACCESS_TEAM')->name('agency.invitelink.renew')->whereNumber('groupId');

		// Mode immobilier (transitions)
		Route::post('/mode/check', [\App\Http\Controllers\AgencyController::class, 'checkModeTransition'])->name('agency.mode.check');
		Route::post('/mode', [\App\Http\Controllers\AgencyController::class, 'updateMode'])->name('agency.mode.update');

		// Conformite (mentions Hoguet)
		Route::post('/conformite', [\App\Http\Controllers\ConformiteController::class, 'update'])->name('compliance.update');
		Route::get('/conformite', [\App\Http\Controllers\ConformiteController::class, 'show'])->name('compliance.show');

		// Reset des personnalisations perso d'un onglet (membre d'agence -> herite a nouveau de l'agence)
		Route::delete('/apparence/perso', [\App\Http\Controllers\AppearanceController::class, 'resetPersonal'])->name('appearance.reset-perso');
		Route::delete('/preferences/perso', [\App\Http\Controllers\AgencyController::class, 'resetPersonalPreferences'])->name('preferences.reset-perso');
		Route::delete('/avis/perso', [\App\Http\Controllers\AgencyController::class, 'resetReviewPreferences'])->name('avis.reset-perso');
		Route::delete('/taches/perso', [\App\Http\Controllers\AgencyController::class, 'resetPersonalTaskSettings'])->name('taches.reset-perso');
		Route::delete('/honoraires/perso', [\App\Http\Controllers\FeeController::class, 'resetPersonal'])->name('honoraires.reset-perso');

		// Refonte agence v5 : preferences agence (groups.metadata.settings) - manager+
		Route::post('/agence/{groupId}/preferences', [\App\Http\Controllers\AgencyController::class, 'updatePreferences'])->middleware('planaccess:ACCESS_TEAM')->name('agency.preferences.update')->whereNumber('groupId');
	});

	// Phase Agence : le dashboard agence vit dans le hub /compte/equipe/{id} ; ce GET est un raccourci de redirection.
	Route::get('/agence/dashboard', [\App\Http\Controllers\AgencyDashboardController::class, 'show'])->name('agency.dashboard.show');

	Route::prefix('filter')->group(function () {
		Route::prefix('profil')->group(function () {
			Route::get('/', [ProfilController::class, 'list'])->name('filter.profil.list');
			Route::get('/modal-list', [ProfilController::class, 'modalList'])->name('filter.profil.modal-list');
			Route::post('/default/{id}', [ProfilController::class, 'default'])->name('filter.profil.default');
			Route::post('/edit/{id}', [ProfilController::class, 'edit'])->name('filter.profil.edit');
			Route::post('/{id}/{showFilter?}', [ProfilController::class, 'show'])->name('filter.profil.show');
			Route::post('/', [ProfilController::class, 'create'])->name('filter.profil.create');
			Route::delete('/{id}', [ProfilController::class, 'delete'])->name('filter.profil.delete');
		});

		Route::prefix('library')->group(function () {
			Route::get('/{id}', [FilterController::class, 'show'])->name('filter.library.show');
			Route::post('/orderize/{id}', [FilterController::class, 'orderize'])->name('filter.library.order');
			Route::post('/{id}', [FilterController::class, 'create'])->name('filter.library.create');
			Route::delete('/{id}', [FilterController::class, 'delete'])->name('filter.library.delete');
		});

		// Routes pour la nouvelle interface de filtres par catégories
		Route::get('/categories', [FilterController::class, 'categories'])->name('filter.categories');
		Route::post('/category/{categoryId}', [FilterController::class, 'categoryFilters'])->name('filter.category.filters');

		Route::prefix('search')->group(function () {
			Route::post('/', [SearchController::class, 'search'])->name('filter.search');
			Route::post('/count', [SearchController::class, 'count'])->name('filter.search.count');
			Route::post('/list', [SearchController::class, 'list'])->name('filter.search.list');
		});

		Route::post('/plu', [FilterController::class, 'showPlu'])->name('filter.plu.show');
		Route::get('/moral-person', [FilterController::class, 'moralPerson'])->name('filter.personmoral.show');
		Route::get('/co-owner', [FilterController::class, 'coOwner'])->name('filter.coowner.show');
		Route::get('/lead-label', [FilterController::class, 'leadLabel'])->name('filter.leadlabel.show');
		Route::get('/lead-status', [FilterController::class, 'leadStatus'])->name('filter.leadstatus.show');
		Route::get('/street-number', [FilterController::class, 'streetNumber'])->name('filter.streetnumber.show');
	});

	// Aperçus Premium avec les chiffres de la commune (levier E4, 2026-10-30) : totaux et un exemple seulement,
	// jamais le détail d'une fonction Premium, d'où l'absence de contrôle de forfait.
	Route::prefix('premium')->group(function () {
		// Liens de l'alerte de la commune pendant l'essai (levier E1, 2026-10-30) : clic noté puis redirection.
		Route::get('/alerte/{target}', [App\Http\Controllers\SectorAlertController::class, 'open'])->where('target', 'annonce|annonces|opportunites|commune')->name('premium.sector-alert');
		Route::get('/apercu/{feature}', [App\Http\Controllers\PremiumPreviewController::class, 'preview'])->where('feature', 'opportunity|market|dashboard')->name('premium.preview');
		Route::get('/communes', [App\Http\Controllers\PremiumPreviewController::class, 'communes'])->name('premium.communes');
		Route::post('/commune', [App\Http\Controllers\PremiumPreviewController::class, 'saveCommune'])->name('premium.commune');
		Route::post('/clic', [App\Http\Controllers\PremiumPreviewController::class, 'click'])->name('premium.click');
	});

	Route::prefix('opportunity')->group(function () {
		// index rend la page promo/teaser quand le plan n'inclut pas la variable.
		Route::get('/', [OpportunityController::class, 'index'])->name('opportunity.search.index');
		// Grille V2 : données opportunités gatées par le plan (Premium+).
		Route::middleware('planaccess:ACCESS_OPPORTUNITY')->group(function () {
			Route::post('/search', [OpportunityController::class, 'search'])->name('opportunity.search');
			Route::post('/count', [OpportunityController::class, 'count'])->name('opportunity.search.count');
			Route::post('/search/list', [OpportunityController::class, 'list'])->name('opportunity.search.list');
		});
	});

	Route::prefix('parcel')->group(function () {
		// Route::get('print/{id}', [ParcelController::class, 'print'])->name('parcel.print');
		Route::get('pdf-doc/{name}_{id}', [ParcelController::class, 'pdfDoc'])->name('parcel.pdfDoc');
		Route::get('pdf-doc-download/{name}_{id}', [ParcelController::class, 'pdfDocDownload'])->name('parcel.pdfDocDownload');
		Route::get('pdf-doc-ecadastre-empty', [ParcelController::class, 'pdfDocDownloadEmpty'])->middleware('planaccess:ACCESS_CADASTRAL_EXTRACT')->name('parcel.pdfDocDownloadEmpty');
		Route::post('pdf-doc-ecadastre/{id}', [ParcelController::class, 'pdfECadastralUrl'])->middleware('planaccess:ACCESS_CADASTRAL_EXTRACT')->name('parcel.pdfECadastralUrl');
		
		Route::post('/', [TabInfoController::class, 'load'])->name('parcel.card');
		Route::get('picture/{type}/{id}', [TabEnergieController::class, 'dpeges'])->name('parcel.dpeges');
		Route::get('preview/{id}', [ParcelController::class, 'preview'])->name('parcel.preview');
		Route::get('svg/{id}', [ParcelController::class, 'svg'])->name('parcel.svg');
		Route::post('strongweakpoint', [ParcelController::class, 'refreshStrongWeakPoint'])->name('parcel.refresh.strongweakpoint');
		Route::post('building/regenerate-svg', [ParcelController::class, 'regenerateBuildingSvg'])->name('parcel.building.regenerateSvg');
		Route::post('bati-detail', [ParcelController::class, 'batiDetailUpsert'])->name('parcel.bati-detail.upsert');

		// Liste des parcelles detenues par une personne morale / copropriete (modale onglet Ayants-droit)
		Route::post('owner-parcels', [TabOwnerController::class, 'ownerParcels'])->name('parcel.owner.parcels');


		// Route::post('/banner', [ParcelUserController::class, 'saveBanner'])->name('parcel.save.banner');
		Route::post('/color', [ParcelUserController::class, 'saveColor'])->name('parcel.save.color');
		Route::post('/cover', [ParcelUserController::class, 'saveCover'])->name('parcel.save.cover');
		Route::post('/batitype', [ParcelUserController::class, 'saveBatitype'])->name('parcel.save.batitype');
		Route::post('/surface', [ParcelUserController::class, 'saveSurface'])->name('parcel.save.surface');
		Route::post('/address', [ParcelUserController::class, 'createAddress'])->name('parcel.address.create');
		Route::post('/address/select', [ParcelUserController::class, 'selectAddress'])->name('parcel.address.select');
		Route::post('/address/refresh', [ParcelUserController::class, 'refreshAddress'])->name('parcel.address.refresh');
		Route::delete('/address/{id}', [ParcelUserController::class, 'deleteAddress'])->name('parcel.address.delete');

		Route::get('establishment/{id}/{catId}/{subCatId?}', [ParcelController::class, 'establishment'])->name('parcel.establishment');

		Route::prefix('tab')->group(function () {
			Route::post('/estimation', [TabEstimController::class, 'load'])->name('parcel.tab.estimation');
			// Onglet « Biens similaires » (2026-09-18) : comparables DVF / annonces de l'avis de
			// valeur. Meme droit que l'avis (ACCESS_ESTIMATE) : la page PDF « biens en vente »
			// montre deja ces annonces a tout plan avec estimation.
			Route::prefix('estimation/comparables')->middleware('planaccess:ACCESS_ESTIMATE')->group(function () {
				Route::post('/dvf', [TabComparablesController::class, 'dvf'])->name('parcel.tab.estimation.comparables.dvf');
				Route::post('/adverts', [TabComparablesController::class, 'adverts'])->name('parcel.tab.estimation.comparables.adverts');
				Route::post('/selected', [TabComparablesController::class, 'selected'])->name('parcel.tab.estimation.comparables.selected');
				Route::post('/state', [TabComparablesController::class, 'state'])->name('parcel.tab.estimation.comparables.state');
				Route::put('/', [TabComparablesController::class, 'save'])->name('parcel.tab.estimation.comparables.save');
				Route::delete('/', [TabComparablesController::class, 'reset'])->name('parcel.tab.estimation.comparables.reset');
			});
			Route::post('/foncier', [TabFoncierController::class, 'load'])->name('parcel.tab.foncier');
			Route::post('/owner', [TabOwnerController::class, 'load'])->name('parcel.tab.owner');
			Route::post('/energie', [TabEnergieController::class, 'load'])->name('parcel.tab.energie');
			Route::post('/plu', [TabPluController::class, 'load'])->name('parcel.tab.plu');
			Route::post('/permit', [TabPermitController::class, 'load'])->name('parcel.tab.permit');
			Route::post('/filiation', [TabFiliationController::class, 'load'])->name('parcel.tab.filiation');
			Route::get('/filiation/ancestors/{cadastreId}', [TabFiliationController::class, 'getAncestors'])->name('parcel.tab.filiation.ancestors');
			Route::post('/filiation/ancestors-batch', [TabFiliationController::class, 'getAncestorsBatch'])->name('parcel.tab.filiation.ancestors.batch');
			Route::get('/filiation/descendants/{cadastreId}', [TabFiliationController::class, 'getDescendants'])->name('parcel.tab.filiation.descendants');
			Route::post('/filiation/descendants-batch', [TabFiliationController::class, 'getDescendantsBatch'])->name('parcel.tab.filiation.descendants.batch');
			Route::post('/fiscalite', [TabFiscaliteController::class, 'load'])->name('parcel.tab.fiscalite');
			Route::post('/fiscalite/estimate', [TabFiscaliteController::class, 'estimate'])->name('parcel.tab.fiscalite.estimate');
			Route::post('/annonces', [TabAdvertController::class, 'load'])->middleware('planaccess:ACCESS_ADVERT')->name('parcel.tab.annonces');
			Route::post('/annonces/matching', [TabAdvertController::class, 'matching'])->middleware('planaccess:ACCESS_ADVERT')->name('parcel.tab.annonces.matching');
			Route::post('/leads', [TabLeadsController::class, 'load'])->name('parcel.tab.leads');
			Route::post('/swp', [SwpController::class, 'loadTab'])->name('parcel.tab.swp');
		});

		// Points clés (SWP = Strong/Weak Points)
		Route::prefix('swp')->group(function () {
			Route::post('/catalog', [SwpController::class, 'getCatalog'])->name('parcel.swp.catalog');
			Route::post('/save', [SwpController::class, 'saveOverrides'])->name('parcel.swp.save');
			Route::post('/custom', [SwpController::class, 'createCustom'])->name('parcel.swp.custom.create');
			Route::post('/custom/delete', [SwpController::class, 'deleteCustom'])->name('parcel.swp.custom.delete');
			Route::post('/quick-toggle', [SwpController::class, 'quickToggle'])->name('parcel.swp.quick-toggle');
		});

		// Notes de parcelle (timeline)
		Route::prefix('note')->group(function () {
			Route::post('/', [ParcelNoteController::class, 'store'])->name('parcel.note.store');
			Route::get('/refresh', [ParcelNoteController::class, 'refresh'])->name('parcel.note.refresh');
			Route::get('/{id}', [ParcelNoteController::class, 'show'])->name('parcel.note.show');
			Route::delete('/{id}', [ParcelNoteController::class, 'destroy'])->name('parcel.note.destroy');
		});

	});

	// Grille tarifaire V2 (2026-07-10) : chaque document PDF est gaté par sa
	// variable de plan (core.project_extras) via le middleware planaccess.
	Route::prefix('action')->group(function () {
		Route::post('csv', [ActionController::class, 'result_to_csv'])->name('action.result.csv');
		Route::post('pdf', [ActionController::class, 'result_to_pdf'])->name('action.result.pdf');
		Route::post('parcel/pdf', [ActionController::class, 'parcel_to_pdf'])->middleware('planaccess:ACCESS_PDF_PARCEL')->name('action.parcel.pdf');
		Route::post('parcel/avis', [ActionController::class, 'parcel_avis_valeur'])->middleware('planaccess:ACCESS_ESTIMATE')->name('action.parcel.avis');
		Route::post('parcel/estimation/save', [ActionController::class, 'parcel_save_estimation'])->middleware('planaccess:ACCESS_ESTIMATE')->name('action.parcel.estimation.save');
		Route::post('parcel/plans', [ActionController::class, 'parcel_plan_to_pdf'])->middleware('planaccess:ACCESS_PLAN_CADASTRAL')->name('action.parcel.plans');
		Route::post('parcel/plan-situation', [ActionController::class, 'parcel_plan_situation_to_pdf'])->middleware('planaccess:ACCESS_PLAN_SITUATION')->name('action.parcel.plan-situation');
		Route::post('compare/pdf', [ActionController::class, 'compare_to_pdf'])->middleware('planaccess:ACCESS_PDF_ADVERT_COMPARE')->name('action.compare.pdf');
		Route::post('advert/sheet/pdf', [ActionController::class, 'advert_to_pdf'])->middleware('planaccess:ACCESS_PDF_ADVERT_COMPARE')->name('action.advert.sheet.pdf');
		Route::post('leads/csv', [ActionController::class, 'leads_to_csv'])->middleware('planaccess:ACCESS_LEAD')->name('action.leads.csv');
		Route::post('leads/pdf', [ActionController::class, 'leads_to_pdf'])->middleware('planaccess:ACCESS_LEAD')->name('action.leads.pdf');
		// Route::get('test/avis', [ActionController::class, 'test_avis_valeur'])->name('action.parcel.avistest'); // TODO SUPPR
	});

	Route::prefix('advert')->group(function () {
		// Fragments UX : show/card rendent l'état « bloqué » (card_blocked) quand
		// le plan n'inclut pas ACCESS_ADVERT → volontairement hors middleware.
		Route::post('/show', [AdvertController::class, 'show'])->name('advert.show');
		Route::post('/show/{id}', [AdvertController::class, 'card'])->name('advert.card');

		// Grille V2 : routes data/actions annonces gatées par le plan.
		Route::middleware('planaccess:ACCESS_ADVERT')->group(function () {
			Route::post('/list', [AdvertController::class, 'list'])->name('advert.list');
			Route::post('/search', [AdvertController::class, 'search'])->name('advert.search');

			Route::post('/count', [AdvertController::class, 'count'])->name('advert.count');
			Route::get('/full/{id}', [AdvertController::class, 'full'])->name('advert.full');

			Route::post('/propertie/hidden/toggle', [AdvertController::class, 'toggleHidden'])->name('advert.propertie.hidden.toggle');

			Route::post('/compare/data', [AdvertController::class, 'compareData'])->name('advert.compare.data');
			// Mini-fiche d'une annonce localisee cliquee sur la carte (2026-10-03)
			Route::post('/map-card', [AdvertController::class, 'mapCard'])->name('advert.map.card');
		});
	});

	Route::prefix('contacts')->group(function () {
	    Route::get('/list', [ContactController::class, 'index'])->name('contacts.index');
	    Route::post('/list', [ContactController::class, 'index'])->name('contacts.list');
	    Route::get('/select/{type?}', [ContactController::class, 'select'])->name('contacts.select');
	    Route::get('/suggest', [ContactController::class, 'suggest'])->name('contacts.suggest'); // Sprint B - B7
	    Route::post('/', [ContactController::class, 'store'])->name('contacts.store');
	    Route::post('/leads/polygon', [ContactController::class, 'leadPolygon'])->name('contacts.leads.polygon');
	    Route::post('/{id}/create-lead', [ContactController::class, 'createLead'])->name('contacts.createLead');
	    Route::get('/{id}/leads', [ContactController::class, 'contactLeads'])->name('contacts.leads');
	    Route::post('/{id}', [ContactController::class, 'show'])->name('contacts.show');
	    Route::post('/update/{id}', [ContactController::class, 'update'])->name('contacts.update');
	    Route::delete('/{id}', [ContactController::class, 'destroy'])->name('contacts.destroy');
	    Route::get('/refresh-modal/{id?}', [ContactController::class, 'refreshModal'])->name('contacts.refreshModal');
	    Route::get('/{id}/json', [ContactController::class, 'getJson'])->name('contacts.json');
	    Route::get('/{id}/timeline', [ContactController::class, 'timeline'])->name('contacts.timeline');
	});

	Route::prefix('saved_search')->group(function () {
	    Route::get('/list/{type?}', [SavedSearchController::class, 'list'])->name('saved_search.list');
		Route::post('/save/{type}', [SavedSearchController::class, 'save'])->name('saved_search.save');
		// Route::post('/update/{id}', [SavedSearchController::class, 'update'])->name('saved_search.update');
		Route::delete('/delete/{id}', [SavedSearchController::class, 'delete'])->name('saved_search.delete');
	});

	Route::get('/estimate/link-search', [EstimationController::class, 'linkSearch'])->middleware('planaccess:ACCESS_ESTIMATE')->name('estimate.link.search');
	Route::get('estimation/{hash}/download', [ActionController::class, 'downloadEstimationPdf'])->name('estimation.download');

	// Grille V2 : moteur d'estimation + assets de l'avis de valeur (Basique+).
	Route::prefix('estimate')->middleware('planaccess:ACCESS_ESTIMATE')->group(function () {
		Route::post('/logo', [EstimationController::class, 'uploadLogo'])->name('estimate.logo.upload');
		Route::post('/picture', [EstimationController::class, 'uploadPicture'])->name('estimate.picture.upload');
		Route::delete('/logo', [EstimationController::class, 'deleteLogo'])->name('estimate.logo.delete');
		Route::delete('/picture', [EstimationController::class, 'deletePicture'])->name('estimate.picture.delete');
        Route::post('/generate-text', [EstimationController::class, 'generateEditText'])->name('estimate.save.generate.text');
        Route::post('/edit-text', [EstimationController::class, 'saveEditText'])->name('estimate.save.edit.text');
        Route::post('/mode-text', [EstimationController::class, 'saveModeText'])->name('estimate.save.mode.text');
        Route::post('/show-fees', [EstimationController::class, 'saveShowFees'])->middleware(['planaccess:ACCESS_CRM', 'planaccess:CRM_FEES'])->name('estimate.save.show.fees');
        Route::post('/avis-photo-toggle', [EstimationController::class, 'toggleAvisPhoto'])->name('estimate.avis.photo.toggle');
        Route::post('/avis-photo-reorder', [EstimationController::class, 'reorderAvisPhotos'])->name('estimate.avis.photo.reorder');
        Route::post('/lead-photo-toggle', [EstimationController::class, 'toggleLeadPhoto'])->name('estimate.lead.photo.toggle');
        Route::post('/lead-photo-reorder', [EstimationController::class, 'reorderLeadPhotos'])->name('estimate.lead.photo.reorder');
        Route::post('/photo/{id}/edit', [EstimationController::class, 'editPhoto'])->name('estimate.photo.edit');
        Route::post('/photo/{id}/revert', [EstimationController::class, 'revertPhoto'])->name('estimate.photo.revert');
        Route::put('/photo/{id}/caption', [EstimationController::class, 'updatePhotoCaption'])->name('estimate.photo.caption');
        Route::post('/photo/watermark/batch', [EstimationController::class, 'applyWatermarkBatch'])->name('estimate.photo.watermark.batch');
        Route::get('/watermark', [EstimationController::class, 'getWatermark'])->name('estimate.watermark.get');
        Route::put('/watermark', [EstimationController::class, 'saveWatermarkConfig'])->name('estimate.watermark.save');
        Route::post('/watermark/logo', [EstimationController::class, 'uploadWatermarkLogo'])->name('estimate.watermark.logo.upload');
        Route::delete('/watermark/logo', [EstimationController::class, 'deleteWatermarkLogo'])->name('estimate.watermark.logo.delete');
		Route::post('/', [EstimationController::class, 'calculate'])->name('estimate.calculate');
	});

	Route::prefix('parametres')->group(function () {
		Route::post('/', [SettingController::class, 'list'])->name('parametres.list');
		// Task templates CRUD
		// Google Calendar OAuth

		Route::prefix('agenda')->group(function () {
			Route::get('/google/redirect', [SettingController::class, 'googleRedirect'])->name('parametres.google.redirect');
			Route::get('/google/callback', [SettingController::class, 'googleCallback'])->name('parametres.google.callback');
			Route::post('/google/sync', [SettingController::class, 'googleSync'])->name('parametres.google.sync');
			Route::post('/google/disconnect', [SettingController::class, 'googleDisconnect'])->name('parametres.google.disconnect');
		});

		// Grille V2 : paramétrage pipeline/automatisations/export CRM = Premium+.
		Route::middleware('planaccess:ACCESS_PIPELINE')->group(function () {
		Route::post('/task-templates', [SettingController::class, 'storeTaskTemplate'])->name('parametres.task-templates.store');
		Route::put('/task-templates/{id}', [SettingController::class, 'updateTaskTemplate'])->name('parametres.task-templates.update');
		Route::delete('/task-templates/{id}', [SettingController::class, 'deleteTaskTemplate'])->name('parametres.task-templates.delete');
		Route::put('/task-templates/{id}/toggle', [SettingController::class, 'toggleTaskTemplate'])->name('parametres.task-templates.toggle');
		Route::put('/task-templates/{id}/auto-complete', [SettingController::class, 'setAutoCompleteOn'])->name('parametres.task-templates.auto-complete');

		// Pipeline statuses CRUD
		Route::post('/pipeline/statuses', [SettingController::class, 'storeStatus'])->name('parametres.pipeline.statuses.store');
		Route::put('/pipeline/statuses/{id}', [SettingController::class, 'updateStatus'])->name('parametres.pipeline.statuses.update');
		Route::delete('/pipeline/statuses/{id}', [SettingController::class, 'archiveStatus'])->name('parametres.pipeline.statuses.archive');
		Route::post('/pipeline/statuses/reorder', [SettingController::class, 'reorderStatuses'])->name('parametres.pipeline.statuses.reorder');

		// Pipeline triggers CRUD
		Route::post('/pipeline/triggers', [SettingController::class, 'storeTrigger'])->name('parametres.pipeline.triggers.store');
		Route::put('/pipeline/triggers/{id}', [SettingController::class, 'updateTrigger'])->name('parametres.pipeline.triggers.update');
		Route::delete('/pipeline/triggers/{id}', [SettingController::class, 'deleteTrigger'])->name('parametres.pipeline.triggers.delete');

		// Pipeline webhooks (CRM/Zapier/HubSpot) - Sprint 2026-05-21
		Route::post('/pipeline/webhooks', [SettingController::class, 'storeWebhook'])->name('parametres.pipeline.webhooks.store');
		Route::put('/pipeline/webhooks/{id}', [SettingController::class, 'updateWebhook'])->name('parametres.pipeline.webhooks.update');
		Route::put('/pipeline/webhooks/{id}/toggle', [SettingController::class, 'toggleWebhook'])->name('parametres.pipeline.webhooks.toggle');
		Route::delete('/pipeline/webhooks/{id}', [SettingController::class, 'deleteWebhook'])->name('parametres.pipeline.webhooks.delete');
		Route::post('/pipeline/webhooks/{id}/test', [SettingController::class, 'testWebhook'])->name('parametres.pipeline.webhooks.test');
		// 2026-10-13 : rattacher à une étape une intégration restée sur une étape archivée.
		Route::put('/pipeline/webhooks/{id}/attach', [SettingController::class, 'attachWebhook'])->name('parametres.pipeline.webhooks.attach');

		// Pipeline preset wizard ("Configurer en 3 minutes")
		Route::post('/pipeline/preset-impact', [SettingController::class, 'presetImpact'])->name('parametres.pipeline.preset-impact');
		Route::post('/pipeline/apply-preset', [SettingController::class, 'applyPipelinePreset'])->name('parametres.pipeline.apply-preset');
		}); // fin planaccess:ACCESS_PIPELINE

		// Horaires d'ouverture par jour + différenciation portail client (Sprint 2026-05-21)
		Route::post('/calendar-hours', [SettingController::class, 'saveCalendarHours'])->name('parametres.calendar-hours');

		// [Refonte 2026-05-19] Routes Sprint B - S1 quick-actions SUPPRIMÉES :
		// le paramétrage des actions rapides par étape est retiré. Les méthodes
		// listStatusActions/storeStatusAction/.../reorderStatusActions du SettingController
		// restent en place côté code mais ne sont plus exposées par aucune route.

		// Sprint C 3.1 - Templates Modele CRUD (éditeur WYSIWYG Quill)
		Route::middleware('planaccess:ACCESS_CRM')->group(function () { // grille V2 : templates docs/emails = CRM
		Route::get('/templates',           [SettingController::class, 'listTemplates'])->name('parametres.templates.list');
		Route::get('/templates/{id}',      [SettingController::class, 'showTemplate'])->name('parametres.templates.show');
		Route::post('/templates',          [SettingController::class, 'storeTemplate'])->name('parametres.templates.store');
		Route::put('/templates/{id}',      [SettingController::class, 'updateTemplate'])->name('parametres.templates.update');
		Route::post('/templates/{id}/duplicate', [SettingController::class, 'duplicateTemplate'])->name('parametres.templates.duplicate');
		Route::post('/templates/preview',  [SettingController::class, 'previewTemplate'])->name('parametres.templates.preview');
		Route::post('/templates/preview-pdf', [SettingController::class, 'previewTemplatePdf'])->name('parametres.templates.preview-pdf');
		Route::delete('/templates/{id}',   [SettingController::class, 'deleteTemplate'])->name('parametres.templates.delete');

		// Sprint C 3.3 - Quick replies (phrases types) CRUD
		Route::get('/quick-replies',         [SettingController::class, 'listQuickReplies'])->name('parametres.quick-replies.list');
		Route::post('/quick-replies',        [SettingController::class, 'storeQuickReply'])->name('parametres.quick-replies.store');
		Route::put('/quick-replies/{id}',    [SettingController::class, 'updateQuickReply'])->name('parametres.quick-replies.update');
		Route::delete('/quick-replies/{id}', [SettingController::class, 'deleteQuickReply'])->name('parametres.quick-replies.delete');
		}); // fin planaccess:ACCESS_CRM (templates)

		Route::post('/colors', [SettingController::class, 'saveColors'])->name('parametres.colors.save');
		// Sauvegarde d'un reglage style_* entity-aware (perso ou agence selon ?entity=)
		Route::post('/style/setting', [SettingController::class, 'styleSetting'])->name('parametres.style.setting');
		// Couverture des documents (mandats/compromis/bail) entity-aware (perso ou agence selon entity=)
		Route::post('/style/cover', [SettingController::class, 'uploadStyleCover'])->name('parametres.style.cover.upload');
		Route::delete('/style/cover', [SettingController::class, 'clearStyleCover'])->name('parametres.style.cover.clear');
		Route::get('/theme-svg/{theme}', [SettingController::class, 'themeSvg'])->name('parametres.theme.svg');

		// Honoraires (fees) - port captimmo
		Route::prefix('honoraires')->middleware(['planaccess:ACCESS_CRM', 'planaccess:CRM_FEES'])->group(function () { // grille V2 + variable Honoraires (2026-09-17)
			Route::get('/list',                      [FeeController::class, 'list'])             ->name('parametres.honoraires.list');
			Route::get('/fee-form/{structureId}',    [FeeController::class, 'feeForm'])          ->name('parametres.honoraires.feeForm');
			Route::get('/fee/{feeId}',               [FeeController::class, 'getFee'])           ->name('parametres.honoraires.getFee');
			Route::post('/fee',                      [FeeController::class, 'feeUpdate'])        ->name('parametres.honoraires.feeUpdate');
			Route::delete('/fee/{feeId}',            [FeeController::class, 'feeDelete'])        ->name('parametres.honoraires.feeDelete');
			Route::post('/fee-template',             [FeeController::class, 'feeTemplate'])      ->name('parametres.honoraires.feeTemplate');
			Route::post('/fee-template-change',      [FeeController::class, 'feeTemplateChange'])->name('parametres.honoraires.feeTemplateChange');
			Route::post('/preview',                  [FeeController::class, 'calculatePreview']) ->name('parametres.honoraires.preview');
		});
	});

	// Route pour récupérer la liste des secteurs de l'utilisateur (pour actualiser les 3 vues).
	// ?entity=group:N (manager) → on liste les secteurs PARTAGÉS de l'agence N (scope=group) ;
	// sinon → périmètre effectif (perso ∪ agence) comme avant.
	Route::get('/user/sectors-list/{id?}', function ($id = null) {
		$user = auth()->user();
		$secEntity = request()->input('entity');
		$secGroupId = null;
		if ($secEntity && preg_match('/^group:(\d+)$/', (string) $secEntity, $secM) && $user->canManageAgency((int) $secM[1])) {
			$secGroupId = (int) $secM[1];
		}
		$sectorQuery = function () use ($user, $secGroupId) {
			return $secGroupId ? \Rootsud\City\User\Models\Sector::ofAgency($secGroupId) : $user->effectiveSectors();
		};

		if ($id) {
			// Retourner un secteur spécifique avec ses détails et ses éléments
			$sector = $sectorQuery()->with('map')->where('id', $id)->first();
			if (!$sector) {
				return response()->json(['error' => 'Secteur non trouvé'], 404);
			}

			// Grouper les éléments par type pour les requêtes bulk
			$municipalityCodes = [];
			$districtCodes = [];
			$sectionCodes = [];
			$departmentCodes = [];

			foreach ($sector->map as $map) {
				$code = $map->sector_element_id;
				if (!$code) continue;

				switch ($map->sector_element_type) {
					case 'Rootsud\City\Models\France\Fr_department':
						$departmentCodes[] = $code;
						break;
					case 'Rootsud\City\Models\France\Fr_insee':
						$municipalityCodes[] = $code;
						break;
					case 'Rootsud\City\Models\France\Fr_district':
						$districtCodes[] = $code;
						break;
					case 'Rootsud\City\Models\France\Fr_section':
						$sectionCodes[] = $code;
						break;
				}
			}

			$elements = [];

			// Charger les communes en bulk
			if (!empty($municipalityCodes)) {
				$municipalities = \Rootsud\City\Models\France\Fr_insee::whereIn('code_insee', $municipalityCodes)
					->with('code_postal.code_postal')
					->get();

				$elements['municipality'] = $municipalities->map(function($m) {
					$postalCodes = $m->code_postal->map(function($cp) {
						return $cp->code_postal->code_postal ?? null;
					})->filter()->unique()->implode(' / ');
					return [
						'code' => $m->code_insee,
						'title' => $m->nom_commune . ($postalCodes ? ' ' . $postalCodes : '')
					];
				})->values()->toArray();
			}

			// Charger les quartiers en bulk
			if (!empty($districtCodes)) {
				// Les codes sont au format "INSEE/IRIS"
				$districtConditions = collect($districtCodes)->map(function($code) {
					$parts = explode('/', $code);
					return ['code_insee' => $parts[0], 'iris' => $parts[1] ?? ''];
				});

				$districts = \Rootsud\City\Models\France\Fr_district::select('fr_districts.*', 'fr_insees.code_insee', 'fr_insees.nom_commune')
					->leftJoin('fr_insees', 'fr_insees.id', '=', 'fr_districts.insee_id')
					->where(function($query) use ($districtConditions) {
						foreach ($districtConditions as $cond) {
							$query->orWhere(function($q) use ($cond) {
								$q->where('fr_insees.code_insee', $cond['code_insee'])
								  ->where('fr_districts.iris', $cond['iris']);
							});
						}
					})
					->get();

				if (!isset($elements['sectiondistrict'])) {
					$elements['sectiondistrict'] = [];
				}

				foreach ($districts as $d) {
					// Format: "Quartier (Ville)" si différent, sinon juste "Quartier"
					$districtName = $d->name ?: ('IRIS ' . $d->iris);
					$title = $districtName;
					if ($d->nom_commune && $d->nom_commune !== $districtName) {
						$title = $districtName . ' (' . $d->nom_commune . ')';
					}
					$elements['sectiondistrict'][] = [
						'code' => $d->code_insee . '/' . $d->iris,
						'title' => $title,
						'layer' => 'district'
					];
				}
			}

			// Charger les sections en bulk
			if (!empty($sectionCodes)) {
				// Les codes sont au format "INSEE/SECTION"
				$sectionConditions = collect($sectionCodes)->map(function($code) {
					$parts = explode('/', $code);
					return ['code_insee' => $parts[0], 'code_section' => $parts[1] ?? ''];
				});

				$sections = \Rootsud\City\Models\France\Fr_section::select('fr_sections.*', 'fr_insees.code_insee', 'fr_insees.nom_commune')
					->leftJoin('fr_insees', 'fr_insees.id', '=', 'fr_sections.insee_id')
					->where(function($query) use ($sectionConditions) {
						foreach ($sectionConditions as $cond) {
							$query->orWhere(function($q) use ($cond) {
								$q->where('fr_insees.code_insee', $cond['code_insee'])
								  ->where('fr_sections.code_section', $cond['code_section']);
							});
						}
					})
					->get();

				if (!isset($elements['sectiondistrict'])) {
					$elements['sectiondistrict'] = [];
				}

				foreach ($sections as $s) {
					$elements['sectiondistrict'][] = [
						'code' => $s->code_insee . '/' . $s->code_section,
						'title' => $s->nom_commune . ' - Section ' . $s->code_section,
						'layer' => 'section'
					];
				}
			}

			// Charger les départements en bulk
			if (!empty($departmentCodes)) {
				$departments = \Rootsud\City\Models\France\Fr_department::whereIn('code_department', $departmentCodes)->get();

				$elements['department'] = $departments->map(function($d) {
					return [
						'code' => $d->code_department,
						'title' => $d->nom_department . ' (' . $d->code_department . ')'
					];
				})->values()->toArray();
			}

			return response()->json([
				'sector' => [
					'id' => $sector->id,
					'name' => $sector->name,
					'created_at' => $sector->created_at
				],
				'elements' => $elements
			]);
		}

		// Retourner tous les secteurs avec résumé des éléments par type
		// (périmètre agence si ?entity=group:N, sinon effectif perso ∪ agence)
		$sectors = $sectorQuery()->withCount('map')->with('map:id,sector_id,sector_element_type')->get();
		$result = $sectors->map(function($s) use ($user) {
			$types = $s->map->groupBy('sector_element_type')->map->count();
			$sScope = $s->scope ?? 'user';
			return [
				'id' => $s->id,
				'name' => $s->name,
				'created_at' => $s->created_at,
				'map_count' => $s->map_count,
				// Périmètre : 'user' (perso) ou 'group' (secteur partagé d'une agence).
				'scope' => $sScope,
				'group_id' => $s->group_id,
				// editable : secteur perso, OU secteur d'agence si l'user gère cette agence (manager/owner).
				'editable' => ($sScope !== 'group')
					|| ($s->group_id && method_exists($user, 'canManageAgency') && $user->canManageAgency((int) $s->group_id)),
				'element_types' => [
					'municipality' => $types->get('Rootsud\City\Models\France\Fr_insee', 0),
					'district' => $types->get('Rootsud\City\Models\France\Fr_district', 0),
					'section' => $types->get('Rootsud\City\Models\France\Fr_section', 0),
					'department' => $types->get('Rootsud\City\Models\France\Fr_department', 0),
				],
			];
		});
		return response()->json(['sectors' => $result]);
	})->name('user.sectors.list');
	
	Route::prefix('scrap')->group(function () {
		Route::get('logo/{type?}/{withCustom?}', [ScrapController::class, 'scrapLogo'])->name('scrap.logo');
		Route::get('parcel/plans/{id}/{scaleRatio?}/{dpi?}/{buildingIndex?}/{buildingId?}/{orientation?}', [ScrapController::class, 'scrapParcelPlan'])->name('scrap.parcel.plan');
		Route::get('parcel/preview/{id}', [ScrapController::class, 'scrapParcelPreview'])->name('scrap.parcel.preview');
		Route::get('parcel/preview3D/{id}', [ScrapController::class, 'scrapParcelPreview3D'])->name('scrap.parcel.preview3d');
		Route::get('parcel/map/{id}', [ScrapController::class, 'scrapParcelMap'])->name('scrap.parcel.map');
		Route::get('parcel/around/{id}', [ScrapController::class, 'scrapParcelAround'])->name('scrap.parcel.around');
		Route::get('parcel/satellite/{id}', [ScrapController::class, 'scrapParcelSatellite'])->name('scrap.parcel.satellite');
		Route::get('parcel/couche/{type}/{id}', [ScrapController::class, 'scrapParcelOverlayer'])->name('scrap.parcel.overlayer');
		Route::get('parcel/dvf/{id}/{dvfIds?}', [ScrapController::class, 'scrapParcelDvf'])->name('scrap.parcel.dvf');
		Route::get('parcel/propertie/{id}/{propertieIds?}', [ScrapController::class, 'scrapParcelPropertie'])->name('scrap.parcel.propertie');
		Route::get('parcel/stats/price-evol/{id}/{source}/{color?}', [ScrapController::class, 'scrapParcelStatsPriceEvol'])->name('scrap.parcel.priceevol');
		Route::get('compare/radar', [ScrapController::class, 'scrapCompareScoreRadar'])->name('scrap.compare.radar');
	});

	Route::post('/show-filter', [ApplicationController::class, 'showFilter'])->name('application.show.filter');

	// Route pour annuler les requêtes en cours (léger, pas de requête DB)
	Route::post('/request/cancel', function (\Illuminate\Http\Request $request) {
		$token = $request->input('request_id');
		if ($token && auth()->check()) {
			$cancelled = \Rootsud\Core\Services\CancellationTokenService::cancel($token, auth()->id());
			return response()->json(['cancelled' => $cancelled]);
		}
		return response()->json(['cancelled' => false]);
	})->name('request.cancel');

	// Documents GED - grille V2 : module Premium+ (ACCESS_GED).
	// Hors gate : /check-tasks (polling de génération des PDF avis - requis en
	// Basique) et GET /defaults (liste des annexes jointes aux avis de valeur).
	Route::prefix('documents')->group(function () {
		Route::post('/check-tasks', [DocumentController::class, 'checkTasks'])->name('documents.check.tasks');
		Route::get('/defaults', [DocumentController::class, 'defaults'])->name('documents.defaults');

		Route::middleware('planaccess:ACCESS_GED')->group(function () {
			Route::get('/recent', [DocumentController::class, 'recent'])->name('documents.recent');
			Route::get('/parcel/{cadastreId}', [DocumentController::class, 'parcelDocuments'])->name('documents.parcel');
			Route::get('/lead/{leadId}', [DocumentController::class, 'leadDocuments'])->name('documents.lead');
			Route::get('/contact/{contactId}', [DocumentController::class, 'contactDocuments'])->name('documents.contact');
			Route::post('/upload', [DocumentController::class, 'upload'])->name('documents.upload');
			Route::get('/download/{id}', [DocumentController::class, 'download'])->name('documents.download');
			Route::get('/{id}/adverts', [DocumentController::class, 'comparatifAdverts'])->name('documents.adverts');
			Route::post('/{id}/auto-attach', [DocumentController::class, 'toggleAutoAttach'])->name('documents.auto-attach');
			Route::post('/{id}/portal-visible', [DocumentController::class, 'togglePortalVisible'])->name('documents.portal-visible');
			Route::put('/{id}', [DocumentController::class, 'update'])->name('documents.update');
			Route::delete('/{id}', [DocumentController::class, 'destroy'])->name('documents.destroy');
			Route::post('/defaults/upload', [DocumentController::class, 'uploadDefault'])->name('documents.defaults.upload');
			Route::post('/defaults/reorder', [DocumentController::class, 'reorderDefaults'])->name('documents.defaults.reorder');
		});
	});

	// Dashboard (plein écran)
	Route::get('/dashboard', [LoadController::class, 'dashboard'])->name('home.dashboard.show');
	Route::post('/dashboard', [DashboardController::class, 'index'])->name('dashboard.index');
	Route::get('/dashboard/first-steps', [DashboardController::class, 'firstSteps'])->name('dashboard.firstSteps');
	Route::get('/dashboard/chart/{type}', [DashboardController::class, 'chart'])->name('dashboard.chart');
	Route::get('/dashboard/activity', [DashboardController::class, 'activity'])->name('dashboard.activity');
	// Notifications drawer (2026-06-25) - cloche notifs dans le design du tiroir portail.
	Route::get('/dashboard/notifications', [DashboardController::class, 'notificationsFeed'])->name('dashboard.notifications_feed');
	Route::post('/dashboard/notifications/read', [DashboardController::class, 'markNotificationsRead'])->name('dashboard.notifications_feed.read');

	// Onboarding - popup d'accueil (stepper 3 étapes). 2 routes utilitaires ;
	// le mode + le pipeline réutilisent /parametres/mode et /parametres/pipeline/apply-preset.
	Route::get('/onboarding/scenarios', [\App\Http\Controllers\OnboardingController::class, 'scenarios'])->name('onboarding.scenarios');
	Route::get('/onboarding/convertible-teams', [\App\Http\Controllers\OnboardingController::class, 'convertibleTeams'])->name('onboarding.convertible-teams');
	Route::post('/onboarding/complete', [\App\Http\Controllers\OnboardingController::class, 'complete'])->name('onboarding.complete');

	// Acquisition - specific GET routes BEFORE catch-all {map?}
	// Grille V2 : module Leads dès Basique (kanban/statuts/contacts) ; les
	// actions CRM/équipe/portail portent un middleware additionnel ci-dessous.
	// Logement d'un appartement dans son immeuble (fiche annonce) : lecture des DPE, reservee a l'acces
	// Annonces et non aux leads (sinon 432 et modale plan a chaque fiche pour un compte sans Leads).
	Route::post('/acquisition/advert/apartment-hints', [AcquisitionController::class, 'advertApartmentHints'])->middleware('planaccess:ACCESS_ADVERT')->name('acquisition.advertApartmentHints');
	Route::middleware('planaccess:ACCESS_LEAD')->group(function () {
	Route::get('/acquisition/labels', [AcquisitionController::class, 'labels'])->name('acquisition.labels');
	// 2026-09-23 : export CSV du carnet des acquereurs (quota EXPORT_RESULT, comme l'export de leads).
	Route::get('/acquisition/buyers/export', [AcquisitionController::class, 'buyersExport'])->name('acquisition.buyersExport');
	Route::get('/acquisition/lead/prefill', [AcquisitionController::class, 'leadPrefill'])->name('acquisition.leadPrefill');
	Route::get('/acquisition/lead/{id}', [AcquisitionController::class, 'show'])->name('acquisition.show');
	Route::get('/acquisition/lead/{id}/refresh', [AcquisitionController::class, 'refreshLeadCard'])->name('acquisition.refreshLeadCard');
	Route::get('/acquisition/lead/{id}/portal-card', [AcquisitionController::class, 'portalCard'])->middleware('planaccess:ACCESS_PORTAL')->name('acquisition.portalCard');
	Route::get('/acquisition/lead/{id}/fragments', [AcquisitionController::class, 'leadFragments'])->name('acquisition.leadFragments');
	Route::get('/acquisition/lead/{id}/matches', [AcquisitionController::class, 'leadMatches'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.leadMatches');
	Route::get('/acquisition/lead/{id}/located-adverts', [AcquisitionController::class, 'leadLocatedAdverts'])->name('acquisition.leadLocatedAdverts');
	Route::get('/acquisition/lead/{id}/compatible-leads', [AcquisitionController::class, 'compatibleLeads'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.compatibleLeads');
	Route::get('/acquisition/parcel-map-context/{id}', [AcquisitionController::class, 'parcelMapContext'])->name('acquisition.parcelMapContext');
	Route::post('/acquisition/lead/{id}/propose-match', [AcquisitionController::class, 'proposeMatch'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.proposeMatch');
	Route::delete('/acquisition/lead/{id}/propose-match/{matchId}', [AcquisitionController::class, 'unproposeMatch'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.unproposeMatch');
	Route::get('/acquisition/lead/{id}/history', [AcquisitionController::class, 'history'])->name('acquisition.history');
	Route::get('/acquisition/lead/{id}/contacts', [AcquisitionController::class, 'getContacts'])->name('acquisition.getContacts');
	Route::get('/acquisition/lead/{id}/contact-options', [AcquisitionController::class, 'contactOptions'])->name('acquisition.contactOptions');
	// Option C - candidats de template (fork(s) + système) pour le _template_picker des modales.
	Route::get('/acquisition/lead/{id}/templates', [AcquisitionController::class, 'leadTemplates'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.leadTemplates');
	Route::get('/acquisition/lead/{id}/visit-properties', [AcquisitionController::class, 'getVisitProperties'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.getVisitProperties');
	Route::get('/acquisition/lead/{id}/matching-score', [AcquisitionController::class, 'getMatchingScore'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.getMatchingScore');
	Route::get('/acquisition/lead/{id}/map-context', [AcquisitionController::class, 'getMapContext'])->name('acquisition.getMapContext');
	Route::get('/acquisition/parcelLeads/{cadastreId}', [AcquisitionController::class, 'parcelLeads'])->name('acquisition.parcelLeads');
	Route::get('/acquisition/advertStatus/{flxId}', [AcquisitionController::class, 'advertStatus'])->name('acquisition.advertStatus');
	Route::post('/acquisition/advert/location-states', [AcquisitionController::class, 'advertLocationStates'])->name('acquisition.advertLocationStates');
	Route::get('/acquisition/advert/located-bounds/{flxId}', [AcquisitionController::class, 'advertLocatedBounds'])->name('acquisition.advertLocatedBounds');
	Route::get('/acquisition/advert/located-parcels/{flxId}', [AcquisitionController::class, 'advertLocatedParcels'])->name('acquisition.advertLocatedParcels');
	Route::delete('/acquisition/unlocateAdvert/{id}', [AcquisitionController::class, 'unlocateAdvert'])->name('acquisition.unlocateAdvert');
	Route::get('/acquisition/lead/{id}/fees',          [FeeController::class, 'getLeadFeeDetail'])    ->name('acquisition.lead.fees');
	Route::post('/acquisition/lead/{id}/manual-fee',     [FeeController::class, 'saveLeadManualFee'])    ->middleware(['planaccess:ACCESS_CRM', 'planaccess:CRM_FEES'])->name('acquisition.lead.manualFee');
	Route::delete('/acquisition/lead/{id}/manual-fee',   [FeeController::class, 'deleteLeadManualFee'])  ->middleware(['planaccess:ACCESS_CRM', 'planaccess:CRM_FEES'])->name('acquisition.lead.manualFee.delete');
	Route::post('/acquisition/lead/{id}/recompute-fees', [FeeController::class, 'recomputeLeadFees'])    ->middleware(['planaccess:ACCESS_CRM', 'planaccess:CRM_FEES'])->name('acquisition.lead.recomputeFees');
	Route::get('/acquisition/lead/{id}/estimation-data',  [FeeController::class, 'getLeadEstimationData']) ->name('acquisition.lead.estimationData');
	// Panel d'une estimation autonome (non liée à un lead) - modale ouverte depuis l'omnibar.
	Route::get('/acquisition/estimation/{id}/panel', [AcquisitionController::class, 'estimationPanel'])->where('id', '[0-9]+')->name('acquisition.estimationPanel');
	Route::get('/acquisition/kpis', [DashboardController::class, 'kpis'])->name('acquisition.kpis');
	Route::get('/acquisition/templates', [AcquisitionController::class, 'listTemplates'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.templates');
	Route::get('/acquisition/team-members', [AcquisitionController::class, 'teamMembers'])->middleware('planaccess:ACCESS_TEAM')->name('acquisition.teamMembers');
	Route::get('/acquisition/proposal/{id}/download', [AcquisitionController::class, 'downloadProposal'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.downloadProposal');

	}); // fin planaccess:ACCESS_LEAD (bloc GET)

	// === Portail Client (côté pro, lifecycle des tokens) - DOIVENT être avant /acquisition/{map?} catch-all ===
	Route::middleware('planaccess:ACCESS_PORTAL')->group(function () { // grille V2 : portail = CRM
	Route::get('/acquisition/lead/{id}/portal',         [\App\Http\Controllers\ClientPortalController::class, 'index'])->name('portal.index');
	Route::post('/acquisition/lead/{id}/portal',        [\App\Http\Controllers\ClientPortalController::class, 'store'])->name('portal.store');
	Route::put('/portal/{tokenId}',                     [\App\Http\Controllers\ClientPortalController::class, 'update'])->name('portal.update');
	Route::post('/portal/{tokenId}/regenerate',         [\App\Http\Controllers\ClientPortalController::class, 'regenerate'])->name('portal.regenerate');
	Route::delete('/portal/{tokenId}',                  [\App\Http\Controllers\ClientPortalController::class, 'revoke'])->name('portal.revoke');
	Route::get('/portal/{tokenId}/visits',              [\App\Http\Controllers\ClientPortalController::class, 'visits'])->name('portal.visits');
	Route::get('/portal/{tokenId}/qr',                  [\App\Http\Controllers\ClientPortalController::class, 'qrCode'])->name('portal.qr');
	// Sprint 1 Bloc 2 - envoi du lien portail au client (email + SMS)
	Route::get('/portal/{tokenId}/preview-send',        [\App\Http\Controllers\ClientPortalController::class, 'previewSend'])->name('portal.previewSend');
	Route::post('/portal/{tokenId}/send',               [\App\Http\Controllers\ClientPortalController::class, 'sendToClient'])->name('portal.send');

	}); // fin planaccess:ACCESS_PORTAL

	Route::get('/acquisition/kpi', [LoadController::class, 'acquisition'])->name('home.acquisition.kpi');
	Route::get('/acquisition/{id}/{map?}', [LoadController::class, 'acquisition'])->where('id', '[0-9]+')->name('home.acquisition.lead.show');
	Route::get('/acquisition/{map?}', [LoadController::class, 'acquisition'])->name('home.acquisition.show');

	// Acquisition - POST/PUT routes
	// Le fragment liste est HORS du groupe planaccess : sans ACCESS_LEAD le
	// contrôleur rend l'écran teaser upgrade (pattern agenda) au lieu d'un 432.
	Route::post('/acquisition', [AcquisitionController::class, 'index'])->name('acquisition.index');
	// Pastille « Personnaliser mon pipeline » de l'en-tête des leads : ne plus l'afficher (2026-10-30).
	Route::post('/acquisition/pipeline-hint/seen', [App\Http\Controllers\PipelineHintController::class, 'seen'])->name('acquisition.pipelineHint.seen');
	Route::middleware('planaccess:ACCESS_LEAD')->group(function () { // grille V2
	Route::post('/acquisition/kpi', [DashboardController::class, 'kpiPage'])->name('acquisition.kpi');
	Route::post('/acquisition/kanban', [AcquisitionController::class, 'kanban'])->name('acquisition.kanban');
	Route::post('/acquisition/kanban/more', [AcquisitionController::class, 'kanbanMore'])->name('acquisition.kanbanMore');
	Route::post('/acquisition/kanban/column-mode', [AcquisitionController::class, 'setKanbanColumnMode'])->name('acquisition.kanban.columnMode');
	// 2026-09-23 : carnet des acquereurs (3e vue : tableau des acheteurs et locataires).
	Route::post('/acquisition/buyers', [AcquisitionController::class, 'buyersTable'])->name('acquisition.buyersTable');
	Route::post('/acquisition/lead/create', [AcquisitionController::class, 'createLead'])->name('acquisition.createLead');
	Route::put('/acquisition/lead/{id}/status', [AcquisitionController::class, 'updateStatus'])->name('acquisition.updateStatus');
	Route::put('/acquisition/lead/{id}/reorder', [AcquisitionController::class, 'reorderLead'])->name('acquisition.reorderLead');
	Route::put('/acquisition/lead/{id}/parcel', [AcquisitionController::class, 'updateParcel'])->name('acquisition.updateParcel');
	Route::post('/acquisition/lead/{id}/activity', [AcquisitionController::class, 'addActivity'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.addActivity');
	Route::put('/acquisition/lead/activity/{id}', [AcquisitionController::class, 'updateActivity'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.updateActivity');
	Route::put('/acquisition/lead/activity/{id}/pin', [AcquisitionController::class, 'togglePinActivity'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.togglePinActivity');
	Route::delete('/acquisition/lead/activity/{id}', [AcquisitionController::class, 'deleteActivity'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.deleteActivity');
	Route::post('/acquisition/lead/{id}/task', [AcquisitionController::class, 'createTask'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.createTask');
	Route::put('/acquisition/lead/task/{id}/complete', [AcquisitionController::class, 'completeTask'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.completeTask');
	Route::post('/acquisition/lead/task/{id}/confirm-visit', [AcquisitionController::class, 'confirmVisitTask'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.confirmVisitTask');
	Route::post('/acquisition/lead/task/{id}/link-visit-event', [AcquisitionController::class, 'linkVisitTaskEvent'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.linkVisitTaskEvent');
	Route::post('/acquisition/lead/task/{id}/counter-propose', [AcquisitionController::class, 'counterProposeVisitTask'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.counterProposeVisitTask');
	Route::put('/acquisition/lead/task/{id}', [AcquisitionController::class, 'updateTask'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.updateTask');
	Route::delete('/acquisition/lead/task/{id}', [AcquisitionController::class, 'deleteTask'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.deleteTask');
	Route::post('/acquisition/lead/{id}/tasks/reorder', [AcquisitionController::class, 'reorderTasks'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.reorderTasks');
	Route::put('/acquisition/lead/{id}/assign', [AcquisitionController::class, 'assignLead'])->middleware('planaccess:ACCESS_TEAM')->name('acquisition.assignLead');
	Route::post('/acquisition/lead/{id}/assignees', [AcquisitionController::class, 'attachAssignee'])->middleware('planaccess:ACCESS_TEAM')->name('acquisition.attachAssignee');
	Route::delete('/acquisition/lead/{id}/assignees/{userId}', [AcquisitionController::class, 'detachAssignee'])->middleware('planaccess:ACCESS_TEAM')->name('acquisition.detachAssignee');
	Route::put('/acquisition/lead/{id}/assignees/{userId}/primary', [AcquisitionController::class, 'setPrimaryAssignee'])->middleware('planaccess:ACCESS_TEAM')->name('acquisition.setPrimaryAssignee');
	Route::post('/acquisition/lead/{id}/label', [AcquisitionController::class, 'toggleLabel'])->name('acquisition.toggleLabel');
	Route::post('/acquisition/labels', [AcquisitionController::class, 'createLabel'])->name('acquisition.createLabel');
	Route::put('/acquisition/labels/{id}/archive', [AcquisitionController::class, 'archiveLabel'])->name('acquisition.archiveLabel');
	Route::post('/acquisition/lead/{id}/proposal', [AcquisitionController::class, 'storeProposal'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.storeProposal');
	Route::post('/acquisition/lead/proposal/{id}/relaunch', [AcquisitionController::class, 'relaunchProposalAction'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.relaunchProposal');
	// Sprint 1 Bloc 3 - Quick action "Envoyer l'estimation"
	Route::post('/acquisition/lead/{id}/send-estimation', [AcquisitionController::class, 'sendEstimation'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.sendEstimation');
	Route::get('/acquisition/lead/{id}/send-estimation/preview', [AcquisitionController::class, 'sendEstimationPreview'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.sendEstimationPreview');
	// #2 - Aperçu du rendu d'une proposition avant envoi (section comm > proposals)
	Route::get('/acquisition/lead/{id}/proposal-preview', [AcquisitionController::class, 'proposalPreview'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.proposalPreview');
	// Sprint 1 Bloc 4 - Bulk action sur matches épinglés
	Route::post('/acquisition/lead/{id}/matches/bulk-action', [AcquisitionController::class, 'bulkMatchAction'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.matches.bulk');
	// Présentation des épinglés (2026-09-17) : aperçu éditable puis envoi, sens piloté par le type du lead
	Route::get('/acquisition/lead/{id}/matches/selection/preview', [AcquisitionController::class, 'matchSelectionPreview'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.matches.selectionPreview');
	Route::post('/acquisition/lead/{id}/matches/selection/send', [AcquisitionController::class, 'matchSelectionSend'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.matches.selectionSend');
	Route::post('/acquisition/lead/{id}/matches/selection/pdf', [AcquisitionController::class, 'matchSelectionPdf'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.matches.selectionPdf');
	// 2026-09-23 - Relance des acquereurs d'un bien (fiche vendeur / bailleur) : apercu + envoi groupe.
	Route::get('/acquisition/lead/{id}/buyer-followup/preview', [AcquisitionController::class, 'buyerFollowupPreview'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.buyerFollowupPreview');
	Route::post('/acquisition/lead/{id}/buyer-followup/send', [AcquisitionController::class, 'buyerFollowupSend'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.buyerFollowupSend');
	// Sprint 1 Bloc 5 - Bon de visite (générer + valider signature manuelle)
	Route::post('/acquisition/lead/{id}/bon-visite/generate', [AcquisitionController::class, 'generateBonVisite'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.bonVisite.generate');
	Route::post('/acquisition/lead/{id}/proposal/{proposalId}/mark-signed-manual', [AcquisitionController::class, 'markProposalSignedManual'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.proposal.markSignedManual');
	// Option A : marquer un mandat en attente « sans suite » depuis la fiche lead (n° conservé, hors chaîne)
	Route::post('/acquisition/lead/{id}/proposal/{proposalId}/sans-suite', [AcquisitionController::class, 'markProposalSansSuite'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.proposal.sansSuite');
	// Sprint 2026-05-28 - Lien Lead_proposal ↔ Agenda_event (stocké dans
	// Lead_proposal.metadata.agenda_event_id). Utilisé par la modal bon de
	// visite quand l'utilisateur coche "Créer aussi un RDV" : après création
	// des 2 entités, le JS appelle ce endpoint pour les relier.
	Route::post('/acquisition/lead/{id}/proposal/{proposalId}/link-event', [AcquisitionController::class, 'linkProposalToEvent'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.proposal.linkEvent');
	// Phase 2 - V1 Lettre prospection / V4 Demande pieces / V3 Mandat / V6 Annonce / V8 CR visite / V9 Compromis / V10 Acte
	Route::get('/acquisition/lead/{id}/lettre-prospection/preview', [AcquisitionController::class, 'lettreProspectionPreview'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.lettreProspectionPreview');
	Route::get('/acquisition/lead/{id}/lettre-prospection/preview-pdf', [AcquisitionController::class, 'lettreProspectionPreviewPdf'])->where('id', '[0-9]+')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.lettreProspectionPreviewPdf');
	Route::post('/acquisition/lead/{id}/send-lettre-prospection', [AcquisitionController::class, 'sendLettreProspection'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.sendLettreProspection');
	Route::post('/acquisition/lead/{id}/send-demande-pieces', [AcquisitionController::class, 'sendDemandePieces'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.sendDemandePieces');
	Route::get('/acquisition/lead/{id}/required-documents/catalog', [AcquisitionController::class, 'requiredDocumentsCatalog'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.required_documents.catalog');
	Route::post('/acquisition/lead/{id}/mandat/generate', [AcquisitionController::class, 'generateMandat'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.mandat.generate');
	Route::post('/acquisition/lead/{id}/advert/publish', [AcquisitionController::class, 'publishAdvert'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.advert.publish');
	Route::get('/acquisition/lead/{id}/visit/{visitId}/cr', [AcquisitionController::class, 'getCrVisite'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.visit.cr.get');
	Route::post('/acquisition/lead/{id}/visit/{visitId}/cr', [AcquisitionController::class, 'saveCrVisite'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.visit.cr');
	Route::post('/acquisition/lead/{id}/visit/{visitId}/cr/draft', [AcquisitionController::class, 'saveCrVisiteDraft'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.visit.cr.draft');
	Route::post('/acquisition/lead/{id}/compromis/generate', [AcquisitionController::class, 'generateCompromis'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.compromis.generate');
	Route::post('/acquisition/lead/{id}/close-sale', [AcquisitionController::class, 'closeSale'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.closeSale');
	Route::get('/acquisition/lead/{id}/close-sale/prefill', [AcquisitionController::class, 'closeSalePrefill'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.closeSalePrefill');
	Route::get('/acquisition/lead/{id}/bail/prefill', [AcquisitionController::class, 'bailPrefill'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.bailPrefill');
	Route::get('/acquisition/lead/{id}/preavis/prefill', [AcquisitionController::class, 'preavisPrefill'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.preavisPrefill');
	Route::get('/acquisition/lead/{id}/compromis/prefill', [AcquisitionController::class, 'compromisPrefill'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.compromisPrefill');
	Route::get('/acquisition/lead/{id}/invoice/{invoiceId}/download', [AcquisitionController::class, 'downloadInvoice'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.invoice.download');
	// Sprint A - A9 préavis loc/bailleur/congé pour vente, A6 avenant mandat (Hoguet)
	// Solution C (2026-06-11) : préavis/caution/bail/PV ouverts au tier particulier (capability) - les autres routes gardent 'hoguet'.
	Route::post('/acquisition/lead/{id}/preavis/generate', [AcquisitionController::class, 'generatePreavis'])->middleware('capability:rental.docs')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.preavis.generate');
	Route::post('/acquisition/lead/{id}/mandat/{originalProposalId}/avenant', [AcquisitionController::class, 'generateAvenantMandat'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.mandat.avenant');
	// Sprint A - A3 rétractation SRU (Hoguet)
	Route::post('/acquisition/lead/{id}/sru-retractation', [AcquisitionController::class, 'recordSruRetractation'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.sru.retract');
	// Sprint B - B3b : relance ciblée d'un signataire multi-parties
	Route::post('/acquisition/lead/{id}/proposal/{proposalId}/signer/{signerId}/relance', [AcquisitionController::class, 'relanceSigner'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.signer.relance');
	// Sprint B - B3c : Consultation EDL + comparaison entrée/sortie + PDF
	Route::get('/acquisition/lead/{id}/edl-data/{kind}', [AcquisitionController::class, 'getEdlData'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.edl.data');
	Route::get('/acquisition/lead/{id}/edl-pdf/{kind}', [AcquisitionController::class, 'downloadEdlPdf'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.edl.pdf');
	Route::get('/acquisition/lead/{id}/edl-photo/{photoId}', [AcquisitionController::class, 'streamEdlPhoto'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.edl.photo');
	// 2026-09-22 : compte rendu de visite VENDEUR (template cr_visite_vendeur) - apercu editable puis envoi manuel
	Route::get('/acquisition/lead/{id}/visit/{visitId}/cr-vendeur/preview', [AcquisitionController::class, 'crVendeurPreview'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.visit.crVendeur.preview');
	Route::post('/acquisition/lead/{id}/visit/{visitId}/cr-vendeur', [AcquisitionController::class, 'sendCrVendeur'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.visit.crVendeur');
	// Sprint B - D1 : contre-proposition admin à une offre acheteur
	Route::post('/acquisition/lead/{leadId}/offer/{offerId}/counter', [AcquisitionController::class, 'generateCounterOffer'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.offer.counter');
	// Sprint B - Groupe 5 : Documents pro (C2/C3/C9)
	Route::post('/acquisition/lead/{id}/bordereau-pieces/generate', [AcquisitionController::class, 'generateBordereauPieces'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.bordereau.generate');
	// Mandat de gestion locative RETIRÉ (2026-06-30) - feature non gérée (génération + honoraires de gestion absents).
	Route::post('/acquisition/lead/{id}/mandat-recherche/generate', [AcquisitionController::class, 'generateMandatRecherche'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.mandat.recherche.generate');
	// Solution C (2026-06-11) - mandat de négociation notariale : notaire exempté Hoguet (art. 2 loi 70-9),
	// réf interne NEG-AAAA-NNNN, AUCUN registre. Gate = capacité dédiée (PAS le middleware hoguet).
	Route::post('/acquisition/lead/{id}/mandat-notarial/generate', [AcquisitionController::class, 'generateMandatNotarial'])->middleware('capability:notarial.mandate')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.mandat.notarial.generate');
	Route::post('/acquisition/lead/{id}/pv-remise-cles/generate', [AcquisitionController::class, 'generatePvRemiseCles'])->middleware('capability:keys.pv')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.pvcles.generate');
	// Lot 3 - documents obligatoires : informations précontractuelles (avant mandat) + acte de cautionnement (bail)
	Route::post('/acquisition/lead/{id}/informations-precontractuelles/generate', [AcquisitionController::class, 'generateInformationsPrecontractuelles'])->middleware('hoguet')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.infosprecontract.generate');
	Route::post('/acquisition/lead/{id}/acte-cautionnement/generate', [AcquisitionController::class, 'generateActeCautionnement'])->middleware('capability:rental.docs')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.cautionnement.generate');
	// Sprint B - Groupe 6 : Communication libre (F2 message + F4 log appel)
	Route::post('/acquisition/lead/{id}/message', [AcquisitionController::class, 'sendLeadMessage'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.message');
	Route::get('/acquisition/lead/{id}/message-template', [AcquisitionController::class, 'getMessageTemplate'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.messageTemplate');
	// Messages reçus depuis l'espace client (card « Messages du client », onglet Communications)
	Route::post('/acquisition/lead/{id}/portal-message/{activityId}/reply', [AcquisitionController::class, 'replyPortalMessage'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.portalMessage.reply');
	Route::post('/acquisition/lead/{id}/portal-message/{activityId}/handled', [AcquisitionController::class, 'markPortalMessageHandled'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.portalMessage.handled');
	Route::post('/acquisition/lead/{id}/call/log', [AcquisitionController::class, 'logCall'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.call.log');
	// Phase 2 - V4 Required documents (cote pro)
	Route::get('/acquisition/lead/{id}/required-docs', [AcquisitionController::class, 'getRequiredDocs'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.requiredDocs');
	Route::get('/acquisition/lead/{id}/required-docs/render', [AcquisitionController::class, 'renderRequiredDocsPartial'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.requiredDocs.render');
	Route::post('/acquisition/lead/{id}/required-docs/{docId}/fulfill', [AcquisitionController::class, 'markRequiredDocFulfilled'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.requiredDocs.fulfill');
	Route::post('/acquisition/lead/{id}/required-docs/{docId}/unfulfill', [AcquisitionController::class, 'markRequiredDocUnfulfilled'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.requiredDocs.unfulfill');
	// Phase 3 - A8 : Seed manuel checklist TENANT (8 docs ALUR)
	Route::post('/acquisition/lead/{id}/dossier-locataire/seed', [AcquisitionController::class, 'seedTenantDossier'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.seedTenantDossier');
	// Phase 3 - A2 : Niveaux Must/Nice/Deal sur les critères de matching
	Route::post('/acquisition/lead/{id}/criteria-levels', [AcquisitionController::class, 'updateCriteriaLevels'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.updateCriteriaLevels');
	// Profil financier acheteur/locataire (mode de financement + note libre dans requirement_data)
	Route::post('/acquisition/lead/{id}/financing', [AcquisitionController::class, 'updateFinancing'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.updateFinancing');
	// Phase 3 - A10 : Inviter un garant pour un lead TENANT
	Route::post('/acquisition/lead/{id}/invite-guarantor', [AcquisitionController::class, 'inviteGuarantor'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.inviteGuarantor');
	// Phase 3 - A9.1 : Génération bail multi-parties (TENANT)
	Route::post('/acquisition/lead/{id}/bail/generate', [AcquisitionController::class, 'generateBail'])->middleware('capability:rental.docs')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.generateBail');
	// Quittance de loyer / reçu partiel (art. 21 loi 89-462) - pros + particuliers (capacité rental.docs)
	Route::post('/acquisition/lead/{id}/quittance/generate', [AcquisitionController::class, 'generateQuittance'])->middleware('capability:rental.docs')->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.quittance.generate');
	// Phase 3 - A9.2 / A9.3 : Sauvegarde état des lieux (entrée + sortie)
	Route::post('/acquisition/lead/{id}/etat-lieux/entree', [AcquisitionController::class, 'saveEtatLieuxEntree'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.saveEtatLieuxEntree');
	Route::post('/acquisition/lead/{id}/etat-lieux/sortie', [AcquisitionController::class, 'saveEtatLieuxSortie'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.lead.saveEtatLieuxSortie');
	Route::put('/acquisition/lead/{id}/requirement', [AcquisitionController::class, 'updateRequirement'])->name('acquisition.updateRequirement');
	// Note interne du lead (colonnes leads.comment + leads.url, migrees depuis les prospectings).
	// VOLONTAIREMENT sans `planaccess:` : l'onglet Communications est gate par ACCESS_CRM
	// (2 plans sur 40) alors que la note doit rester lisible et editable par tous les plans
	// porteurs d'ACCESS_LEAD. Meme precedent que la route `requirement` ci-dessus.
	Route::put('/acquisition/lead/{id}/note', [AcquisitionController::class, 'updateLeadNote'])->name('acquisition.updateLeadNote');
	// Renommage du dossier : libelle interne (fiche, kanban, liste). Pas de planaccess -
	// comme la note, il herite d'ACCESS_LEAD (le CRM ne concerne que 2 plans sur 40).
	Route::put('/acquisition/lead/{id}/title', [AcquisitionController::class, 'updateLeadTitle'])->name('acquisition.updateLeadTitle');
	Route::put('/acquisition/lead/{id}/priority', [AcquisitionController::class, 'updatePriority'])->name('acquisition.updatePriority');
	// Lead contacts (CRM pivot)
	Route::post('/acquisition/lead/{id}/contacts', [AcquisitionController::class, 'attachContact'])->name('acquisition.attachContact');
	Route::delete('/acquisition/lead/{id}/contacts/{pivotId}', [AcquisitionController::class, 'detachContact'])->name('acquisition.detachContact');
	Route::put('/acquisition/lead/{id}/contacts/{pivotId}/primary', [AcquisitionController::class, 'setPrimaryContact'])->name('acquisition.setPrimaryContact');
	Route::put('/acquisition/lead/{id}/contacts/{pivotId}/status', [AcquisitionController::class, 'updateContactStatus'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.updateContactStatus');
	// Lead contact offers
	Route::post('/acquisition/lead/{id}/contacts/{pivotId}/offers', [AcquisitionController::class, 'createOffer'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.createOffer');
	Route::post('/acquisition/lead/{id}/contacts/{pivotId}/applications', [AcquisitionController::class, 'createApplicationManual'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.createApplicationManual');
	Route::put('/acquisition/lead/{id}/offers/{offerId}/status', [AcquisitionController::class, 'updateOfferStatusAction'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.updateOfferStatus');
	// Phase 3 - A6/A7 : Override admin sur la décision d'offre (accept/reject/counter)
	Route::post('/acquisition/lead/{id}/offer/{offerId}/decision-admin', [AcquisitionController::class, 'offerDecisionAdmin'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.offerDecisionAdmin');
	// Lead contact visits
	Route::post('/acquisition/lead/{id}/contacts/{pivotId}/visits', [AcquisitionController::class, 'recordVisit'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.recordVisit');
	Route::put('/acquisition/lead/{id}/visits/{visitId}/feedback', [AcquisitionController::class, 'updateVisitFeedback'])->middleware('planaccess:ACCESS_CRM')->name('acquisition.updateVisitFeedback');
	Route::post('/acquisition/createLeadModal', [AcquisitionController::class, 'createLeadModal'])->name('acquisition.createLeadModal');
	Route::post('/acquisition/locateAdvert', [AcquisitionController::class, 'locateAdvert'])->name('acquisition.locateAdvert');

	}); // fin planaccess:ACCESS_LEAD (bloc POST/PUT)

	// Agenda - specific routes BEFORE catch-all {map?}
	Route::post('/agenda', [AgendaController::class, 'index'])->name('agenda.index');
	Route::middleware('planaccess:ACCESS_AGENDA')->group(function () { // grille V2 : agenda = Premium+
	Route::get('/agenda/events', [AgendaController::class, 'events'])->name('agenda.events');
	Route::post('/agenda/events', [AgendaController::class, 'store'])->name('agenda.store');
	Route::put('/agenda/events/{id}', [AgendaController::class, 'update'])->name('agenda.update');
	Route::put('/agenda/events/{id}/report', [AgendaController::class, 'updateReport'])->name('agenda.updateReport');
	Route::delete('/agenda/events/{id}', [AgendaController::class, 'destroy'])->name('agenda.destroy');
	Route::get('/agenda/event-types', [AgendaController::class, 'eventTypes'])->name('agenda.eventTypes');
	Route::get('/agenda/event-form', [AgendaController::class, 'eventForm'])->name('agenda.eventForm');
	Route::get('/agenda/attendee-search', [AgendaController::class, 'searchAttendees'])->name('agenda.attendees.search');
	Route::get('/agenda/check-conflict', [AgendaController::class, 'checkConflict'])->name('agenda.checkConflict');
	Route::get('/agenda/search', [AgendaController::class, 'searchEvents'])->name('agenda.search');
	Route::get('/agenda/events/{id}/ics', [AgendaController::class, 'exportIcs'])->name('agenda.exportIcs');
	Route::get('/agenda/stats', [AgendaController::class, 'stats'])->name('agenda.stats');
	Route::put('/agenda/event/{id}/participation', [AgendaController::class, 'updateParticipation'])->name('agenda.participation');
	Route::get('/acquisition/leads/search', [AgendaController::class, 'searchLeads'])->name('agenda.leads.search');
	}); // fin planaccess:ACCESS_AGENDA
	Route::get('/agenda/{map?}', [LoadController::class, 'agenda'])->name('home.agenda.show');

	Route::get('/annonce/{id?}/{map?}', [LoadController::class, 'annonce'])->name('home.annonce.show');
	Route::get('/annonce-sauvegarde/{id?}/{map?}', [LoadController::class, 'annonceSauvegarde'])->name('home.annonce.sauvegarde.show');
	Route::get('/opportunite/{map?}', [LoadController::class, 'opportunite'])->name('home.opportunity.show');
	Route::post('/analyse-marche', [MarketController::class, 'index'])->name('market.index');
	Route::get('/analyse-marche/{map?}', [LoadController::class, 'analyseMarche'])->name('home.market.show');
	Route::get('/cadastre/{map?}', [LoadController::class, 'cadastre'])->name('home.cadastre.show');
	Route::get('/cadastre/list/{map?}', [LoadController::class, 'cadastre'])->name('home.cadastre.list');
	Route::get('/contacts/{id?}{map?}', [LoadController::class, 'contact'])->name('home.contacts.index');
	Route::get('/historique/{map?}', [LoadController::class, 'history'])->name('home.history.show');

	// Centre d'aide privé (/aide) : guides de resources/guides, lisibles par tout utilisateur connecté
	// (pas de planaccess : un guide de module verrouillé présente le forfait qui le débloque).
	Route::get('/aide', [\App\Http\Controllers\HelpController::class, 'index'])->name('help.index');
	Route::get('/aide/index.json', [\App\Http\Controllers\HelpController::class, 'indexJson'])->name('help.index.json');
	Route::get('/aide/recherche', [\App\Http\Controllers\HelpController::class, 'search'])->name('help.search');
	Route::post('/aide/recherche/vide', [\App\Http\Controllers\HelpController::class, 'searchMiss'])->middleware('throttle:30,1')->name('help.search.miss');
	// Mesure (phase 8) : avis « Ce guide vous a-t-il aidé ? », événements du navigateur, statistiques de l'équipe.
	Route::post('/aide/avis', [\App\Http\Controllers\HelpController::class, 'feedback'])->middleware('throttle:30,1')->name('help.feedback');
	Route::post('/aide/mesure', [\App\Http\Controllers\HelpController::class, 'track'])->middleware('throttle:60,1')->name('help.track');
	Route::get('/aide/statistiques', [\App\Http\Controllers\HelpController::class, 'stats'])->name('help.stats');
	Route::get('/aide/media/{path}', [\App\Http\Controllers\HelpController::class, 'media'])->where('path', '.+')->name('help.media');
	Route::get('/aide/{category}', [\App\Http\Controllers\HelpController::class, 'category'])->where('category', '[a-z0-9-]+')->name('help.category');
	Route::get('/aide/{category}/{slug}', [\App\Http\Controllers\HelpController::class, 'show'])->where(['category' => '[a-z0-9-]+', 'slug' => '[a-z0-9-]+'])->name('help.show');
	Route::get('/couche/{map?}', [LoadController::class, 'overlayer'])->name('home.overlayer.show');
	Route::get('/parametres/{map?}', [LoadController::class, 'setting'])->name('home.setting.show');

	// Feedback utilisateur (bug / amélioration / fonctionnalité)
	Route::post('/feedback', [FeedbackController::class, 'store'])->name('feedback.store');

	// Sprint 2026-05-22 - CRUD avis clients (admin)
	Route::middleware('planaccess:ACCESS_CRM')->group(function () { // grille V2 : avis clients = CRM
	Route::get('/reviews',                       [\App\Http\Controllers\Lead_reviewController::class, 'index'])->name('reviews.index');
	Route::post('/reviews',                      [\App\Http\Controllers\Lead_reviewController::class, 'store'])->name('reviews.store');
	Route::put('/reviews/{id}',                  [\App\Http\Controllers\Lead_reviewController::class, 'update'])->where('id', '[0-9]+')->name('reviews.update');
	Route::post('/reviews/{id}/toggle-pin',      [\App\Http\Controllers\Lead_reviewController::class, 'togglePin'])->where('id', '[0-9]+')->name('reviews.togglePin');
	Route::post('/reviews/{id}/toggle-publish',  [\App\Http\Controllers\Lead_reviewController::class, 'togglePublish'])->where('id', '[0-9]+')->name('reviews.togglePublish');
	Route::post('/reviews/reorder',              [\App\Http\Controllers\Lead_reviewController::class, 'reorder'])->name('reviews.reorder');
	Route::post('/reviews/{id}/reject',          [\App\Http\Controllers\Lead_reviewController::class, 'reject'])->where('id', '[0-9]+')->name('reviews.reject');
	Route::post('/reviews/{id}/respond',         [\App\Http\Controllers\Lead_reviewController::class, 'respond'])->where('id', '[0-9]+')->name('reviews.respond');
	Route::get('/reviews/reports',               [\App\Http\Controllers\Lead_reviewController::class, 'reports'])->name('reviews.reports');
	Route::post('/reviews/reports/{reportId}/resolve', [\App\Http\Controllers\Lead_reviewController::class, 'resolveReport'])->where('reportId', '[0-9]+')->name('reviews.resolveReport');
	Route::post('/reviews/share-profile',        [\App\Http\Controllers\Lead_reviewController::class, 'shareProfile'])->name('reviews.shareProfile');
	Route::post('/reviews/import',               [\App\Http\Controllers\Lead_reviewController::class, 'import'])->name('reviews.import');
	Route::delete('/reviews/{id}',               [\App\Http\Controllers\Lead_reviewController::class, 'destroy'])->where('id', '[0-9]+')->name('reviews.destroy');
	}); // fin planaccess:ACCESS_CRM (avis clients)
});

// ============================================================================
// Portail Client (PUBLIC, no auth) - protégé par middleware portal.token
// Rate limit pour éviter scraping/brute-force sur les tokens 64 chars
// ============================================================================

// PWA assets - hors du middleware portal.token (servis publiquement)
Route::get('/client/sw.js',                                       [\App\Http\Controllers\ClientPortalPublicController::class, 'serviceWorker'])
    ->middleware('throttle:120,1')
    ->name('client.portal.sw');

// B3a - Signature multi-parties (compromis, bail, EDL, offre d'achat…) - token unique par signataire (Lead_proposal_signer.signature_token)
// Pas de portal.token (utilise son propre token). Throttle pour éviter brute-force.
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/sign/{signatureToken}',          [\App\Http\Controllers\ClientPortalPublicController::class, 'showSignerPage'])->name('sign.signer.show');
    Route::post('/sign/{signatureToken}',         [\App\Http\Controllers\ClientPortalPublicController::class, 'submitSignerSignature'])->name('sign.signer.submit');
    Route::get('/sign/{signatureToken}/download', [\App\Http\Controllers\ClientPortalPublicController::class, 'downloadSignerProposalPdf'])->name('sign.signer.download');
    // B5 - Vérification d'identité du signataire (code de sécurité e-mail/SMS)
    Route::post('/sign/{signatureToken}/send-code',   [\App\Http\Controllers\ClientPortalPublicController::class, 'sendSignerCode'])->name('sign.signer.sendCode');
    Route::post('/sign/{signatureToken}/verify-code', [\App\Http\Controllers\ClientPortalPublicController::class, 'verifySignerCode'])->name('sign.signer.verifyCode');
});

// Sprint C.1 - C11 : Pages légales publiques du portail (mentions / CGU / cookies-RGPD).
// Pas de middleware portal.token (accessibles depuis tout lien - un token optionnel en
// query string permet de garder le branding agence). Throttle léger anti-scraping.
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/portail/mentions-legales',  [\App\Http\Controllers\ClientPortalLegalController::class, 'mentions'])->name('client.portal.legal.mentions');
    Route::get('/portail/cgu',               [\App\Http\Controllers\ClientPortalLegalController::class, 'cgu'])->name('client.portal.legal.cgu');
    Route::get('/portail/cookies-rgpd',      [\App\Http\Controllers\ClientPortalLegalController::class, 'cookies'])->name('client.portal.legal.cookies');
});

Route::middleware(['throttle:60,1', 'portal.token'])->group(function () {
    // B5 portail - Vérification d'identité du destinataire (code e-mail/SMS).
    // Ces 3 routes sont volontairement EXEMPTÉES du verrou (par leur route-name dans
    // ValidatePortalToken) pour ne pas créer de boucle de redirection.
    Route::get('/client/{token}/verify',        [\App\Http\Controllers\ClientPortalPublicController::class, 'showVerify'])->name('client.portal.verify');
    Route::post('/client/{token}/verify/send',  [\App\Http\Controllers\ClientPortalPublicController::class, 'sendVerifyCode'])->name('client.portal.verify.send');
    Route::post('/client/{token}/verify/check', [\App\Http\Controllers\ClientPortalPublicController::class, 'checkVerifyCode'])->name('client.portal.verify.check');

    // Prospection - claim : le prospect (QR de la lettre déposée en boîte aux lettres)
    // complète son identité, son consentement et son projet pour activer son espace.
    Route::get('/client/{token}/claim',  [\App\Http\Controllers\ClientPortalPublicController::class, 'showClaim'])->name('client.portal.claim');
    Route::post('/client/{token}/claim', [\App\Http\Controllers\ClientPortalPublicController::class, 'submitClaim'])->name('client.portal.claim.submit');

    // NB (2026-08-06) : la route `GET /portal/match-tracker/{matchToken}/open`
    // (`MatchTrackerController`, « Phase 3 - A4 ») a été SUPPRIMÉE. Elle était morte à
    // quatre niveaux : déclarée dans ce groupe `portal.token` sans paramètre `{token}`
    // elle renvoyait 404 avant d'atteindre son controller, aucun code ni template ne
    // générait le lien, le modèle `Lead_advert` qu'elle manipulait n'existe pas, et la
    // donnée `email_opens` n'était lue nulle part. Le tracking réel des biens proposés
    // passe par `POST /client/{token}/match/{matchId}/click` (`trackMatchClick`,
    // déclaré plus bas), qui écrit `seen_at`/`clicked_at` sur `lead_proposed_matches`.
    Route::get('/client/{token}',                                 [\App\Http\Controllers\ClientPortalPublicController::class, 'show'])
        ->middleware('portal.track')
        ->name('client.portal.show');
    Route::get('/client/{token}/manifest.json',                   [\App\Http\Controllers\ClientPortalPublicController::class, 'manifest'])
        ->name('client.portal.manifest');
    Route::post('/client/{token}/heartbeat',                      [\App\Http\Controllers\ClientPortalPublicController::class, 'heartbeat'])
        ->name('client.portal.heartbeat');
    Route::post('/client/{token}/event',                          [\App\Http\Controllers\ClientPortalPublicController::class, 'markEvent'])
        ->name('client.portal.event');
    Route::post('/client/{token}/rgpd-request',                   [\App\Http\Controllers\ClientPortalPublicController::class, 'rgpdRequest'])
        ->name('client.portal.rgpd');
    // Phase 4 - N9 : Web Push client portail
    Route::post('/client/{token}/push-subscribe',                 [\App\Http\Controllers\ClientPortalPublicController::class, 'pushSubscribe'])
        ->name('client.portal.push.subscribe');
    Route::post('/client/{token}/push-unsubscribe',               [\App\Http\Controllers\ClientPortalPublicController::class, 'pushUnsubscribe'])
        ->name('client.portal.push.unsubscribe');
    Route::get('/client/{token}/documents/{docId}/download',      [\App\Http\Controllers\ClientPortalPublicController::class, 'downloadDocument'])
        ->middleware('portal.track')
        ->name('client.portal.download');
    Route::get('/client/{token}/parcel/svg/{cadastreId}',         [\App\Http\Controllers\ClientPortalPublicController::class, 'parcelSvg'])
        ->name('client.portal.parcel.svg');

    // Actions client interactives - créent toutes une Lead_activity côté pro
    Route::post('/client/{token}/match-reaction',                 [\App\Http\Controllers\ClientPortalPublicController::class, 'matchReaction'])
        ->name('client.portal.matchReaction');
    Route::get('/client/{token}/visit-slots',                     [\App\Http\Controllers\ClientPortalPublicController::class, 'visitSlots'])
        ->name('client.portal.visitSlots');
    Route::post('/client/{token}/visit-request',                  [\App\Http\Controllers\ClientPortalPublicController::class, 'visitRequest'])
        ->name('client.portal.visitRequest');
    Route::post('/client/{token}/visit-request/{requestId}/accept', [\App\Http\Controllers\ClientPortalPublicController::class, 'visitRequestAccept'])
        ->name('client.portal.visitRequestAccept');
    Route::post('/client/{token}/visit-request/{requestId}/attendance', [\App\Http\Controllers\ClientPortalPublicController::class, 'visitRequestAttendance'])
        ->name('client.portal.visitRequestAttendance');
    // Sprint C 3.5 - Self booking : le client propose un créneau de visite/RDV depuis son portail
    Route::post('/client/{token}/self-booking', [\App\Http\Controllers\ClientPortalPublicController::class, 'selfBooking'])
        ->name('client.portal.selfBooking');
    // Questionnaire post-visite (2026-09-23) : le GET affiche la page SANS rien ecrire (un
    // scanner de liens ne cree plus de faux retour), le POST enregistre le retour du visiteur.
    Route::get('/client/{token}/visit/{eventId}/feedback', [\App\Http\Controllers\ClientPortalPublicController::class, 'visitFeedback'])
        ->where('eventId', '[0-9]+')
        ->name('client.portal.visitFeedback');
    Route::post('/client/{token}/visit/{eventId}/feedback', [\App\Http\Controllers\ClientPortalPublicController::class, 'submitVisitFeedback'])
        ->where('eventId', '[0-9]+')
        ->name('client.portal.visitFeedback.submit');
    // Sprint B - D3 : tracking click match depuis portail
    Route::post('/client/{token}/match/{matchId}/click', [\App\Http\Controllers\ClientPortalPublicController::class, 'trackMatchClick'])
        ->name('client.portal.matchClick');
    Route::post('/client/{token}/event/{eventId}/attendance', [\App\Http\Controllers\ClientPortalPublicController::class, 'eventAttendance'])
        ->name('client.portal.eventAttendance');
    Route::post('/client/{token}/rdv-action',                     [\App\Http\Controllers\ClientPortalPublicController::class, 'rdvAction'])
        ->name('client.portal.rdvAction');
    Route::post('/client/{token}/message',                        [\App\Http\Controllers\ClientPortalPublicController::class, 'sendMessage'])
        ->name('client.portal.message');
    Route::post('/client/{token}/document/upload',                [\App\Http\Controllers\ClientPortalPublicController::class, 'uploadDocument'])
        ->name('client.portal.uploadDocument');
    // Sprint 1 Bloc 5 - Signature électronique d'une proposition (bon de visite)
    Route::post('/client/{token}/proposal/{proposalId}/sign',     [\App\Http\Controllers\ClientPortalPublicController::class, 'signProposal'])
        ->name('client.portal.signProposal');
    // Aperçu du PDF AVANT signature (étape « Lire » de la modal, flux mono-partie) - sert le PDF inline.
    Route::get('/client/{token}/proposal/{proposalId}/download',  [\App\Http\Controllers\ClientPortalPublicController::class, 'downloadPortalProposalPdf'])
        ->where('proposalId', '[0-9]+')
        ->name('client.portal.proposalDownload');

    // Phase 3 - A6/A7 : Workflow Offre d'achat (acheteur dépose / vendeur décide)
    Route::post('/client/{token}/offer/create',                   [\App\Http\Controllers\ClientPortalPublicController::class, 'createOffer'])
        ->name('client.portal.createOffer');
    Route::post('/client/{token}/offer/{offerId}/decision',       [\App\Http\Controllers\ClientPortalPublicController::class, 'offerDecision'])
        ->name('client.portal.offerDecision');
    Route::post('/client/{token}/offer/{offerId}/withdraw',       [\App\Http\Controllers\ClientPortalPublicController::class, 'withdrawOffer'])
        ->name('client.portal.withdrawOffer');

    // Demande de location (locataire dépose un dossier) - stockée comme Lead_contact_offer
    Route::post('/client/{token}/application/create',             [\App\Http\Controllers\ClientPortalPublicController::class, 'createApplication'])
        ->name('client.portal.createApplication');
});