<script lang="ts">
  import AttributeView from "./AttributeView.svelte";
  import { query, useEntity } from "../lib/entity";
  import UpObject from "./display/UpObject.svelte";
  import { UpType } from "../lib/types";
  import { createEventDispatcher, setContext } from "svelte";
  import { writable } from "svelte/store";
  import type { UpEntry } from "upend";
  import Spinner from "./utils/Spinner.svelte";
  import NotesEditor from "./utils/NotesEditor.svelte";
  import type { AttributeChange } from "../types/base";
  import Selector from "./utils/Selector.svelte";
  import type { IValue } from "upend/types";
  import IconButton from "./utils/IconButton.svelte";
  import type { BrowseContext } from "../util/browse";
  import { Link, useParams } from "svelte-navigator";
  import { GROUP_TYPE_ADDR } from "upend/constants";
  import {
    deleteEntry,
    putEntityAttribute,
    putEntry,
    queryOnce,
  } from "../lib/api";
  import Icon from "./utils/Icon.svelte";
  import BlobViewer from "./display/BlobViewer.svelte";
  import { i18n } from "../i18n";
  import EntryList from "./widgets/EntryList.svelte";
  const dispatch = createEventDispatcher();
  const params = useParams();

  export let address: string;
  export let index: number | undefined;
  export let detail: boolean;
  export let editable = false;

  let blobHandled = false;

  let indexStore = writable(index);
  $: $indexStore = index;

  let addressesStore = writable([]);
  $: $addressesStore = $params.addresses?.split(",") || [];

  setContext("browse", {
    index: indexStore,
    addresses: addressesStore,
  } as BrowseContext);

  $: ({ entity, entityInfo, error, revalidate } = useEntity(address));

  $: allTypeAddresses = ($entity?.attr["IS"] || []).map((attr) => attr.value.c);

  $: allTypeEntries = query(
    `(matches (in ${allTypeAddresses.map((addr) => `@${addr}`).join(" ")}) ? ?)`
  ).result;

  let allTypes: { [key: string]: UpType } = {};
  $: {
    allTypes = {};
    ($allTypeEntries?.entries || []).forEach((entry) => {
      if (allTypes[entry.entity] === undefined) {
        allTypes[entry.entity] = new UpType(entry.entity);
      }

      switch (entry.attribute) {
        case "TYPE":
          allTypes[entry.entity].name = String(entry.value.c);
          break;
        case "LBL":
          allTypes[entry.entity].label = String(entry.value.c);
          break;
        case "TYPE_HAS":
          allTypes[entry.entity].attributes.push(String(entry.value.c));
          break;
      }
    });

    allTypes = allTypes;
  }

  let typedAttributes = {} as { [key: string]: UpEntry[] };
  let untypedAttributes = [] as UpEntry[];

  $: {
    typedAttributes = {};
    untypedAttributes = [];

    ($entity?.attributes || []).forEach((entry) => {
      const entryTypes = Object.entries(allTypes).filter(([_, t]) =>
        t.attributes.includes(entry.attribute)
      );
      if (entryTypes.length > 0) {
        entryTypes.forEach(([addr, _]) => {
          if (typedAttributes[addr] == undefined) {
            typedAttributes[addr] = [];
          }
          typedAttributes[addr].push(entry);
        });
      } else {
        untypedAttributes.push(entry);
      }
    });

    typedAttributes = typedAttributes;
    untypedAttributes = untypedAttributes;
  }

  $: filteredUntypedAttributes = untypedAttributes.filter(
    (entry) =>
      ![
        "IS",
        "LBL",
        "NOTE",
        "LAST_VISITED",
        "NUM_VISITED",
        "LAST_ATTRIBUTE_WIDGET",
      ].includes(entry.attribute)
  );

  $: currentUntypedAttributes = editable
    ? untypedAttributes
    : filteredUntypedAttributes;

  $: currentBacklinks =
    (editable
      ? $entity?.backlinks
      : $entity?.backlinks.filter(
          (entry) => !["HAS"].includes(entry.attribute)
        )) || [];

  $: groups = ($entity?.backlinks || [])
    .filter((e) => e.attribute === "HAS")
    .map((e) => [e.address, e.entity])
    .sort(); // TODO

  let attributesUsed: UpEntry[] = [];
  $: {
    if ($entityInfo?.t === "Attribute") {
      queryOnce(`(matches ? "${$entityInfo.c}" ?)`).then(
        (result) => (attributesUsed = result.entries)
      );
    }
  }

  async function onChange(ev: CustomEvent<AttributeChange>) {
    const change = ev.detail;
    switch (change.type) {
      case "create":
        await putEntry({
          entity: address,
          attribute: change.attribute,
          value: change.value,
        });
        break;
      case "delete":
        await deleteEntry(change.address);
        break;
      case "update":
        await putEntityAttribute(address, change.attribute, change.value);
        break;
      default:
        console.error("Unimplemented AttributeChange", change);
        return;
    }
    revalidate();
  }

  let identities = [address];
  function onResolved(ev: CustomEvent<string[]>) {
    identities = ev.detail;
    dispatch("resolved", ev.detail);
  }

  let groupToAdd: IValue | undefined;
  async function addGroup() {
    if (!groupToAdd) {
      return;
    }
    await putEntry([
      {
        entity: String(groupToAdd.c),
        attribute: "HAS",
        value: {
          t: "Address",
          c: address,
        },
      },
      {
        entity: String(groupToAdd.c),
        attribute: "IS",
        value: {
          t: "Address",
          c: GROUP_TYPE_ADDR,
        },
      },
    ]);
    revalidate();
    groupToAdd = undefined;
  }

  async function removeGroup(groupAddress: string) {
    await deleteEntry(groupAddress);
    revalidate();
  }

  async function deleteObject() {
    if (confirm(`${$i18n.t("Really delete")} "${identities.join(" | ")}"?`)) {
      await deleteEntry(address);
      dispatch("close");
    }
  }

  async function onAttributeWidgetSwitch(ev: CustomEvent<string>) {
    await putEntityAttribute(address, "LAST_ATTRIBUTE_WIDGET", {
      t: "String",
      c: ev.detail,
    });
  }

  $: entity.subscribe(async (object) => {
    if (object && object.listing.entries.length) {
      await putEntityAttribute(object.address, "LAST_VISITED", {
        t: "Number",
        c: new Date().getTime() / 1000,
      });

      await putEntityAttribute(object.address, "NUM_VISITED", {
        t: "Number",
        c: (parseInt(String(object.get("NUM_VISITED"))) || 0) + 1,
      });
    }
  });
