<script>
    import { mapStore } from '$lib/stores/mapStore.js';
    import { setMessages } from '$lib/stores/messagesStore.js';
    import { refreshGlobalCount } from '$lib/stores/globalCountStore.js';
    import { addMessage } from '$lib/firebase/messages.js'
    import { hasFace } from '$lib/utils/faceDetection.js';
    import { uploadImage } from '$lib/Firebase/storage.js';


    // isMobile drives whether this renders as the original bottom sheet
    // (mobile, unchanged) or a centered popup with a dimmed backdrop (desktop)
    let {lat, lng, isMobile} = $props();

    // shared close/cancel handler - used by the Cancel button and, on
    // desktop, by clicking the backdrop outside the popup
    function close() {
        mapStore.set({selectedMessage: null, composing: false});
    }

    let text = $state('');
    let submitting = $state(false);
    let remaining = $derived(240-text.length);

    // --- rotating placeholder prompts -------------------------------------
    // A small set of incomplete, contemplative fragments that cycle through
    // as a placeholder for the textarea while it's empty - a gentle nudge
    // toward a certain kind of writing, without dictating what gets written.
    const placeholderPrompts = [
        "today I noticed...",
        "if you're reading this...",
        "this place...",
        "I wish someone knew...",
        "the air smells like...",
        "before I forget..."
    ];

    // index of the prompt currently shown, and its crossfade opacity
    let promptIndex = $state(0);
    let promptOpacity = $state(1);

    // whether the textarea currently has focus - tracked via onfocus/onblur
    // on the textarea below, since this overlay's visibility needs to react
    // to focus in JS, not just CSS.
    let textareaFocused = $state(false);

    // The overlay (see markup below) only exists while the textarea is both
    // empty and unfocused. The moment either condition flips - the user
    // starts typing, or just taps/clicks into the field - the prompts get
    // out of the way entirely, per the feature spec.
    let showPromptOverlay = $derived(text.length === 0 && !textareaFocused);

    // Cycles through placeholderPrompts on a timer, crossfading between them
    // via promptOpacity. This effect only runs while showPromptOverlay is
    // true; the moment it becomes false (typing starts, or the field gains
    // focus), the cleanup below clears any pending timers and the cycle
    // simply stops - clearing the textarea (and leaving it unfocused) makes
    // showPromptOverlay true again, re-running this effect and starting a
    // fresh cycle.
    $effect(() => {
        if (!showPromptOverlay) return;

        // Timing mirrors the fade conventions used elsewhere in the app
        // (the opening ritual / "first here" bubble): a multi-second hold
        // per prompt, then a ~1s crossfade into the next one.
        const holdMs = 3000;
        const fadeMs = 1000;

        let fadeOutTimer;
        let advanceTimer;

        // Schedules one hold -> fade-out -> advance -> fade-in step, then
        // schedules itself again. An $effect only re-runs when the values it
        // READS change, and this step only WRITES promptIndex/promptOpacity,
        // so the cycle has to keep itself going via setTimeout rather than
        // relying on the effect re-running on its own.
        function scheduleStep() {
            fadeOutTimer = setTimeout(() => {
                promptOpacity = 0; // begin crossfade out

                advanceTimer = setTimeout(() => {
                    promptIndex = (promptIndex + 1) % placeholderPrompts.length;
                    promptOpacity = 1; // crossfade the new prompt in
                    scheduleStep();
                }, fadeMs);
            }, holdMs);
        }

        scheduleStep();

        // cleanup: runs when showPromptOverlay flips to false (typing starts
        // or the field is focused) and on component unmount - clears
        // whichever of the two timers is currently pending so the cycle
        // stops cleanly with no stray callbacks.
        return () => {
            clearTimeout(fadeOutTimer);
            clearTimeout(advanceTimer);
        };
    });

    let selectedFile = $state(null);
    let imagePreview = $state(null); // what <img> will read
    let imageError = $state(null);
    let checkingFace = $state(false); // this will show the checking image... message

    // --- mood color (intentionally unexplained feature) -------------------
    // 5 preset pastel swatches, same hue/saturation/lightness formula as
    // pins.js's randomPastel() (hsl(hue, 60%, 72%)) so a picked color sits
    // naturally alongside the existing pin palette. Picking one is optional:
    // selectedMoodColor stays null until tapped, and tapping the already-
    // selected swatch again clears it back to null. null flows through to
    // addMessage() -> Firestore as moodColor: null, and pins.js falls back to
    // its existing random pastel for any pin with no moodColor.
    const moodColors = [
        'hsl(340, 60%, 72%)', // soft pink
        'hsl(210, 60%, 72%)', // soft blue
        'hsl(140, 60%, 72%)', // soft green
        'hsl(265, 60%, 72%)', // soft purple
        'hsl(40, 60%, 72%)'   // soft peach/yellow
    ];
    let selectedMoodColor = $state(null);

    function toggleMoodColor(color) {
        // tapping the selected swatch again deselects it (back to "no opinion")
        selectedMoodColor = selectedMoodColor === color ? null : color;
    }

    async function handleImageSelect(event) {
        const file = event.target.files[0]; // only one file will be allowed so always index 0

        if (!file) return; // no file do nothing 
        checkingFace = true; // show loading state 
        imageError = null; // clear errors
        
        try {
            // run face detection 
            const faceFound = await hasFace(file);

            if (faceFound) {
                // no upload & error
                imageError = 'Images may not contain faces. Please choose another photo.';

                //reset file input for other attempt
                event.target.value = '';
                selectedFile= null;
                imagePreview = null;
            } else {
                // no face we move on 
                selectedFile = file;
                // create preview
                imagePreview = URL.createObjectURL(file);
            }

        } catch (err) {
            // fixed: previously allowed the upload on any detection error — wrong for a
            // face-blocking feature. Now we surface the error and block the upload instead.
            console.error('[faceDetection] error:', err);
            imageError = 'Could not verify the image. Please try a different photo.';
            event.target.value = '';
            selectedFile = null;
            imagePreview = null;
        } finally {
            // no matter what turn the loading state off
            checkingFace = false;
        }
    }

    async function handleSubmit() {
        // no submit with no text or text over limit
        if (!text.trim() || remaining < 0) return;

        submitting = true;
        let imageUrl = ''; // stays empty if no image

        if (selectedFile) {
            imageUrl = await uploadImage(selectedFile);
        }

        await addMessage(lat, lng, text.trim(), imageUrl, selectedMoodColor);

        // the global "memory counter" (meta/stats) just incremented inside
        // addMessage() above - refetch it so the pill's count updates
        // immediately for this device, without waiting on a page reload
        refreshGlobalCount();

        // reload pins so that they show up with new message
        const { getNearbyMessages } = await import ('$lib/firebase/messages.js');
        const updated = await getNearbyMessages(lat, lng);
        // setMessages (instead of messagesStore.set) compares against the
        // previous list and plays a soft chime for any newly-appeared pins
        setMessages(updated);

        // reset compose state
        text = '';
        selectedFile = null;
        imagePreview = null;
        selectedMoodColor = null;
        submitting = false;

        // close the sheet
        mapStore.set({selectedMessage: null, composing:false});
    }
