<template>
  <teleport to="body">
    <modal
      @close="() => emit('close')"
      :modal-dialog-class="modalDialogClass"
      :hide-footer="false"
    >
      <template #header>
        <h3 class="modal-title">{{ modalTitle }}</h3>
      </template>

      <template #body-head>
        <div class="modal-body">
          <div class="search">
            <label class="col-form-label" style="float: right">
              {{
                trans(ADD_PERSONS_SUGGESTED_COUNTER, {
                  count: suggestedCounter,
                })
              }}
            </label>

            <input
              id="search-persons"
              name="query"
              v-model="query"
              :placeholder="trans(ADD_PERSONS_SEARCH_SOME_PERSONS)"
              ref="searchRef"
            />
            <i class="fa fa-search fa-lg" />
            <i
              class="fa fa-times"
              v-if="queryLength >= 3"
              @click="resetSuggestion"
            />
          </div>
        </div>

        <div class="modal-body" v-if="checkUniq === 'checkbox'">
          <div class="count">
            <span>
              <a v-if="suggestedCounter > 2" @click="selectAll">
                {{ trans(ACTION_CHECK_ALL) }}
              </a>
              <a v-if="selectedCounter > 0" @click="resetSelection">
                <i v-if="suggestedCounter > 2"> • </i>
                {{ trans(ACTION_RESET) }}
              </a>
            </span>
            <span v-if="selectedCounter > 0">
              {{
                trans(ADD_PERSONS_SELECTED_COUNTER, { count: selectedCounter })
              }}
            </span>
          </div>
        </div>
      </template>

      <template #body>
        <div class="results">
          <person-suggestion
            v-for="item in selectedAndSuggested"
            :key="item.key"
            :item="item"
            :isSelected="item.isSelected"
            :type="checkUniq"
            @update-selected="(payload) => emit('updateSelected', payload)"
            @trigger-add-contact="triggerAddContact"
          />

          <div v-if="hasNoResult">
            <div class="noResult chill-no-data-statement">
              {{
                trans(ADD_PERSONS_SUGGESTED_COUNTER, {
                  count: suggestedCounter,
                })
              }}
            </div>
          </div>

          <div
            v-if="props.allowCreate && query.length > 0"
            class="create-button"
          >
            <button
              type="button"
              class="btn btn-submit"
              @click="emit('onAskForCreate', { query })"
            >
              {{ trans(ONTHEFLY_CREATE_BUTTON, { q: query }) }}
            </button>
          </div>
        </div>
      </template>

      <template #footer>
        <button
          type="button"
          class="btn btn-create"
          @click.prevent="pickEntities"
        >
          {{ trans(ACTION_ADD) }}
        </button>
      </template>
    </modal>
  </teleport>
</template>

<script setup lang="ts">
import { ref, reactive, computed, nextTick, watch, onMounted } from "vue";
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import PersonSuggestion from "./PersonSuggestion.vue";
import { searchEntities } from "ChillPersonAssets/vuejs/_api/AddPersons";

import {
  trans,
  ADD_PERSONS_SUGGESTED_COUNTER,
  ADD_PERSONS_SEARCH_SOME_PERSONS,
  ADD_PERSONS_SELECTED_COUNTER,
  ONTHEFLY_CREATE_BUTTON,
  ACTION_CHECK_ALL,
  ACTION_RESET,
  ACTION_ADD,
} from "translator";

import type {
  Suggestion,
  Search,
  SearchOptions,
  Entities,
} from "ChillPersonAssets/types";
import { ThirdpartyCompany } from "../../../../../../ChillThirdPartyBundle/Resources/public/types";

interface Props {
  modalTitle: string;
  options: SearchOptions;
  suggested?: Suggestion[];
  selected: Map<string, Suggestion>;
  modalDialogClass?: string;
  allowCreate?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  suggested: () => [],
  modalDialogClass: "modal-dialog-scrollable modal-xl",
  allowCreate: () => true,
});

const emit = defineEmits<{
  (e: "close"): void;
  (e: "onPickEntities"): void;
  (e: "onAskForCreate", payload: { query: string }): void;
  (e: "triggerAddContact", payload: { parent: ThirdpartyCompany }): void;
  (
    e: "updateSelected",
    payload: { suggestion: Suggestion; isSelected: boolean },
  ): void;
  (e: "cleanSelected"): void;
}>();

const searchRef = ref<HTMLInputElement | null>(null);