</script>

<div class="inspect" class:detail class:blob={blobHandled}>
  <header>
    <h2>
      {#if $entity}
        <UpObject banner {address} on:resolved={onResolved} />
      {:else}
        <Spinner centered />
      {/if}
    </h2>
  </header>
  <div class="main-content">
    <div class="detail-col">
      {#if groups?.length || editable}
        <section class="groups labelborder">
          <header><h3>{$i18n.t("Groups")}</h3></header>
          <div class="content">
            {#each groups as [entryAddress, address]}
              <div class="group">
                <UpObject {address} link />
                {#if editable}
                  <IconButton
                    name="x-circle"
                    on:click={() => removeGroup(entryAddress)}
                  />
                {/if}
              </div>
            {/each}
            {#if editable}
              <div class="selector">
                <Selector
                  type="value"
                  valueTypes={["Address"]}
                  bind:value={groupToAdd}
                  on:input={addGroup}
                  placeholder="Choose an entity..."
                />
              </div>
            {/if}
          </div>
        </section>
      {/if}
      <div class="blob-viewer">
        <BlobViewer
          {address}
          {editable}
          {detail}
          on:handled={(ev) => (blobHandled = ev.detail)}
        />
      </div>
      <NotesEditor {address} {editable} on:change={onChange} />
      {#if !$error}
        {#if Boolean($allTypeEntries)}
          <div class="attributes">
            {#each Object.entries(typedAttributes) as [typeAddr, entries] (typeAddr)}
              <AttributeView
                {entries}
                type={allTypes[typeAddr]}
                {editable}
                on:change={onChange}
                initialWidget={String($entity.get("LAST_ATTRIBUTE_WIDGET"))}
                on:widgetSwitched={onAttributeWidgetSwitch}
              />
            {/each}

            {#if currentUntypedAttributes.length > 0 || editable}
              <AttributeView
                title={$i18n.t("Other attributes")}
                {editable}
                entries={currentUntypedAttributes}
                on:change={onChange}
              />
            {/if}

            {#if currentBacklinks.length > 0}
              <AttributeView
                title={`${$i18n.t("Referred to")} (${
                  $entity.backlinks.length
                })`}
                entries={currentBacklinks}
                reverse
                on:change={onChange}
              />
            {/if}

            {#if $entityInfo?.t === "Attribute"}
              <div class="buttons">
                <div class="button">
                  <Link to="/surface?x={$entityInfo.c}">
                    {$i18n.t("Surface view")}
                  </Link>
                </div>
              </div>

              <section class="labelborder">
                <header>
                  <h3>{$i18n.t("Used")} ({attributesUsed.length})</h3>
                </header>
                <EntryList
                  columns="entity,value"
                  columnWidths={["auto", "33%"]}
                  entries={attributesUsed}
                  orderByValue
                />
              </section>
            {/if}
          </div>

          {#if editable}
            <div class="button" on:click={deleteObject}>
              <Icon name="trash" />
            </div>
          {/if}
        {:else}
          <Spinner centered />
        {/if}
      {:else}
        <div class="error">
          {$error}
        </div>
      {/if}
    </div>
  </div>
</div>

<style scoped lang="scss">
  @use "./util";

  header h2 {
    margin-bottom: 0;
  }

  .inspect,
  .main-content {
    flex: auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 0;
  }

  .groups {
    margin: 0.25rem 0;
    .content {
      display: flex;
      flex-wrap: wrap;
      gap: 0.5rem 0.5rem;
      align-items: center;
    }

    .group {
      display: inline-flex;
      align-items: center;
    }

    .selector {
      width: 100%;
    }
  }

  .attributes {
    flex: auto;
    height: 0; // https://stackoverflow.com/a/14964944
    min-height: 12em;
    overflow-y: auto;
  }

  @media screen and (max-height: 1080px) {
    .main-content {
      overflow-y: auto;
      // min-height: 0;
    }

    .attributes {
      height: unset;
      min-height: unset;
      overflow-y: unset;
    }
  }

  .inspect.detail {
    .main-content {
      position: relative;
      flex-direction: row;
      justify-content: end;
    }

    .blob-viewer {
      width: 73%;
      height: 100%;
      position: absolute;
      left: 1%;
      top: 0;
    }

    .detail-col {
      width: 25%;
    }

    &.blob {
      .detail-col {
        flex-grow: 0;
      }
    }
  }

  .main-content .detail-col {
    display: flex;
    flex-direction: column;
    flex-grow: 1;
  }

  .buttons {
    display: flex;
  }

  .error {
    color: red;
  }
</style>
