<!--
@component

An interactive UI that sits in the header row of a table and allows the user to
quickly add a new field based on an existing column or configure a field backed
by a new column to be added to the relation in the database.

Note: The form interface is implemented with a literal HTML <form> element with
method="post", meaning that the parent page is reloaded upon field creation. It
is not necessary to update the table DOM dynamically upon successful form
submission.
-->

<!--TODO: disable new column creation if the relation is a Postgres view.-->
<!--TODO: display a warning if column name already exists and its type is
incompatible with the current presentation configuration.-->

<script lang="ts">
  import FieldDetails from "../field-details.svelte";
  import {
    all_presentation_tags,
    type Presentation,
  } from "../presentation.svelte";

  type Assert<_T extends true> = void;

  type Props = {
    /**
     * An array of all existing column names visible in the current relation,
     * to the current user. This is used to populate the autocomplete combobox.
     */
    columns?: {
      name: string;
      regtype: string;
    }[];
  };

  let { columns = [] }: Props = $props();

  let expanded = $state(false);
  let name_customized = $state(false);
  let name_value = $state("");
  let label_value = $state("");
  let presentation_customized = $state(false);
  let presentation_value = $state<Presentation | undefined>(
    get_empty_presentation("Text"),
  );
  let popover_element = $state<HTMLDivElement | undefined>();
  let search_input_element = $state<HTMLInputElement | undefined>();
  // Hacky workaround because as of September 2025 implicit anchor association
  // is still pretty broken, at least in Firefox.
  let anchor_name = $state(`--anchor-${Math.floor(Math.random() * 1000000)}`);

  // If the database-friendly column name has not been explicitly set, keep it
  // synchronized with the human-friendly field label as typed in the table
  // header cell.
  $effect(() => {
    if (!name_customized) {
      // FIXME: apply transformations to make SQL-friendly
      name_value = label_value
        .toLocaleLowerCase("en-US")
        .replace(/\s+/g, "_")
        .replace(/^[^a-z]/, "_")
        .replace(/[^a-z0-9]/g, "_");
    }
  });

  // When the field name as entered corresponds to an existing column's name,
  // the presentation state is updated to the default for that column's Postgres
  // type, *unless* the presentation has already been customized by the user.
  $effect(() => {
    if (!presentation_customized) {
      // TODO: Should this normalize the comparison to lowercase?
      const regtype =
        columns.find(({ name }) => name === name_value)?.regtype ?? "";
      const presentation_tag = presentation_tags_by_regtype[regtype]?.[0];
      console.info(`Determining default presentation tag for ${regtype}.`);
      if (presentation_tag) {
        presentation_value = get_empty_presentation(presentation_tag);
      }
    }
  });

  // TODO: Move to presentation.svelte.rs
  // Elements of array are compatible presentation tags; first element is the
  // default.
  const presentation_tags_by_regtype: Readonly<
    Record<string, ReadonlyArray<(typeof all_presentation_tags)[number]>>
  > = {
    text: ["Text", "Dropdown"],
    "timestamp with time zone": ["Timestamp"],
    uuid: ["Uuid"],
  };

  function get_empty_presentation(
    tag: (typeof all_presentation_tags)[number],
  ): Presentation {
    if (tag === "Dropdown") {
      return { t: "Dropdown", c: { allow_custom: true, options: [] } };
    }
    if (tag === "Numeric") {
      return { t: "Numeric", c: {} };
    }
    if (tag === "Text") {
      return { t: "Text", c: { input_mode: { t: "SingleLine", c: {} } } };
    }
    if (tag === "Timestamp") {
      return { t: "Timestamp", c: { format: "yyyy-MM-dd'T'HH:mm:ssXXX" } };
    }
    if (tag === "Uuid") {
      return { t: "Uuid", c: {} };
    }
    type _ = Assert<typeof tag extends never ? true : false>;
    throw new Error("this should be unreachable");
  }

  function toggle_expanded() {
    expanded = !expanded;
    if (expanded) {
      // Delay focus call until next frame so that the element has an
      // opportunity to render.
      setTimeout(() => {
        search_input_element?.focus();
      }, 0);
    }
  }

  function handle_name_input() {
    name_customized = true;
  }
</script>

<div class="field-adder">
  <div
    class={[
      "field-adder__header-lookalike",
      expanded && "field-adder__header-lookalike--visible",
    ]}
    style:anchor-name={anchor_name}
  >
    <input
      bind:this={search_input_element}
      bind:value={label_value}
      class="field-adder__label-input"
      onclick={() => {
        popover_element?.showPopover();
      }}
      oninput={() => {
        popover_element?.showPopover();
      }}
      onkeydown={(ev) => {
        if (ev.key === "Escape") {
          toggle_expanded();
        }
      }}
      type="text"
    />
    <form method="post" action="add-field">
      <div
        bind:this={popover_element}
        class="popover field-adder__popover"
        popover="auto"
        style:position-anchor={anchor_name}
      >
        <div class="field-adder__completions" role="listbox">
          {#each columns
            .map(({ name }) => name)
            .filter((name) => name
                  .toLocaleLowerCase("en-US")
                  .includes(label_value.toLocaleLowerCase("en-US")) || name
                  .toLocaleLowerCase("en-US")
                  .includes(name_value.toLocaleLowerCase("en-US"))) as completion}
            <button
              aria-selected={name_value === completion}
              onclick={() => {
                if (!name_customized) {
                  label_value = completion;
                }
                name_value = completion;
                search_input_element?.focus();
              }}
              role="option"
              type="button"
            >
              {completion}
            </button>
          {/each}
        </div>
        <div class="field-adder__configs">
          {#if presentation_value}
            <FieldDetails
              bind:name_value
              bind:label_value
              bind:presentation={presentation_value}
              on_name_input={handle_name_input}
              on_presentation_input={() => {
                presentation_customized = true;
              }}
            />
          {/if}
          <div class="form__buttons">
            <button class="button button--primary" type="submit">Create</button>
          </div>
        </div>
      </div>
    </form>
  </div>

  <div class="field-adder__summary-buttons">
    <button
      aria-label="toggle field adder"
      class="button button--clear"
      onclick={toggle_expanded}
      type="button"
    >
      <i class="ti ti-{expanded ? 'x' : 'plus'}"></i>
    </button>
  </div>
</div>

<style lang="css">
</style>
