<script lang="ts">
    import Button from "$lib/components/base/button.svelte";
    import Select from "$lib/components/base/select.svelte";
    import TextField from "$lib/components/base/text_field.svelte";
    import Textarea from "$lib/components/base/textarea.svelte";
    import Toggle from "$lib/components/base/toggle.svelte";
    import PhotoCard from "$lib/components/photo_card.svelte";
    import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
    import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
    import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
    import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
    import { ms } from "$lib/meilisearch";
    import { SummitLog } from "$lib/models/summit_log";
    import { Trail, trailSchema } from "$lib/models/trail";
    import { Waypoint } from "$lib/models/waypoint";
    import { categories } from "$lib/stores/category_store";
    import { summitLog } from "$lib/stores/summit_log_store";
    import { show_toast } from "$lib/stores/toast_store";
    import {
        trail,
        trails_create,
        trails_update,
    } from "$lib/stores/trail_store";
    import { waypoint } from "$lib/stores/waypoint_store";
    import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
    import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
    import { createForm } from "$lib/vendor/svelte-form-lib";
    import cryptoRandomString from "crypto-random-string";
    import { format } from "date-fns";
    import type { GPX, Icon, LeafletEvent, Map } from "leaflet";
    import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
    import "leaflet/dist/leaflet.css";
    import { onMount } from "svelte";

    export let data: { trail: Trail };

    let L: any;
    let map: Map;

    let gpxLayer: GPX;

    let openWaypointModal: () => void;
    let openSummitLogModal: () => void;

    let loading = false;

    onMount(async () => {
        L = (await import("leaflet")).default;
        await import("leaflet-gpx");
        await import("leaflet.awesome-markers");

        map = L.map("map").setView([0, 0], 2);

        L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
            attribution: "© OpenStreetMap contributors",
        }).addTo(map);

        if (
            data.trail.expand.gpx_data &&
            data.trail.expand.gpx_data.length > 0
        ) {
            addGPXLayer(data.trail.expand.gpx_data, false);
        }

        if (data.trail.expand.waypoints?.length > 0) {
            for (const waypoint of data.trail.expand.waypoints) {
                const marker = createMarkerFromWaypoint(L, waypoint);
                marker.addTo(map);
            }
        }
    });

    const { form, errors, handleChange, handleSubmit } = createForm<Trail>({
        initialValues: data.trail,
        validationSchema: trailSchema,
        onSubmit: async (submittedTrail) => {
            loading = true;
            try {
                const htmlForm = document.getElementById(
                    "trail-form",
                ) as HTMLFormElement;
                const formData = new FormData(htmlForm);
                if (!submittedTrail.id) {
                    const createdTrail = await trails_create(submittedTrail, formData);
                    $form.id = createdTrail.id;
                } else {
                    await trails_update($trail, submittedTrail, formData);
                }

                show_toast({
                    type: "success",
                    icon: "check",
                    text: "Trail saved successfully.",
                });
            } catch (e) {
                console.error(e);

                show_toast({
                    type: "error",
                    icon: "close",
                    text: "Error saving trail.",
                });
            } finally {
                loading = false;
            }
        },
    });

    function addGPXLayer(gpx: string, addWaypoints: boolean = true) {
        return new Promise<void>(function (resolve, reject) {
            gpxLayer?.remove();
            gpxLayer = new L.GPX(gpx, {
                async: true,
                gpx_options: {
                    parseElements: [
                        "track",
                        "route",
                        ...(addWaypoints ? ["waypoint"] : []),
                    ],
                },
                marker_options: {
                    wptIcons: {
                        "": L.AwesomeMarkers.icon({
                            icon: "circle",
                            prefix: "fa",
                            markerColor: "cadetblue",
                            iconColor: "white",
                        }) as Icon,
                    },
                    startIcon: L.AwesomeMarkers.icon({
                        icon: "circle-half-stroke",
                        prefix: "fa",
                        markerColor: "cadetblue",
                        iconColor: "white",
                    }) as Icon,
                    endIcon: L.AwesomeMarkers.icon({
                        icon: "flag-checkered",
                        prefix: "fa",
                        markerColor: "cadetblue",
                        iconColor: "white",
                    }) as Icon,
                    startIconUrl: "",
                    endIconUrl: "",
                    shadowUrl: "",
                },
            })
                .on("addpoint", function (e: any) {
                    if (e.point_type === "start") {
                        e.point.setZIndexOffset(1000);
                        $form.lat = e.point._latlng.lat;
                        $form.lon = e.point._latlng.lng;
                    } else if (e.point_type === "waypoint") {
                        const waypoint = new Waypoint(
                            e.point._latlng.lat,
                            e.point._latlng.lng,
                            { name: e.point.options.title, marker: e.point },
                        );
                        if (!$form.expand.waypoints) {
                        }
                        $form.expand.waypoints.push(waypoint);
                    }
                })
                .on("loaded", function (e: LeafletEvent) {
                    map.fitBounds(e.target.getBounds());
                    $form.distance = e.target.get_distance();
                    $form.elevation_gain = e.target.get_elevation_gain();
                    $form.duration = e.target.get_total_time() / 1000 / 60;
                    resolve();
                })
                .on("error", reject)
                .addTo(map);
        });
    }

    function openFileBrowser() {
        document.getElementById("fileInput")!.click();
    }

    function handleFileSelection() {
        const selectedFile = (
            document.getElementById("fileInput") as HTMLInputElement
        ).files?.[0];

        if (!selectedFile) {
            return;
        }

        $form.gpx = selectedFile?.name;
        $form.expand.waypoints = [];

        var reader = new FileReader();

        reader.readAsText(selectedFile);

        reader.onload = async function (e) {
            trail.set(new Trail(""));
            await addGPXLayer(e.target?.result as string);
            const closestCity = (await ms.index("cities500").search("", {
                filter: [`_geoRadius(${$form.lat}, ${$form.lon}, 10000)`],
                sort: [`_geoPoint(${$form.lat}, ${$form.lon}):asc`],
                limit: 1,
            })).hits[0];

            $form.location = closestCity.name;
        };
    }

    function openMarkerPopup(waypoint: Waypoint) {
        waypoint.marker?.openPopup();
    }

    function handleWaypointMenuClick(
        currentWaypoint: Waypoint,
        index: number,
        e: CustomEvent<{ text: string; value: string }>,
    ) {
        if (e.detail.value === "edit") {
            waypoint.set(currentWaypoint);
            openWaypointModal();
        } else if (e.detail.value === "delete") {
            currentWaypoint.marker?.remove();
            $form.expand.waypoints.splice(index, 1);
            $form.expand.waypoints = $form.expand.waypoints;
        }
    }

    function beforeWaypointModalOpen() {
        const mapCenter = map.getCenter();
        waypoint.set(new Waypoint(mapCenter.lat, mapCenter.lng));
        openWaypointModal();
    }

    function saveWaypoint(e: CustomEvent<Waypoint>) {
        const savedWaypoint = e.detail;
        let editedWaypointIndex = $form.expand.waypoints.findIndex(
            (s) => s.id == savedWaypoint.id,
        );

        if (editedWaypointIndex >= 0) {
            $form.expand.waypoints[editedWaypointIndex].marker?.remove();
            $form.expand.waypoints[editedWaypointIndex] = savedWaypoint;
        } else {
            savedWaypoint.id = cryptoRandomString({ length: 15 });

            $form.expand.waypoints = [...$form.expand.waypoints, savedWaypoint];
        }
        const marker = createMarkerFromWaypoint(L, savedWaypoint);
        marker.addTo(map);
        savedWaypoint.marker = marker;
    }

    function openPhotoBrowser() {
        document.getElementById("photoInput")!.click();
    }

    function handlePhotoSelection() {
        const files = (
            document.getElementById("photoInput") as HTMLInputElement
        ).files;

        if (!files) {
            return;
        }

        for (const file of files) {
            $form._photoFiles.push(file);

            (function (file) {
                var reader = new FileReader();
                reader.onload = function (e) {
                    if (e.target?.result) {
                        if ($form.photos.length == 0) {
                            $form.thumbnail = e.target.result as string;
                        }
                        $form.photos = [
                            ...$form.photos,
                            e.target.result as string,
                        ];
                    }
                };
                reader.readAsDataURL(file);
            })(file);
        }
    }

    function makePhotoThumbnail(index: number) {
        $form.thumbnail = $form.photos[index];
    }

    function handlePhotoDelete(index: number) {
        const photoToDelete = $form.photos[index];
        let reassignThumbnail: boolean = false;
        if ($form.thumbnail == photoToDelete) {
            reassignThumbnail = true;
        }
        $form.photos.splice(index, 1);
        $form._photoFiles.splice(index, 1);
        $form.photos = $form.photos;
        if (reassignThumbnail) {
            $form.thumbnail = $form.photos[0];
        }
    }

    function beforeSummitLogModalOpen() {
        summitLog.set(new SummitLog(format(new Date(), "yyyy-MM-dd")));
        openSummitLogModal();
    }

    function saveSummitLog(e: CustomEvent<SummitLog>) {
        const savedSummitLog = e.detail;
        savedSummitLog.date = format(savedSummitLog.date, "yyyy-MM-dd");
        let editedSummitLogIndex = $form.expand.summit_logs.findIndex(
            (s) => s.id == savedSummitLog.id,
        );

        if (editedSummitLogIndex >= 0) {
            $form.expand.summit_logs[editedSummitLogIndex] = savedSummitLog;
        } else {
            savedSummitLog.id = cryptoRandomString({ length: 15 });
            $form.expand.summit_logs = [
                ...$form.expand.summit_logs,
                savedSummitLog,
            ];
        }
    }

    function handleSummitLogMenuClick(
        currentSummitLog: SummitLog,
        index: number,
        e: CustomEvent<{ text: string; value: string }>,
    ) {
        if (e.detail.value === "edit") {
            currentSummitLog.date = format(currentSummitLog.date, "yyyy-MM-dd");
            summitLog.set(currentSummitLog);
            openSummitLogModal();
        } else if (e.detail.value === "delete") {
            $form.expand.summit_logs.splice(index, 1);
            $form.expand.summit_logs = $form.expand.summit_logs;
        }
    }
