<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 Icon from "./utils/Icon.svelte";
  import BlobViewer from "./display/BlobViewer.svelte";
  import { i18n } from "../i18n";
  import EntryList from "./widgets/EntryList.svelte";
  import api from "../lib/api";
  const dispatch = createEventDispatcher();
  const params = useParams();

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

  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") {
      api
        .query(`(matches ? "${$entityInfo.c}" ?)`)
        .then((result) => (attributesUsed = result.entries));
    }
  }

  async function onChange(ev: CustomEvent<AttributeChange>) {
    const change = ev.detail;
    switch (change.type) {
      case "create":
        await api.putEntry({
          entity: address,
          attribute: change.attribute,
          value: change.value,
        });
        break;
      case "delete":
        await api.deleteEntry(change.address);
        break;
      case "update":
        await api.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 api.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 api.deleteEntry(groupAddress);
    revalidate();
  }

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

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

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

      await api.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>
  {#if !showAsEntries}
    <div class="main-content">
      <div class="detail-col">
        <div class="blob-viewer">
          <BlobViewer
            {address}
            {editable}
            {detail}
            on:handled={(ev) => (blobHandled = ev.detail)}
          />
        </div>
        <NotesEditor {address} {editable} on:change={onChange} />
        {#if !$error}
          {#if Object.keys(allTypes).length || groups.length}
            <section class="tags labelborder">
              <header><h3>{$i18n.t("Tags")}</h3></header>
              <div class="content">
                {#each Object.values(allTypes) as type}
                  <div
                    class="tag type"
                    on:mouseenter={() => (highlightedType = type.address)}
                    on:mouseleave={() => (highlightedType = undefined)}
                  >
                    <UpObject address={type.address} link />
                    {#if editable}
                      <IconButton name="x-circle" />
                    {/if}
                  </div>
                {/each}
                {#each groups as [entryAddress, address]}
                  <div class="tag 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}
          {#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}
                  highlighted={highlightedType == typeAddr}
                />
              {/each}

              {#if currentUntypedAttributes.length > 0 || editable}
                <AttributeView
                  title={$i18n.t("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>
  {:else}
    <div class="entries">
      <h2>{$i18n.t("Attributes")}</h2>
      <EntryList
        {editable}
        entries={$entity.attributes}
        columns={detail
          ? "timestamp, provenance, attribute, value"
          : "attribute, value"}
      />
      <h2>{$i18n.t("Backlinks")}</h2>
      <EntryList
        entries={$entity.backlinks}
        columns={detail
          ? "timestamp, provenance, entity, attribute"
          : "entity, attribute"}
      />
    </div>
  {/if}
  <div class="footer">
    <IconButton
      name="detail"
      title="Show as entries"
      active={showAsEntries}
      on:click={() => (showAsEntries = !showAsEntries)}
    />
  </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;
  }

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

    .tag {
      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;
  }

  .entries {
    flex-grow: 1;
  }

  .footer {
    display: flex;
    justify-content: end;
  }

  .buttons {
    display: flex;
  }

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