<script lang="ts">
  import { readable, type Readable } from "svelte/store";
  import type { UpListing } from "upend";
  import type { Address, IValue } 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";
  import IconButton from "../utils/IconButton.svelte";
  import Selector from "../utils/Selector.svelte";
  import { createEventDispatcher } from "svelte";
  import type { WidgetChange } from "src/types/base";
  import debug from "debug";
  const dispatch = createEventDispatcher();
  const dbg = debug(`kestrel:EntityList`);

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

  $: deduplicatedEntities = Array.from(new Set(entities));

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

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

  // Sorting
  let sortedEntities: Address[] = [];

  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 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);
      },
    };
  }

  // Adding
  let addSelector: Selector | undefined;
  let adding = false;

  $: if (adding && addSelector) addSelector.focus();

  function addEntity(ev: CustomEvent<IValue>) {
    dbg("Adding entity", ev.detail);
    const addAddress = ev.detail?.t == "Address" ? ev.detail.c : undefined;
    if (!addAddress) return;

    dispatch("change", {
      type: "entry-add",
      address: addAddress,
    } as WidgetChange);
  }

  function removeEntity(address: string) {
    if (
      confirm(
        $i18n.t("Are you sure you want to remove this entry from members?"),
      )
    ) {
      dbg("Removing entity", address);
      dispatch("change", {
        type: "entry-delete",
        address,
      } as WidgetChange);
    }
  }
</script>

<div
  class="entitylist style-{style}"
  class:has-thumbnails={thumbnails}
  bind:clientWidth
>
  {#if !sortedEntities.length}
    <div class="message">
      {$i18n.t("No entries.")}
    </div>
  {/if}
  <div class="items">
    {#each sortedEntities as entity (entity)}
      <div data-address={entity} use:observe class="item">
        {#if visible.has(entity)}
          {#if thumbnails}
            <UpObjectCard
              address={entity}
              labels={sortKeys[entity]}
              banner={false}
              on:resolved={(event) => {
                addSortKeys(entity, event.detail, true);
              }}
            />
            <div class="icon">
              <IconButton
                name="trash"
                color="#dc322f"
                on:click={() => removeEntity(entity)}
              />
            </div>
          {:else}
            <div class="object">
              <UpObject
                link
                address={entity}
                labels={sortKeys[entity]}
                on:resolved={(event) => {
                  addSortKeys(entity, event.detail, true);
                }}
              />
            </div>
            <div class="icon">
              <IconButton
                name="trash"
                color="#dc322f"
                on:click={() => removeEntity(entity)}
              />
            </div>
          {/if}
        {:else}
          <div class="skeleton" style="text-align: center">...</div>
        {/if}
      </div>
    {/each}
    {#if address}
      <div class="add item">
        {#if adding}
          <Selector
            bind:this={addSelector}
            type="value"
            valueTypes={["Address"]}
            on:input={addEntity}
            on:focus={(ev) => {
              if (!ev.detail) {
                adding = false;
              }
            }}
          />
        {:else}
          <IconButton
            name="plus-circle"
            outline
            subdued
            on:click={() => {
              adding = true;
            }}
          />
        {/if}
      </div>
    {/if}
  </div>
</div>

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

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

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

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

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

  .item {
    min-width: 0;
    overflow: hidden;
  }

  .message {
    text-align: center;
    margin: 0.5em;
    opacity: 0.66;
  }

  .entitylist:not(.has-thumbnails) {
    .item {
      display: flex;
      .object {
        width: 100%;
      }

      .icon {
        width: 0;
        transition: width 0.3s ease;
        text-align: center;
      }

      &:hover {
        .icon {
          width: 1.5em;
        }
      }
    }
  }

  .entitylist.has-thumbnails {
    .item {
      position: relative;

      .icon {
        position: absolute;
        top: 0.5em;
        right: 0.5em;

        opacity: 0;
        transition: opacity 0.3s ease;
      }

      &:hover .icon {
        opacity: 1;
      }
    }
  }

  .add {
    display: flex;
    flex-direction: column;
  }

  .entitylist.style-grid .add {
    grid-column: 1 / -1;
  }
</style>
