<script>
    import { onMount } from 'svelte';
    import { get } from 'svelte/store';
    import { env } from '$env/dynamic/public';
    import { messagesStore, livePinIdsStore } from '$lib/stores/messagesStore.js'; // messages store, plus live-arrival ids for the "pop" animation below
    import { mapStore } from '$lib/stores/mapStore.js'; // use this to track interactions with da map
    import { messagePin, locationPin } from '$lib/utils/pins.js'; // custom SVG pin generators
    import { mapStyles } from '$lib/utils/mapStyles.js'; // desaturated/atmospheric map styling - only works now that mapId is gone
    import { getTrail, addTrailPoint } from '$lib/utils/trail.js'; // private, on-device-only location history

    // export let latitude;
    // export let longitude;
    // ^ this didn't work for some reason, so instead we get the props like this: (based on internet research this is the fix)
    // firstHereMounted/firstHereVisible: "you're the first here" bubble state,
    // computed in +page.svelte (zero-results check + fade timing) - rendered
    // here so it can be positioned near the user's location marker below.
    let { lat, lng, firstHereMounted = false, firstHereVisible = false } = $props();

    let mapDiv;
    let map = $state(null);

    let markers = []; // keep track of pins on map

    // Marker/Size/Point/Polyline are only available once their libraries
    // have loaded (assigned in onMount below) - declared here so
    // toMarkerIcon, addUserLocationMarker, renderPins, and the trail effect
    // can all use them.
    let Marker, Size, Point, Polyline;

    // --- personal location trail (private, on-device only) ----------------
    // `trailPoints` holds this device's full visit history (see trail.js),
    // read/appended once in onMount. `trailPolyline` is the Polyline
    // instance once created (lazily, inside the $effect below) - plain
    // variables, not runes, since they're written once and don't need to
    // trigger re-renders themselves.
    let trailPoints = [];
    let trailPolyline;

    // `showTrail` is the only piece of UI state for this feature - toggled
    // by the small button in the markup below. Defaults to ON: the trail is
    // styled to be faint enough (see the $effect below) that it doesn't
    // compete with message pins, so showing it by default lets people
    // notice this quiet "personal map of presence" without it being loud -
    // the toggle just lets them hide it if they'd rather not see it.
    let showTrail = $state(true);

    // converts a { url, size } pin (from pins.js) into an Icon object for
    // the legacy Marker API - centers the icon on its coordinate (anchor at
    // size/2, size/2) the same way the old AdvancedMarkerElement content's
    // `translateY(-50%)` did, but for both axes since Marker's default
    // anchor is bottom-center rather than center-center.
    function toMarkerIcon({ url, size }) {
        return {
            url,
            scaledSize: new Size(size, size),
            anchor: new Point(size / 2, size / 2)
        };
    }

    let userMarker;

    /** Jisu Legacy - 내 위치 마커 (메시지 핀과 구분되는 파란 점) */
    function addUserLocationMarker(centerLat, centerLng) {
        // use the shared locationPin() so the user's marker matches the
        // style of the message pins, instead of building a one-off dot here
        userMarker = new Marker({
            position: { lat: centerLat, lng: centerLng },
            map,
            title: 'Your location',
            zIndex: 1000,
            icon: toMarkerIcon(locationPin())
        });
    }

    onMount (async () => {
        const centerLat = Number(lat);
        const centerLng = Number(lng);

        const { setOptions, importLibrary } = await import('@googlemaps/js-api-loader');
        setOptions({
            key: env.PUBLIC_MAPS_KEY,
            version: 'weekly',
        });

        // switched from AdvancedMarkerElement back to the legacy Marker API
        // (with SVG data-URL icons from pins.js) so a JS `styles` array can
        // take effect on this map again - Google ignores any `styles` array
        // on maps that have a mapId set, and AdvancedMarkerElement requires
        // one. NOTE: the legacy `Marker` class (plus `Size`/`Point`) live in
        // the 'marker'/'core' libraries, NOT 'maps' - both are still needed.
        // Polyline (used by the location trail below) lives in 'maps'
        // alongside Map.
        const { Map, Polyline: PolylineCtor } = await importLibrary('maps');
        Polyline = PolylineCtor;
        ({ Marker } = await importLibrary('marker'));
        ({ Size, Point } = await importLibrary('core'));

        // --- personal location trail: log this visit ----------------------
        // addTrailPoint() writes { lat, lng, timestamp: Date.now() } to
        // localStorage and returns the FULL updated history (oldest first),
        // including this visit - so the line drawn below is always
        // up to date. This runs unconditionally on every successful
        // geolocation fetch (MapView only mounts once lat/lng are known),
        // regardless of `showTrail` - the history keeps growing even while
        // the trail is hidden. Nothing here touches Firestore; it's purely
        // local to this device/browser (see trail.js).
        trailPoints = addTrailPoint(centerLat, centerLng);

        map = new Map(mapDiv, {
            center: { lat: centerLat, lng: centerLng },
            // bumped from 15 -> 17: at 15 the initial view was wider than the
            // ~150m-per-side geohash cells getNearbyMessages queries, so
            // nearby pins opened tiny/clustered near the center. 17 frames
            // roughly that query area on open, so pins are visible without
            // the user needing to zoom in first.
            zoom: 18,
            disableDefaultUI: true,
            gestureHandling: 'greedy',
            // mapId removed - legacy Markers don't need it, and removing it
            // is what lets this `styles` array be respected (see comment above) -
            // desaturated/atmospheric look defined in mapStyles.js
            styles: mapStyles
        });

        addUserLocationMarker(centerLat, centerLng);
    });

    // --- "pins pop into existence" bounce animation ------------------------
    // google.maps.Marker (legacy API) has no built-in entrance animation, and
    // its icon is a static data-URL image rather than a DOM element, so a
    // CSS transition/keyframe animation isn't an option here. Instead, this
    // swaps the marker's icon through a few different `size` values over
    // ~300ms via setIcon(): tiny -> overshoot (115%) -> normal (100%).
    // toMarkerIcon() rebuilds the Size/anchor for each size, so the icon
    // stays centered on its coordinate throughout - the net effect reads as
    // a quick "pop in with a little bounce", the same shape as a CSS
    // `scale(0) -> scale(1.15) -> scale(1)` keyframe animation.
    function animatePinBounce(marker, icon) {
        const steps = [
            { scale: 0.01, delay: 0 },   // start effectively invisible
            { scale: 1.15, delay: 90 },  // overshoot past full size
            { scale: 1.0, delay: 220 }   // settle to normal size
        ];

        steps.forEach(({ scale, delay }) => {
            setTimeout(() => {
                const size = Math.max(1, Math.round(icon.size * scale));
                marker.setIcon(toMarkerIcon({ url: icon.url, size }));
            }, delay);
        });
    }

    // function to render pins
    function renderPins(messages) {
        // clear current pins
        markers.forEach(marker => marker.setMap(null));
        markers = [];

        // ids of pins that just arrived via the real-time listener (see
        // subscribeToNearbyMessages, messages.js) since the last render -
        // these get the "pop" bounce below. Everything else (initial load,
        // refresh, moving to a new area) appears at full size immediately,
        // same as before.
        const newLiveIds = get(livePinIdsStore);

        messages.forEach(message => {
            // every message gets the same plain circle pin (previously this
            // varied - star/circle/heart - based on read/unread/echoed state,
            // swapped on click; that's gone now, so clicking a pin never
            // changes its appearance, only opens the detail view below).
            // message.moodColor (optional, set by ComposeSheet's swatch row and
            // stored in Firestore via addMessage) overrides the random pastel
            // color inside messagePin() when present - see pins.js's pinColor()
            const icon = messagePin(message.moodColor);
            const isLivePin = newLiveIds.has(message.id);

            const marker = new Marker({
                position: { lat: message.lat, lng: message.lng },
                map,
                title: message.text,
                // live-arrival pins start at near-zero size so
                // animatePinBounce below has somewhere to animate FROM -
                // everything else renders at full size immediately
                icon: toMarkerIcon(isLivePin ? { ...icon, size: 1 } : icon),
                // higher than addUserLocationMarker's zIndex (1000) - a
                // message posted from exactly where you're standing sits at
                // the SAME coordinates as your location dot, and without
                // this, the larger (32px) location marker draws on top and
                // completely hides the smaller (28px) message pin beneath it.
                zIndex: 1001
            });

            // legacy Marker uses the Maps JS event system (addListener), not
            // DOM events - this also avoids the AdvancedMarkerElement
            // gmp-click deprecation warning entirely
            marker.addListener('click', () => {
                mapStore.set({ selectedMessage: message, composing: false });
            });

            if (isLivePin) {
                animatePinBounce(marker, icon);
            }

            markers.push(marker);
        });

        // consume the live-pin set now that we've rendered it, so a later
        // render triggered by an unrelated change (e.g. someone echoing an
        // existing message) doesn't re-bounce these same pins again
        if (newLiveIds.size > 0) {
            livePinIdsStore.set(new Set());
        }
    }

    // this is a reactive statement so anytime the store changes it updates
    $effect(() => {
        if (map && $messagesStore.length > 0 ){ // if they both exist
            renderPins($messagesStore); // we put pins on the map
        }
    })

    // --- personal location trail: draw + toggle ---------------------------
    // Re-runs whenever `map` (becomes available once, in onMount) or
    // `showTrail` (toggled by the button below) changes. `trailPoints` and
    // `Polyline` are plain variables, not runes - they're both assigned in
    // onMount BEFORE `map` is, in the same synchronous block, so by the time
    // `map` becomes non-null and triggers this effect, they're already set
    // (same pattern the renderPins effect above relies on for Marker/etc).
    $effect(() => {
        if (!map || !Polyline) return;

        // lazily create the Polyline once, the first time the map is ready.
        // Edge case: a single point (first-ever visit) has nothing to
        // connect, so we simply don't create a line - trailPolyline stays
        // undefined and the toggle below has nothing to show/hide.
        if (!trailPolyline && trailPoints.length >= 2) {
            const path = trailPoints.map(p => ({ lat: p.lat, lng: p.lng }));

            trailPolyline = new Polyline({
                path,
                clickable: false, // purely decorative - shouldn't intercept clicks meant for the map/pins
                zIndex: 1, // sit beneath message/location pins (which default to higher stacking)
                // the solid stroke is fully transparent - only the dash
                // `icons` below are actually drawn, which is how dashed
                // lines are done with google.maps.Polyline (it has no
                // native dash-pattern option)
                strokeOpacity: 0,
                icons: [{
                    icon: {
                        path: 'M 0,-1 0,1', // a short vertical dash
                        strokeOpacity: 0.35, // faint - shouldn't compete visually with message pins
                        strokeColor: '#999', // neutral grey, fits the desaturated/parchment map palette
                        scale: 3
                    },
                    offset: '0',
                    repeat: '14px' // gap between dashes
                }]
            });
        }

        // show/hide based on the toggle - setMap(null) removes it from the
        // map without destroying the instance, so toggling back on doesn't
        // need to rebuild the path.
        if (trailPolyline) {
            trailPolyline.setMap(showTrail ? map : null);
        }
    })

