import { Component, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { LucideAngularModule, Swords, Plus, Globe, Pencil, Trash2 } from 'lucide-angular'; import { Router, RouterLink } from '@angular/router'; import { forkJoin, of } from 'rxjs'; import { catchError, switchMap, filter, map } from 'rxjs/operators'; import { CampaignService } from '../../services/campaign.service'; import { LoreService } from '../../services/lore.service'; import { LayoutService, GlobalItem } from '../../services/layout.service'; import { PageTitleService } from '../../services/page-title.service'; import { Campaign, Arc } from '../../services/campaign.model'; import { Lore } from '../../services/lore.model'; import { loadCampaignTreeData, buildCampaignTree, CampaignTreeData } from '../campaign-tree.helper'; @Component({ selector: 'app-campaign-detail', standalone: true, imports: [CommonModule, FormsModule, LucideAngularModule, RouterLink], templateUrl: './campaign-detail.component.html', styleUrls: ['./campaign-detail.component.scss'] }) export class CampaignDetailComponent implements OnInit, OnDestroy { readonly Swords = Swords; readonly Plus = Plus; readonly Globe = Globe; readonly Pencil = Pencil; readonly Trash2 = Trash2; campaign: Campaign | null = null; arcs: Arc[] = []; /** Lore associé si `campaign.loreId` est renseigné ; sinon null. */ linkedLore: Lore | null = null; /** Lores disponibles pour changer l'association en mode édition. */ availableLores: Lore[] = []; /** Mode édition inline. */ editing = false; editName = ''; editDescription = ''; editLoreId = ''; constructor( private route: ActivatedRoute, private router: Router, private campaignService: CampaignService, private loreService: LoreService, private layoutService: LayoutService, private pageTitleService: PageTitleService ) {} ngOnInit(): void { // switchMap annule automatiquement le load précédent si l'utilisateur // change de campagne avant que le forkJoin ne réponde — évite qu'une // réponse en retard écrase des données plus récentes (race condition). this.route.paramMap.pipe( map(pm => pm.get('id')), filter((id): id is string => !!id && id !== this.campaign?.id), switchMap(id => forkJoin({ campaign: this.campaignService.getCampaignById(id), allCampaigns: this.campaignService.getAllCampaigns(), treeData: loadCampaignTreeData(this.campaignService, id).pipe( catchError(() => of({ arcs: [], chaptersByArc: {}, scenesByChapter: {} } as CampaignTreeData)) ) })) ).subscribe(({ campaign, allCampaigns, treeData }) => { this.campaign = campaign; this.editing = false; this.loadLinkedLore(campaign); this.arcs = treeData.arcs; this.showLayout(allCampaigns, treeData); this.pageTitleService.set(campaign.name); }); } /** * Recharge explicitement après une mise à jour locale (ex: saveEdit). * Contrairement au flux ngOnInit, on bypass le filter sur l'ID puisqu'on * veut rafraîchir même si l'ID n'a pas changé. */ private reload(id: string): void { forkJoin({ campaign: this.campaignService.getCampaignById(id), allCampaigns: this.campaignService.getAllCampaigns(), treeData: loadCampaignTreeData(this.campaignService, id).pipe( catchError(() => of({ arcs: [], chaptersByArc: {}, scenesByChapter: {} } as CampaignTreeData)) ) }).subscribe(({ campaign, allCampaigns, treeData }) => { this.campaign = campaign; this.editing = false; this.loadLinkedLore(campaign); this.arcs = treeData.arcs; this.showLayout(allCampaigns, treeData); this.pageTitleService.set(campaign.name); }); } /** * Charge le Lore associé (si loreId présent). On swallow l'erreur : * si le Lore a été supprimé entre-temps, on affiche simplement "Univers introuvable". */ private loadLinkedLore(campaign: Campaign): void { if (!campaign.loreId) { this.linkedLore = null; return; } this.loreService.getLoreById(campaign.loreId).pipe( catchError(() => of(null)) ).subscribe(lore => this.linkedLore = lore); } private showLayout(allCampaigns: Campaign[], data: CampaignTreeData): void { const campaignId = this.campaign!.id!; const globalItems: GlobalItem[] = allCampaigns.map(c => ({ id: c.id!, name: c.name, route: `/campaigns/${c.id}` })); this.layoutService.show({ title: this.campaign!.name, items: buildCampaignTree(campaignId, data), footerLabel: 'Toutes les campagnes', createActions: [ { id: 'create-arc', label: '+ Nouvel arc', variant: 'primary', route: `/campaigns/${campaignId}/arcs/create` } ], globalItems, globalBackLabel: 'Toutes les campagnes', globalBackRoute: '/campaigns' }); } // ─────────────── Édition / suppression de la Campagne ─────────────── startEdit(): void { if (!this.campaign) return; this.editName = this.campaign.name; this.editDescription = this.campaign.description ?? ''; this.editLoreId = this.campaign.loreId ?? ''; // On charge les Lores disponibles pour le select uniquement à l'entrée en mode édition. this.loreService.getAllLores().subscribe({ next: (lores) => this.availableLores = lores, error: () => this.availableLores = [] }); this.editing = true; } cancelEdit(): void { this.editing = false; } saveEdit(): void { if (!this.campaign || !this.editName.trim()) return; this.campaignService.updateCampaign(this.campaign.id!, { name: this.editName.trim(), description: this.editDescription, playerCount: this.campaign.playerCount ?? 0, loreId: this.editLoreId ? this.editLoreId : null }).subscribe({ next: (updated) => { this.campaign = updated; this.editing = false; // Recharge pour actualiser le badge "Univers" et le titre sidebar. this.reload(updated.id!); }, error: () => console.error('Erreur lors de la mise à jour de la campagne') }); } /** * Suppression protégée : refus si la campagne contient des arcs. * Les arcs contiennent potentiellement des chapitres/scènes construits longuement. */ deleteCampaign(): void { if (!this.campaign) return; if (this.arcs.length > 0) { alert( `Impossible de supprimer "${this.campaign.name}" : elle contient encore ${this.arcs.length} arc(s).\n` + `Videz la campagne (arcs et chapitres) avant de la supprimer.` ); return; } if (!confirm(`Supprimer définitivement la campagne "${this.campaign.name}" ?`)) return; this.campaignService.deleteCampaign(this.campaign.id!).subscribe({ next: () => this.router.navigate(['/campaigns']), error: () => console.error('Erreur lors de la suppression de la campagne') }); } ngOnDestroy(): void { this.layoutService.hide(); } }