import { NextRequest, NextResponse } from 'next/server'; import { query } from '@/lib/db'; export const dynamic = 'force-dynamic'; const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); async function parseGoogleMapsUrl(inputUrl: string) { let targetUrl = inputUrl.trim(); if (targetUrl.includes('goo.gl') || targetUrl.includes('maps.app.goo.gl')) { try { const res = await fetch(targetUrl, { method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, redirect: 'follow', }); targetUrl = res.url || targetUrl; } catch (err) {} } let lat: number | null = null; let lon: number | null = null; const coordMatch = targetUrl.match(/@(-?\d+\.\d+),(-?\d+\.\d+)/); if (coordMatch) { lat = parseFloat(coordMatch[1]); lon = parseFloat(coordMatch[2]); } return { lat, lon }; } async function geocode(address: string) { if (!address || address.startsWith('http')) return null; const search = async (q: string) => { try { const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(q)}&format=json&limit=1`; const res = await fetch(url, { headers: { 'User-Agent': 'TravelPlannerApp/1.0' } }); const data = await res.json(); if (data && data.length > 0) return { lat: parseFloat(data[0].lat), lon: parseFloat(data[0].lon) }; } catch (err) {} return null; }; let coords = await search(address); if (!coords && address.includes(',')) { const parts = address.split(',').map(p => p.trim()); await sleep(1100); coords = await search(`${parts[parts.length - 2]}, ${parts[parts.length - 1]}`); } return coords; } async function getDriveInfo(origin: { lat: number; lon: number }, dest: { lat: number; lon: number }) { try { const url = `http://router.project-osrm.org/route/v1/driving/${origin.lon},${origin.lat};${dest.lon},${dest.lat}?overview=false`; const res = await fetch(url); const data = await res.json(); if (data.code === 'Ok' && data.routes?.length > 0) { return { distanceKm: Math.round((data.routes[0].distance / 1000) * 10) / 10, durationMin: Math.round(data.routes[0].duration / 60), }; } } catch (err) {} return { distanceKm: null, durationMin: null }; } export async function PUT(req: NextRequest, { params }: { params: { id: string } }) { try { const { id } = params; const { name, address, category_icon, notes, hotel_ids, trip_id } = await req.json(); let sightCoords: { lat: number; lon: number } | null = null; if (address.includes('google.com/maps') || address.includes('goo.gl')) { const parsed = await parseGoogleMapsUrl(address); if (parsed.lat && parsed.lon) sightCoords = { lat: parsed.lat, lon: parsed.lon }; } if (!sightCoords) { sightCoords = await geocode(address || name); } const res = await query( `UPDATE hotel_attractions SET name = $1, address = $2, category_icon = $3, notes = $4, trip_id = $5, lat = COALESCE($6, lat), lon = COALESCE($7, lon) WHERE id = $8 RETURNING *`, [ name, address, category_icon || '🏛️', notes || null, trip_id || null, sightCoords?.lat || null, sightCoords?.lon || null, id, ] ); await query('DELETE FROM hotel_attraction_assignments WHERE attraction_id = $1', [id]); const selectedHotelIds: string[] = Array.isArray(hotel_ids) ? hotel_ids : []; for (const hId of selectedHotelIds) { let distanceKm: number | null = null; let durationMin: number | null = null; const hotelRes = await query('SELECT address, name FROM hotels WHERE id = $1', [hId]); if (hotelRes.rows.length > 0) { const hotel = hotelRes.rows[0]; await sleep(1100); let hCoords = await geocode(hotel.address); if (!hCoords && hotel.name) { await sleep(1100); hCoords = await geocode(hotel.name); } if (hCoords && sightCoords) { const drive = await getDriveInfo(hCoords, sightCoords); distanceKm = drive.distanceKm; durationMin = drive.durationMin; } } await query( `INSERT INTO hotel_attraction_assignments (attraction_id, hotel_id, distance_km, duration_minutes) VALUES ($1, $2, $3, $4)`, [id, hId, distanceKm, durationMin] ); } return NextResponse.json({ success: true, attraction: res.rows[0] }); } catch (e: any) { return NextResponse.json({ success: false, error: e.message }, { status: 500 }); } } export async function DELETE(req: NextRequest, { params }: { params: { id: string } }) { try { await query('DELETE FROM hotel_attractions WHERE id = $1', [params.id]); return NextResponse.json({ success: true }); } catch (e: any) { return NextResponse.json({ success: false, error: e.message }, { status: 500 }); } }