import { searchWorlds, calcParsecs } from './travellerMapApi.js'; const MODULE_ID = 'mgt2-compendium-amiral-denisov'; const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; function cleanSectorName(sector) { return sector?.replace(/\s*\([^)]*\)\s*$/, '').trim() || sector; } function worldCoord(sx, sy, hx, hy) { return { x: (sx - 0) * 32 + (hx - 1), y: (sy - 0) * 40 + (hy - 40) }; } export class TravelDialog extends HandlebarsApplicationMixin(ApplicationV2) { static DEFAULT_OPTIONS = { id: 'mgt2-travel-dialog', classes: ['mgt2-travel-dialog'], position: { width: 600, height: 500 }, window: { icon: 'fas fa-route', title: 'Planificateur de voyage', resizable: true, controls: [] }, }; static PARTS = { main: { template: `modules/${MODULE_ID}/templates/travel-dialog.hbs`, }, }; constructor() { super(); this._fromWorld = null; this._toWorld = null; this._lastWorlds = null; this._lastSegments = null; } _onRender(context, options) { this._wireSearch('from'); this._wireSearch('to'); this.element?.querySelector('[data-action="calculate"]')?.addEventListener('click', () => { this._calculateRoute(); }); this.element?.querySelector('[data-action="create-journal"]')?.addEventListener('click', () => { this._createJournal(); }); } _wireSearch(prefix) { const input = this.element.querySelector(`[name="travel-${prefix}"]`); const results = this.element.querySelector(`.travel-${prefix}-results`); if (!input || !results) return; let timeout = null; input.addEventListener('input', () => { clearTimeout(timeout); const q = input.value.trim(); if (q.length < 2) { results.innerHTML = ''; return; } timeout = setTimeout(async () => { const worlds = await searchWorlds(q); if (!worlds?.length) { results.innerHTML = '
  • Aucun résultat
  • '; return; } results.innerHTML = worlds.slice(0, 10).map(w => `
  • ${w.name} ${w.sector} ${w.hex}
  • ` ).join(''); }, 300); }); results.addEventListener('click', (e) => { const li = e.target.closest('[data-sector]'); if (!li) return; const data = { sector: li.dataset.sector, hex: li.dataset.hex, name: li.dataset.name }; if (prefix === 'from') this._fromWorld = data; else this._toWorld = data; input.value = `${data.name} (${data.sector} ${data.hex})`; results.innerHTML = ''; }); input.addEventListener('blur', () => { setTimeout(() => { results.innerHTML = ''; }, 200); }); } async _calculateRoute() { if (!this._fromWorld || !this._toWorld) { ui.notifications.warn('Veuillez sélectionner un monde de départ et un monde d\'arrivée'); return; } const jumpEl = this.element.querySelector('[name="travel-jump"]'); const jump = parseInt(jumpEl?.value, 10) || 2; const resultsEl = this.element.querySelector('.travel-results'); if (!resultsEl) return; resultsEl.innerHTML = '
    Calcul de l\'itinéraire…
    '; const startSector = cleanSectorName(this._fromWorld.sector); const endSector = cleanSectorName(this._toWorld.sector); const startLoc = `${startSector} ${this._fromWorld.hex}`; const endLoc = `${endSector} ${this._toWorld.hex}`; try { const resp = await fetch( `https://travellermap.com/api/route?start=${encodeURIComponent(startLoc)}&end=${encodeURIComponent(endLoc)}&jump=${jump}` ); if (!resp.ok) { if (resp.status === 404) { const text = await resp.text().catch(() => 'Aucun itinéraire trouvé'); resultsEl.innerHTML = `
    ${this._escapeHtml(text)}
    `; } else { resultsEl.innerHTML = `
    Erreur API (${resp.status})
    `; } return; } const data = await resp.json(); if (!Array.isArray(data) || data.length < 2) { resultsEl.innerHTML = '
    Aucun itinéraire trouvé
    '; return; } this._displayRoute(data, resultsEl); } catch (err) { console.error('TravelDialog | Erreur:', err); resultsEl.innerHTML = `
    Erreur : ${this._escapeHtml(err.message)}
    `; } } _displayRoute(worlds, resultsEl) { this._lastWorlds = worlds; const segments = []; let totalParsecs = 0; for (let i = 0; i < worlds.length - 1; i++) { const a = worlds[i]; const b = worlds[i + 1]; const fromC = worldCoord(a.SectorX, a.SectorY, a.HexX, a.HexY); const toC = worldCoord(b.SectorX, b.SectorY, b.HexX, b.HexY); const dist = calcParsecs(fromC, toC); totalParsecs += dist; segments.push({ from: a, to: b, dist }); } this._lastSegments = segments; let html = `
    ${segments.length} saut${segments.length > 1 ? 's' : ''} · ${totalParsecs} parsecs
    `; html += `
    Durée estimée : ${segments.length} semaine${segments.length > 1 ? 's' : ''}
    `; html += '
      '; segments.forEach((seg) => { const f = seg.from; const t = seg.to; html += `
    1. ${f.Name || '?'} ${f.Sector} ${f.Hex || ''}
      ${t.Name || '?'} ${t.Sector} ${t.Hex || ''}
      Saut-${seg.dist}
    2. `; }); html += '
    '; resultsEl.innerHTML = html; const journalBtn = this.element?.querySelector('.travel-journal-actions'); if (journalBtn) journalBtn.style.display = 'block'; } async _createJournal() { const worlds = this._lastWorlds; const segments = this._lastSegments; if (!worlds?.length || !segments?.length) { ui.notifications.warn('Calculez d\'abord un itinéraire'); return; } const from = worlds[0]; const to = worlds[worlds.length - 1]; const totalParsecs = segments.reduce((s, seg) => s + seg.dist, 0); const totalJumps = segments.length; const jumpRating = this.element?.querySelector('[name="travel-jump"]')?.value || '?'; const lines = []; lines.push(`

    Journal de voyage

    `); lines.push(`

    Départ : ${this._worldLink(from)}

    `); lines.push(`

    Destination : ${this._worldLink(to)}

    `); lines.push(`

    Moteur : J-${jumpRating}

    `); lines.push(`
    `); lines.push(`

    Itinéraire (${totalJumps} saut${totalJumps > 1 ? 's' : ''}, ${totalParsecs} pc)

    `); lines.push(``); segments.forEach((seg, i) => { const f = seg.from; const t = seg.to; lines.push(``); }); lines.push(`
    #DépartArrivéeDistance
    ${i + 1} ${this._worldLink(f)} ${this._worldLink(t)} ${seg.dist} pc
    `); lines.push(`
    `); lines.push(`

    Mondes visités

    `); lines.push(``); const content = lines.join('\n'); try { const journal = await JournalEntry.create({ name: `Voyage : ${from.Name || '?'} → ${to.Name || '?'}`, pages: [{ name: 'Itinéraire', type: 'text', text: { content, format: 2 }, }], }); if (journal) { ui.notifications.info(`Journal créé : ${journal.name}`); journal.sheet?.render(true); } } catch (err) { console.error('TravelDialog | Erreur création journal:', err); ui.notifications.error('Erreur lors de la création du journal'); } } _worldLink(w) { const name = w.Name || '?'; const sector = w.Sector || ''; const hex = w.Hex || ''; return `${name} (${sector} ${hex})`; } _escapeAttr(str) { if (!str) return ''; return str.replace(/"/g, '"').replace(/&/g, '&'); } _escapeHtml(str) { if (!str) return ''; const d = document.createElement('div'); d.textContent = str; return d.innerHTML; } }