</script>

<!-- desktop-only dimmed backdrop behind the centered popup; clicking it
     cancels, like a typical modal dialog. mobile keeps the plain bottom
     sheet with no backdrop, so this is skipped entirely there -->
{#if !isMobile}
    <!-- a plain <button> (not a <div>) so it's keyboard-focusable/operable
         by default, satisfying a11y click-handler rules without extra
         role/tabindex/onkeydown wiring -->
    <button class="backdrop" onclick={close} aria-label="Close compose popup"></button>
{/if}

<!-- class:desktop switches between the original bottom-sheet styles
     (mobile, untouched) and the centered popup styles (desktop) -->
<div class="compose" class:visible={true} class:desktop={!isMobile}>
    <div class="compose-header">
        <button class="cancel" onclick={close}>
        Cancel
        </button>
        <span class="title">Leave a message</span>
        <span class="counter" class:over={remaining < 0}>{remaining}</span>
    </div>

    <!-- wrapper gives the rotating-placeholder overlay below a positioning context -->
    <div class="textarea-wrap">
        <textarea
            bind:value={text}
            aria-label="Leave a message"
            onfocus={() => textareaFocused = true}
            onblur={() => textareaFocused = false}
            rows="5"
        ></textarea>

        <!-- Rotating placeholder overlay: the native placeholder attribute
             can't crossfade between values, so this is a plain <p>
             absolutely positioned over the textarea instead, styled to look
             like placeholder text. It only exists in the DOM while the
             field is empty and unfocused (showPromptOverlay above) -
             aria-hidden since it's an ambient visual cue rather than real
             content, and pointer-events: none so it never blocks clicks
             into the textarea underneath. -->
        {#if showPromptOverlay}
            <p class="prompt-overlay" style="opacity: {promptOpacity}" aria-hidden="true">
                {placeholderPrompts[promptIndex]}
            </p>
        {/if}
    </div>

    <!-- mood color swatches: 5 preset pastel colors, no labels or explanation
         by design (see project brief / pins.js). selecting one is optional -
         a subtle ring + scale-up marks the selected swatch; tapping it again
         deselects. -->
    <div class="mood-row">
        {#each moodColors as color}
            <button
                type="button"
                class="mood-swatch"
                class:selected={selectedMoodColor === color}
                style="background-color: {color}"
                aria-label="color"
                onclick={() => toggleMoodColor(color)}
            ></button>
        {/each}
    </div>

    <div class = "image-section">
        {#if checkingFace}
            <p class="checking">Checking image...</p>

        {:else if imagePreview}
            <div class="preview-wrap">
                <img class="preview" src={imagePreview} alt="preview" />

                <button class="remove-image" onclick={() => { selectedFile = null; imagePreview = null; }}>
                    X
                </button>
            </div>

        {:else}
            {#if imageError}
                <p class="image-error">{imageError}</p>
            {/if}
            <label class="image-label">
                + add photo
                <input
                    type="file"
                    accept="image/*"
                    onchange={handleImageSelect}
                    style="display:none"
                />
            </label>
        {/if}
    </div>

    <button 
    class="submit"
    onclick={handleSubmit}
    disabled={!text.trim() || remaining < 0 || submitting}>
        {submitting ? "leaving it here..." : "leave it here"}    
    </button>

</div>

<style>
    /* mobile (default): unchanged bottom sheet, full-width, anchored to
       the bottom edge with only the top corners rounded */
    .compose {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        background: white;
        border-radius: 20px 20px 0 0;
        padding: 1.2rem 1.5rem 2.5rem;
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
        z-index: 200;
    }

    /* border-box on the popup and everything inside it - without this,
       elements like textarea/.submit (width: 100% + their own padding/border)
       added their padding/border ON TOP of that 100%, so they rendered
       wider than .compose and stuck out past its rounded edges */
    .compose, .compose * {
        box-sizing: border-box;
    }

    /* desktop: centered popup instead of a stretched bottom sheet.
       overrides position/sizing/rounding from .compose above */
    .compose.desktop {
        bottom: auto;
        left: 50%;
        right: auto;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 650px; /* slightly smaller than the previous 900px, but... */
        aspect-ratio: 1; /* ...paired with this, makes height = width -> a big square popup */
        max-width: 95vw;
        /* no max-height/overflow-y - the flex layout below lets the textarea
           grow/shrink to fill the square instead of causing a scrollbar */
        display: flex;
        flex-direction: column;
        border-radius: 20px; /* round all corners, not just the top */
        padding: 1.2rem 1.5rem 1.5rem; /* less bottom padding needed without the bottom-sheet handle area */
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
        /* sit above the backdrop below (350) - was relying on the inherited
           .compose z-index of 200, which the backdrop now exceeds */
        z-index: 360;
    }

    /* on desktop, .compose-header/.image-section/.submit keep their natural
       height and the textarea (flex: 1 below) grows/shrinks to fill the
       remaining square space. flex props are no-ops on mobile since
       .compose there isn't a flex container */
    .compose-header,
    .image-section,
    .submit {
        flex-shrink: 0;
    }

    /* dimmed full-screen overlay behind the desktop popup; clicking it
       cancels. raised above .legend (200) and the global .archive-link
       (300, in +layout.svelte) so those dim along with everything else
       instead of floating above the overlay - this only ever renders on
       desktop (!isMobile), so mobile is unaffected */
    .backdrop {
        /* reset <button> defaults so it behaves like the plain overlay div it replaced */
        border: none;
        cursor: default;
        position: fixed;
        inset: 0;
        background: rgba(0,0,0,0.35);
        z-index: 350;
    }

    .compose-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 1rem;
    }

    .title {
        font-weight: 600;
        font-size: 0.95rem;
        color: #111;
    }

    .cancel {
        background: none;
        border: none;
        color: #999;
        font-size: 0.9rem;
        cursor: pointer;
        padding: 0;
    }

    .counter {
        font-size: 0.85rem;
        color: #999;
        font-variant-numeric: tublar-nums; 
    }

    .counter.over {
        color: #e74c3c;
        font-weight: 600;
    }

    /* positioning context for the rotating-placeholder overlay (see
       .prompt-overlay below). Plain block on mobile, sized by the
       textarea's natural height; .compose.desktop below gives it the
       flex-growing role textarea itself used to have. */
    .textarea-wrap {
        position: relative;
    }

    /* on desktop, .textarea-wrap takes over the flex-growing role that
       textarea used to have on its own, so the wrapper - and the textarea
       inside it - still fill the remaining square space. Scoped to desktop
       only: on mobile .compose isn't a flex container, and an unscoped
       display: flex here would make .textarea-wrap a flex column with no
       defined height, collapsing the textarea inside it to 0 height. */
    .compose.desktop .textarea-wrap {
        flex: 1;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }

    textarea {
        width: 100%;
        border: 1.5px solid #eee;
        border-radius: 12px;
        padding: 0.9rem;
        font-size: 0.95rem;
        line-height: 1.6;
        resize: none;
        outline: none;
        font-family: Georgia, 'Times New Roman', Times, serif;
        color: #111;
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
        /* fill the remaining square space on desktop (no-op on mobile,
           where .compose isn't a flex container); min-height: 0 lets it
           shrink below its content size instead of overflowing the flex column */
        flex: 1;
        min-height: 0;
    }

    textarea:focus {
        border-color: #111;
    }

    /* visually mimics the textarea's placeholder text - same font, size,
       line-height and padding as the textarea (the extra 1.5px on padding
       accounts for the textarea's border under box-sizing: border-box, so
       the overlay text lines up with where typed text would actually
       start), but lighter in color. Absolutely positioned over the textarea
       so promptOpacity (set in the $effect above) can crossfade it between
       prompts - something a native placeholder attribute can't do. */
    .prompt-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        margin: 0;
        padding: calc(0.9rem + 1.5px);
        font-family: Georgia, 'Times New Roman', Times, serif;
        font-size: 0.95rem;
        line-height: 1.6;
        color: #aaa;
        pointer-events: none;
        transition: opacity 1s ease;
    }

    .submit {
        width: 100%;
        margin-top: 0.75rem;
        padding: 0.85rem;
        background: #111;
        color: white;
        border: none;
        border-radius: 12px;
        font-size: 0.95rem;
        font-weight: 500;
        cursor: pointer;
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
    }

    .submit:disabled {
        background: #ccc;
        cursor: not-allowed;
    }

    /* mood color swatch row - small circles, no text/labels by design */
    .mood-row {
        display: flex;
        gap: 0.6rem;
        margin-top: 0.75rem;
    }

    .mood-swatch {
        width: 26px;
        height: 26px;
        border-radius: 50%;
        border: none;
        padding: 0;
        cursor: pointer;
        /* outline (not box-shadow, for the flat 2D look) starts transparent
           so the ring only appears once .selected adds a visible color -
           transitioning it avoids a layout jump */
        outline: 2px solid transparent;
        outline-offset: 2px;
        transition: outline-color 0.15s, transform 0.15s;
    }

    /* selected swatch: dark ring + slight scale-up, the only visual cue
       that a color is picked */
    .mood-swatch.selected {
        outline-color: #111;
        transform: scale(1.15);
    }

    .image-section {
        margin-top: 0.75rem;
    }

    .image-label {
        display: inline-block;
        /* fixed pre-existing typo ("paddingL" -> "padding:") that was throwing a CSS error */
        padding: 0.5rem 1rem;
        border: 1.5px dashed #ddd;
        border-radius: 8px;
        font-size: 0.85rem;
        color: #999;
        cursor: pointer;
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
    }

    .image-label:hover {
        border-color: #111;
        color: #111;
    }

    .preview-wrap {
        position: relative;
        display: inline-block;
    }

    .preview {
        width: 100%;
        max-height: 200px;
        object-fit: cover;
        border-radius: 10px;
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
    }

    .remove-image {
        position: absolute;
        top: 6px;
        right: 6px;
        background: rgba(0,0,0,0.5);
        color: white;
        border: none;
        border-radius: 50%;
        width: 24px;
        height: 24px;
        font-size: 0.7rem;
        cursor: pointer;
        /* flat 2D style for now - shadow removed, paper-style shadows may be added later */
    }

    .checking {
        font-size: 0.85rem;
        color: #999;
    }

    .image-error {
        font-size: 0.85rem;
        color: #e74c3c;
    }
</style>