</script>

<div class="map-wrapper">
    <div class="map" bind:this={mapDiv}></div>

    <!-- small, unobtrusive toggle for the personal location trail (private,
         on-device only - see trail.js). Placed top-left so it's discoverable
         without competing with the FAB (bottom-right) or top bars elsewhere. -->
    <button
        class="trail-toggle"
        class:active={showTrail}
        onclick={() => showTrail = !showTrail}
        aria-label={showTrail ? 'Hide your location trail' : 'Show your location trail'}
        title={showTrail ? 'Hide your location trail' : 'Show your location trail'}
    >
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
            <path d="M1.5 8 H5 M7.5 8 H9.5 M12 8 H14.5" stroke-dasharray="2 2"/>
        </svg>
    </button>

    <!-- "you're the first here" speech bubble. The map is created with
         center: {lat, lng} (see onMount) and the user-location marker is
         drawn at that same center point, so positioning this bubble at
         50%/50% of .map-wrapper places it directly above the marker. -->
    {#if firstHereMounted}
        <p class="first-here" class:visible={firstHereVisible} aria-hidden="true">
            no one has been here before — or at least, no one who said so.
        </p>
    {/if}
</div>

<style>
    /* positioning context for .trail-toggle, which is absolutely placed
       within the map area regardless of the desktop/mobile layout in
       +page.svelte */
    .map-wrapper {
        position: relative;
        width: 100%;
        height: 100%;
    }

    /* fill the .map-container parent (height: 100% instead of min-height: 100vh)
       so that the parent's padding-bottom on mobile actually shrinks the
       visible map instead of the map overflowing past it */
    .map {
        width: 100%;
        height: 100%;
    }

    /* small circular toggle, top-left of the map - flat 2D style (no
       shadow) matching the rest of the app's current look. Muted/translucent
       so it doesn't draw attention away from the map itself; .active just
       darkens it slightly to show the trail is currently on. */
    .trail-toggle {
        position: absolute;
        top: 1rem;
        left: 1rem;
        width: 34px;
        height: 34px;
        border-radius: 50%;
        border: 1px solid rgba(0, 0, 0, 0.08);
        background: rgba(255, 255, 255, 0.7);
        color: #aaa;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        z-index: 120;
        transition: color 0.15s, border-color 0.15s;
    }

    .trail-toggle.active {
        color: #666;
        border-color: rgba(0, 0, 0, 0.15);
    }

    /* "you're the first here" speech bubble - "cute speech bubble" look:
       soft cream background (same #f9f7f4 as the opening ritual overlay in
       +layout.svelte) with rounded corners, so the text reads clearly
       against the desaturated map. Positioned at the center of .map-wrapper
       (= the user-location marker's position, see comment in the markup
       above) and shifted up + left by its own size/2 plus the marker's
       16px radius, so the bubble sits just above the marker with its tail
       (::after below) pointing down at it. Starts invisible; .visible fades
       it in/out over 1.2s (timing controlled by the $effects in +page.svelte). */
    .first-here {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, calc(-100% - 16px));
        margin: 0;
        padding: 0.85rem 1.4rem;
        max-width: 80%;
        font-family: Georgia, 'Times New Roman', Times, serif;
        font-size: 0.95rem;
        color: #666;
        letter-spacing: 0.05em;
        text-align: center;
        line-height: 1.6;
        opacity: 0;
        transition: opacity 1.2s ease;
        pointer-events: none;
        z-index: 50;
        background: #f9f7f4;
        border: 1px solid rgba(0, 0, 0, 0.06);
        border-radius: 16px;
    }

    .first-here.visible {
        opacity: 1;
    }

    /* small triangular "tail" pointing down from the bubble toward the
       location marker beneath it */
    .first-here::after {
        content: '';
        position: absolute;
        bottom: -8px;
        left: 50%;
        transform: translateX(-50%);
        width: 0;
        height: 0;
        border-left: 8px solid transparent;
        border-right: 8px solid transparent;
        border-top: 8px solid #f9f7f4;
    }
</style>