<script lang="ts">
  import { readable, type Readable } from "svelte/store";
  import type { UpListing } from "upend";
  import type { Address } from "upend/types";
  import { query } from "../../lib/entity";
  import UpObject from "../display/UpObject.svelte";
  import UpObjectCard from "../display/UpObjectCard.svelte";
  import { ATTR_LABEL } from "upend/constants";
  import { i18n } from "../../i18n";
  import Icon from "../utils/Icon.svelte";

  export let entities: Address[];
  export let thumbnails = true;
  export let sort = true;

  const deduplicatedEntities = Array.from(new Set(entities));

  let style: "list" | "grid" | "flex" = "grid";

  let clientWidth: number;
  $: style = !thumbnails || clientWidth < 600 ? "list" : "grid";

  // Sorting
  let sortedEntities = [];

  let sortKeys: { [key: string]: string[] } = {};
  function addSortKeys(key: string, vals: string[], resort: boolean) {
    if (!sortKeys[key]) {
      sortKeys[key] = [];
    }
    let changed = false;
    vals.forEach((val) => {
      if (!sortKeys[key].includes(val)) {
        changed = true;
        sortKeys[key].push(val);
      }
    });

    if (resort && changed) sortEntities();
  }

  function sortEntities() {
    if (!sort) return;

    sortedEntities = deduplicatedEntities.concat();

    sortedEntities.sort((a, b) => {
      if (!sortKeys[a]?.length || !sortKeys[b]?.length) {
        if (Boolean(sortKeys[a]?.length) && !sortKeys[b]?.length) {
          return -1;
        } else if (!sortKeys[a]?.length && Boolean(sortKeys[b]?.length)) {
          return 1;
        } else {
          return a.localeCompare(b);
        }
      } else {
        return sortKeys[a][0].localeCompare(sortKeys[b][0], undefined, {
          numeric: true,
        });
      }
    });
  }

  // Labelling
  let labelListing: Readable<UpListing> = readable(undefined);
  $: {
    const addressesString = deduplicatedEntities
      .map((addr) => `@${addr}`)
      .join(" ");

    labelListing = query(
      `(matches (in ${addressesString}) "${ATTR_LABEL}" ? )`
    ).result;
  }

  $: {
    if ($labelListing) {
      deduplicatedEntities.forEach((address) => {
        addSortKeys(
          address,
          $labelListing.getObject(address).identify(),
          false
        );
      });
      sortEntities();
    }
  }

  if (!sort) {
    sortedEntities = entities;
  }

  // Visibility

  let itemsEl: HTMLElement;
  let visible: Set<string> = new Set();
  let observer = new IntersectionObserver((intersections) => {
    intersections.forEach((intersection) => {
      const address = (intersection.target as HTMLElement).dataset["address"];
      if (!address) {
        console.warn("Intersected wrong element?");
        return;
      }

      if (intersection.isIntersecting) {
        visible.add(address);
      }
      visible = visible;
    });
  });

  function observe(node: HTMLElement) {
    observer.observe(node);

    return {
      destroy() {
        observer.unobserve(node);
      },
    };
  }
</script>

<div
  class="gallery style-{style}"
  class:has-thumbnails={thumbnails}
  bind:clientWidth
>
  <div class="header" />
  {#if sortedEntities.length}
    <div class="items" bind:this={itemsEl}>
      {#each sortedEntities as entity (entity)}
        <div data-address={entity} use:observe>
          {#if visible.has(entity)}
            {#if thumbnails}
              <UpObjectCard
                address={entity}
                labels={sortKeys[entity]}
                banner={false}
                on:resolved={(event) => {
                  addSortKeys(entity, event.detail, true);
                }}
              />
            {:else}
              <UpObject
                link
                address={entity}
                labels={sortKeys[entity]}
                on:resolved={(event) => {
                  addSortKeys(entity, event.detail, true);
                }}
              />
            {/if}
          {:else}
            <div class="skeleton" style="text-align: center">...</div>
          {/if}
        </div>
      {/each}
    </div>
  {:else}
    <div class="message">
      <Icon name="x-circle" /><br />
      {$i18n.t("No entries that could be previewed.")}
    </div>
  {/if}
</div>

<style lang="scss">
  .items {
    gap: 4px;
  }

  .gallery.has-thumbnails .items {
    gap: 1rem;
  }

  :global(.gallery.style-grid .items) {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
  }

  :global(.gallery.style-flex .items) {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
  }

  :global(.gallery.style-list .items) {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }

  .message {
    text-align: center;
    margin: 0.5em;
  }
</style>