</script>

<main class="grid grid-cols-[400px_1fr]">
    <form
        id="trail-form"
        class="overflow-y-scroll overflow-x-hidden flex flex-col gap-4 px-8"
        on:submit={handleSubmit}
    >
        <h3 class="text-xl font-semibold">Pick a trail</h3>
        <button class="btn-primary" type="button" on:click={openFileBrowser}
            >Upload GPX</button
        >
        <input
            type="file"
            name="gpx"
            id="fileInput"
            accept=".gpx"
            style="display: none;"
            on:change={handleFileSelection}
        />
        <hr />
        <h3 class="text-xl font-semibold">Basic Info</h3>
        <div class="flex gap-4 justify-around">
            <div class="flex flex-col items-center">
                <span>Distance</span>
                <span class="font-medium">{formatMeters($form.distance)}</span>
                <input type="hidden" name="distance" value={$form.distance} />
            </div>
            <div class="flex flex-col items-center">
                <span>Elevation gain</span>
                <span class="font-medium"
                    >{formatMeters($form.elevation_gain)}</span
                >
                <input
                    type="hidden"
                    name="elevation_gain"
                    value={$form.elevation_gain}
                />
            </div>
            <div class="flex flex-col items-center">
                <span>Est. duration</span>
                <span class="font-medium">{formatTimeHHMM($form.duration)}</span
                >
                <input type="hidden" name="duration" value={$form.duration} />
            </div>
            <input type="hidden" name="lat" value={$form.lat} />
            <input type="hidden" name="lon" value={$form.lon} />
        </div>
        <TextField
            name="name"
            label="Name"
            on:change={handleChange}
            error={$errors.name}
            bind:value={$form.name}
        ></TextField>
        <TextField
            name="location"
            label="Location"
            error={$errors.location}
            bind:value={$form.location}
        ></TextField>
        <Textarea
            name="description"
            label="Describe your trail"
            bind:value={$form.description}
        ></Textarea>
        {#if $form.expand.category}
            <Select
                name="category"
                label="Category"
                bind:value={$form.expand.category.id}
                items={$categories.map((c) => ({ text: c.name, value: c.id }))}
            ></Select>
        {/if}
        <Toggle name="public" label="Public" bind:value={$form.public}></Toggle>
        <hr />
        <h3 class="text-xl font-semibold">Waypoints</h3>
        <ul>
            {#each $form.expand.waypoints ?? [] as waypoint, i}
                <li on:mouseenter={() => openMarkerPopup(waypoint)}>
                    <WaypointCard
                        {waypoint}
                        mode="edit"
                        on:change={(e) =>
                            handleWaypointMenuClick(waypoint, i, e)}
                    ></WaypointCard>
                </li>
            {/each}
        </ul>
        <button
            class="btn-secondary"
            type="button"
            on:click={beforeWaypointModalOpen}
            ><i class="fa fa-plus mr-2"></i>Add Waypoint</button
        >
        <hr />
        <h3 class="text-xl font-semibold">Photos</h3>
        <div class="flex gap-4 max-w-full overflow-x-scroll shrink-0">
            <button
                class="btn-secondary h-32 w-32 shrink-0 grow-0 basis-auto"
                type="button"
                on:click={openPhotoBrowser}><i class="fa fa-plus"></i></button
            >
            <input
                type="file"
                id="photoInput"
                accept="image/*"
                multiple={true}
                style="display: none;"
                on:change={handlePhotoSelection}
            />
            {#each $form.photos ?? [] as photo, i}
                <div class="shrink-0 grow-0 basis-auto">
                    <PhotoCard
                        src={photo}
                        on:delete={() => handlePhotoDelete(i)}
                        isThumbnail={$form.thumbnail === photo}
                        on:thumbnail={() => makePhotoThumbnail(i)}
                    ></PhotoCard>
                </div>
            {/each}
        </div>
        <hr />
        <h3 class="text-xl font-semibold">Summit Book</h3>
        <ul>
            {#each $form.expand.summit_logs ?? [] as log, i}
                <li>
                    <SummitLogCard
                        {log}
                        mode="edit"
                        on:change={(e) => handleSummitLogMenuClick(log, i, e)}
                    ></SummitLogCard>
                </li>
            {/each}
        </ul>
        <button
            class="btn-secondary"
            type="button"
            on:click={beforeSummitLogModalOpen}
            ><i class="fa fa-plus mr-2"></i>Add Entry</button
        >
        <hr />
        <Button
            primary={true}
            large={true}
            type="submit"
            extraClasses="mb-2"
            {loading}>Save Trail</Button
        >
    </form>
    <div class="rounded-xl" id="map"></div>
</main>
<WaypointModal bind:openModal={openWaypointModal} on:save={saveWaypoint}
></WaypointModal>
<SummitLogModal bind:openModal={openSummitLogModal} on:save={saveSummitLog}
></SummitLogModal>

<style>
    #map,
    form {
        height: calc(100vh - 124px);
    }
</style>
