<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 BlobPreview from "./display/BlobPreview.svelte";
  import { 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 { useParams } from "svelte-navigator";
  const params = useParams();

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

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

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

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

  $: ({ entity, 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"].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

  async function onChange(ev: CustomEvent<AttributeChange>) {
    const change = ev.detail;
    switch (change.type) {
      case "create":
        await fetch(`api/obj`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            entity: address,
            attribute: change.attribute,
            value: change.value,
          }),
        });
        break;
      case "delete":
        await fetch(`api/obj/${change.address}`, { method: "DELETE" });
        break;
      case "update":
        await fetch(`api/obj/${address}/${change.attribute}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(change.value),
        });
        break;
      default:
        console.error("Unimplemented AttributeChange", change);
        return;
    }
    revalidate();
  }

  let groupToAdd: IValue | undefined;
  async function addGroup() {
    if (!groupToAdd) {
      return;
    }
    await fetch(`api/obj`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        entity: String(groupToAdd.c),
        attribute: "HAS",
        value: {
          t: "Address",
          c: address,
        },
      }),
    });
    revalidate();
    groupToAdd = undefined;
  }

  async function removeGroup(address: string) {
    await fetch(`api/obj/${address}`, { method: "DELETE" });
    revalidate();
  }
</script>

<div class="inspect">
  <header>
    <h2>
      {#if $entity}
        <UpObject banner {address} on:resolved />
      {:else}
        <Spinner centered />
      {/if}
    </h2>
    {#if groups?.length || editable}
      <section class="groups labelborder">
        <header><h3>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}
  </header>
  <BlobPreview {address} {editable} {detail} />
  <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}
          />
        {/each}

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

        {#if currentBacklinks.length > 0}
          <AttributeView
            title={`Referred to (${$entity.backlinks.length})`}
            entries={currentBacklinks}
            reverse
            on:change={onChange}
          />
        {/if}
      </div>
    {:else}
      <Spinner centered />
    {/if}
  {:else}
    <div class="error">
      {$error}
    </div>
  {/if}
</div>

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

  .inspect {
    flex: auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;

    h2 {
      margin-bottom: 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
    overflow-y: auto;
  }

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