onMounted(() => {
  // give the focus on the search bar
  searchRef.value?.focus();
});

const search = reactive({
  query: "" as string,
  previousQuery: "" as string,
  currentSearchQueryController: null as AbortController | null,
  priorSuggestion: {} as Partial<Suggestion>,
  hasPreviousQuery: false,
});

/**
 * Contains the suggested entities from the search results.
 *
 * In other words, those entities are displayed and selectable by the user
 */
const suggested = ref<Suggestion[]>([]);

const query = computed({
  get: () => search.query,
  set: (val: string) => setQuery(val),
});
const queryLength = computed(() => search.query.length);
const suggestedCounter = computed(() => suggested.value.length);
const selectedCounter = computed(() => props.selected.size);

const checkUniq = computed(() => (props.options.uniq ? "radio" : "checkbox"));

const selectedAndSuggested = computed<(Suggestion & { isSelected: boolean })[]>(
  () => {
    const selectedAndSuggested = [];

    // add selected that are not in the search results
    for (const selected of props.selected.values()) {
      if (!suggested.value.some((s: Suggestion) => s.key === selected.key)) {
        selectedAndSuggested.push({ ...selected, isSelected: false });
      }
    }
    for (const suggestion of suggested.value) {
      selectedAndSuggested.push({
        ...suggestion,
        isSelected: props.selected.has(suggestion.key),
      });
    }

    return selectedAndSuggested;
  },
);

const hasNoResult = computed(
  () => search.hasPreviousQuery && suggested.value.length === 0,
);

function setQuery(q: string) {
  search.query = q;

  if (search.currentSearchQueryController) {
    search.currentSearchQueryController.abort();
    search.currentSearchQueryController = null;
  }

  if (q === "") {
    loadSuggestions([]);
    return;
  }

  const delay = q.length > 3 ? 300 : 700;

  setTimeout(() => {
    if (q !== search.query) return;

    search.currentSearchQueryController = new AbortController();

    searchEntities(
      { query: q, options: props.options },
      search.currentSearchQueryController.signal,
    )
      .then((suggested: Search) => {
        loadSuggestions(suggested.results);
        search.hasPreviousQuery = true;
      })
      .catch((error: DOMException) => {
        if (error instanceof DOMException && error.name === "AbortError") {
          return;
        }
        throw error;
      });
  }, delay);
}

function loadSuggestions(
  suggestedArr: { relevance: number; result: Entities }[],
): void {
  suggested.value = suggestedArr.map((item) => {
    return {
      key: item.result.type + item.result.id,
      relevance: item.relevance,
      result: item.result,
    };
  });
}

function resetSuggestion() {
  search.query = "";
  suggested.value = [];
}

function resetSelection() {
  emit("cleanSelected");
}

function resetSearch() {
  resetSelection();
  resetSuggestion();
}

function selectAll() {
  suggested.value.forEach((suggestion: Suggestion) => {
    emit("updateSelected", { suggestion, isSelected: true });
  });
}

function triggerAddContact(payload: { parent: ThirdpartyCompany }) {
  emit("triggerAddContact", payload);
}

/**
 * Triggered when the user clicks on the "add" button.
 */
function pickEntities(): void {
  emit("onPickEntities");
  search.query = "";
  emit("close");
}

defineExpose({ resetSearch });
</script>

<style lang="scss" scoped>
li.add-persons {
  a {
    cursor: pointer;
  }
}

div.body-head {
  overflow-y: unset;
  div.modal-body:first-child {
    margin: auto 4em;
    div.search {
      position: relative;
      input {
        width: 100%;
        padding: 1.2em 1.5em 1.2em 2.5em;
      }
      i {
        position: absolute;
        opacity: 0.5;
        padding: 0.65em 0;
        top: 50%;
      }
      i.fa-search {
        left: 0.5em;
      }
      i.fa-times {
        right: 1em;
        padding: 0.75em 0;
        cursor: pointer;
      }
    }
  }
  div.modal-body:last-child {
    padding-bottom: 0;
  }
  div.count {
    margin: -0.5em 0 0.7em;
    display: flex;
    justify-content: space-between;
    a {
      cursor: pointer;
    }
  }
}

.create-button > button {
  margin-top: 0.5em;
  margin-left: 0.6em;
}
.noResult {
  text-align: center;
  margin: 2em;
  font-size: large;
}
</style>
