﻿<template>
  <div class="ui-pick" :class="{'is-disabled': disabled, 'is-combined': configuration.preview.combined }">
    
    <!-- previews -->
    <div class="ui-pick-previews" v-if="configuration.preview.enabled && previews.length > 0 && !configuration.preview.combined" v-sortable="{ onUpdate: onSortingUpdated, enabled: configuration.sortable }">
      <div v-for="preview in previews" :key="preview[configuration.keys.id]" class="ui-pick-preview">
        <ui-select-button :icon="getPreviewIcon(preview)" :icon-as-image="configuration.preview.iconAsImage" :label="preview[configuration.keys.name]" :description="getPreviewDescription(preview)" :disabled="disabled" @click="pick(preview[configuration.keys.id])" :tokens="preview" />
        <ui-icon-button v-if="!disabled && configuration.preview.delete" @click="remove(preview[configuration.keys.id])" icon="fth-x" title="@ui.close" :size="14" />
      </div>
    </div>

    <!-- combined preview -->
    <div class="ui-pick-previews" v-if="configuration.preview.enabled && configuration.preview.combined">
      <div class="ui-pick-preview">
         <ui-select-button icon="fth-check-circle" :label="combinedTitle" :disabled="disabled" @click="pick()" />
      </div>
    </div>

    <!-- add button -->
    <div class="ui-pick-add" v-if="canAdd && configuration.addButton.enabled && !configuration.preview.combined">
      <slot name="add">
        <ui-select-button icon="fth-plus" :label="configuration.addButton.label" @click="pick()" :disabled="disabled" />
      </slot>
    </div>    

    <!-- overlay -->
    <ui-dropdown ref="overlay" class="ui-pick-overlay" @opened="overlayOpened" :theme="configuration.theme">

      <!-- headline -->
      <div class="ui-pick-overlay-head">
        <div class="ui-pick-overlay-head-title">
          <span class="-name" v-localize="title"></span>
          <span class="-max" v-if="configuration.limit > 1" v-localize="{ key: '@ui.pick.max', tokens: { count: configuration.limit }}"></span>
        </div>
        <ui-icon-button @click="hide" icon="fth-x" title="@ui.close" />
      </div>

      <!-- search -->
      <ui-search v-if="configuration.search.enabled" ref="search" class="ui-pick-overlay-search" :value="searchValue" @input="onSearch" @submit="onSearchSubmit">
        <template v-slot:button="search" v-if="configuration.autocomplete">
          <button type="button" class="ui-searchinput-button" v-localize:title="'@ui.search.button'" @click="search.onSubmit"><ui-icon symbol="fth-check"></ui-icon></button>
        </template>
      </ui-search>

      <!-- items -->
      <div class="ui-pick-overlay-items">
        <button v-for="item in items" :key="item[configuration.keys.id]" type="button" class="ui-pick-overlay-item" @click="select(item)" :class="{'is-selected': isSelected(item) }">
          <ui-icon v-if="item[configuration.keys.icon] && !configuration.list.iconAsImage" class="-icon" :symbol="item[configuration.keys.icon]" />
          <span v-else="item[configuration.keys.icon]" class="-image">
            <ui-thumbnail :media="item[configuration.keys.icon]" :alt="item[configuration.keys.name]" />
          </span>
          <div class="ui-pick-overlay-item-title">
            <span class="-name" v-localize="item[configuration.keys.name]"></span>
            <span v-if="item[configuration.keys.description] && configuration.list.description" class="-text" v-localize="item[configuration.keys.description]"></span>
          </div>
        </button>
      </div>

      <!-- loading + empty states -->
      <div class="ui-pick-overlay-center" v-if="isLoading || (!configuration.autocomplete && !items.length)">
        <div v-if="!isLoading && !configuration.autocomplete && !items.length" class="ui-pick-overlay-message">
          <ui-icon class="ui-pick-overlay-message-icon" symbol="fth-list" :size="18" />
          No items found
        </div>
        <ui-loading v-if="isLoading" />
      </div>
    </ui-dropdown>
  </div>
</template>


<script lang="ts">
  import { arrayMove, debounce, extendObject } from '../utils';

  const defaultConfig = {
    // picker items, can either be a static list or a promise
    items: [],
    // preview items, can either be a static list or a promise. If null, take the items list
    previews: [],
    // exclude Ids from the picker selection items
    excludedIds: [],
    // autocomplete allows entering custom texts
    autocomplete: false,  
    // maximum selection count
    limit: 10,
    // multiple selection
    multiple: true,
    // whether the previews/results are sortable or not
    sortable: true,
    // close picker when an item is clicked
    closeOnClick: true,
    // title in dropdown
    title: null,
    // automatically open picker
    autoOpen: false,
    // picker has pagination
    paging: false,
    // theme for the dropdown
    theme: 'dark',

    keys: {
      // id key
      id: 'id',
      // name key
      name: 'name',
      // description key
      description: 'text',
      // icon key
      icon: 'icon'
    },

    addButton: {
      // hide the add button
      enabled: true,
      // text key for the add button
      label: '@ui.select'
    },

    list: {
      // output description text if available (second line)
      description: false,
      // displays an image instead of an icon in the list
      iconAsImage: false
    },

    preview: {
      // output previews
      enabled: true,
      // output all selected items in one line (comma-separated)
      combined: false,
      // prefixed title when combine=true
      combinedTitle: null,
      // default icon used for previews
      defaultIcon: 'fth-square',
      // hides the icon in the preview
      icon: true,
      // hides the delete icon in preview
      delete: true,
      // displays an image instead of an icon in the preview
      iconAsImage: false,
      // output description text if available (second line)
      description: true
    },

    search: {
      // hides the search input
      enabled: true,
      // can force a local search for remote items (via promise)
      local: true,
      // sets the current model as the search input when opened
      setDefaultValue: false,
      // focus search input on open
      focus: false,
      // placeholder key in search input
      placeholder: null //vm.options.autocomplete ? 'ui_search_autocomplete' : 'ui_search',
    }
  };

  export default {
    name: 'uiPick',

    props: {
      value: {
        type: [String, Array],
        default: null
      },
      disabled: {
        type: Boolean,
        default: false
      },
      config: {
        type: Object,
        default: () =>
        {
          return defaultConfig;
        }
      }
    },


    watch: {
      config: {
        deep: true,
        handler(newval, oldval)
        {
          if (JSON.stringify(newval) !== JSON.stringify(oldval))
          {
            this.buildConfig();
            this.loaded = false;          
          }
        }
      },
      value(val)
      {
        this.onValueChanged(val);
      }
    },


    computed: {
      multiple()
      {
        return this.configuration.multiple;
      },
      canAdd()
      {
        return (!this.value && !this.multiple) || (!this.value || this.value.length < this.configuration.limit);
      },
      isRemote()
      {
        return typeof this.configuration.items === 'function';
      },
      title()
      {
        if (!!this.configuration.title) return this.configuration.title;
        if (this.configuration.autocomplete) return '@ui.pick.title_autocomplete';
        if (this.multiple) return '@ui.pick.title_multiple';
        return '@ui.pick.title';
      },
      combinedTitle()
      {
        let html = this.configuration.preview.combinedTitle ? this.configuration.preview.combinedTitle : '';

        if (this.previews.length > 0)
        {
          html += ': <b>' + this.previews.map(p => p[this.configuration.keys.name]).join(', ') + '</b>';
        }

        return html;
      }
    },


    data: () => ({
      configuration: {},
      previews: [],
      allItems: [],
      items: [],
      selected: [],
      loaded: false,
      isLoading: false,
      debouncedUpdate: null,
      searchValue: ''
    }),


    created()
    {
      this.buildConfig();
      this.debouncedUpdate = debounce(this.loadSuggestions, 300);
      this.onValueChanged(this.value);
    },


    mounted()
    {
      if (this.configuration.autoOpen)
      {
        this.pick();
      }
    },


    methods: {

      refresh()
      {
        this.buildConfig();
        this.loaded = false;
      },

      onValueChanged(val)
      {
        if (this.multiple)
        {
          this.selected = Array.isArray(val) && val.length ? JSON.parse(JSON.stringify(val)) : [];
        }
        else
        {
          this.selected = val ? [val] : [];
        }

        this.loadPreviews();
      },


      buildConfig()
      {
        var config = JSON.parse(JSON.stringify(defaultConfig));
        this.configuration = extendObject(JSON.parse(JSON.stringify(config)), this.config);
        this.configuration.search = extendObject(config.search, this.config.search || {});
        this.configuration.addButton = extendObject(config.addButton, this.config.addButton || {});
        this.configuration.preview = extendObject(config.preview, this.config.preview || {});
        this.configuration.list = extendObject(config.list, this.config.list || {});
        this.configuration.keys = extendObject(config.keys, this.config.keys || {});
      },


      overlayOpened()
      {
        if (!this.loaded)
        {
          this.load();
        }
        if (this.configuration.search.enabled && this.configuration.search.focus)
        {
          this.$nextTick(() => this.$refs.search.focus());
        }
      },


      pick()
      {
        this.$refs.overlay.toggle();

        if (!this.loaded)
        {
          this.load();
        }
      },


      hide()
      {
        this.$refs.overlay.hide();
      },


      remove(id)
      {
        let index = this.selected.indexOf(id);
        this.selected.splice(index, 1);
        this.onChange(this.multiple ? this.selected : null);
      },


      clear()
      {
        this.selected = [];
        this.onChange(this.multiple ? this.selected : null);
      },


      // loads remote items or items from the given configuration
      // this will store both all items and the reduced search results
      load()
      {
        if (this.isLoading)
        {
          return;
        }

        this.isLoading = true;
        this.allItems = [];

        let onLoaded = (items) =>
        {
          if (this.configuration.paging && typeof items === 'object' && !Array.isArray(items))
          {
            // TODO we need to store metadata to allow pagination
            items = items.items;
          }
          this.allItems = items;
          this.loadSuggestions();
          this.loaded = true;
          this.isLoading = false;
        };

        if (this.isRemote)
        {
          this.configuration.items().then(onLoaded);
        }
        else
        {
          onLoaded(this.configuration.items);
        }
      },


      loadSuggestions()
      {
        let items = [];
        let search = this.searchValue;

        let handleResult = (res) =>
        {
          if (this.configuration.paging && typeof res === 'object' && !Array.isArray(res))
          {
            // TODO we need to store metadata to allow pagination
            res = res.items;
          }

          if (this.configuration.excludedIds && this.configuration.excludedIds.length)
          {
            res = res.filter((item) => this.configuration.excludedIds.indexOf(item[this.configuration.keys.id]) < 0);
          }
         
          this.items = res;
        };

        if (!search)
        {
          items = this.allItems;
        }
        else
        {
          if (!this.isRemote || this.configuration.search.local)
          {
            items = this.allItems.filter((item) => item.name.toLowerCase().indexOf(search) > -1);
          }
          else if (this.isRemote)
          {
            this.configuration.items(search).then(res => handleResult(res));
            return;
          }
        }

        handleResult(items);
      },


      loadPreviews()
      {
        let onLoaded = (items, needsFilter) =>
        {
          if (this.configuration.paging && typeof items === 'object' && !Array.isArray(items))
          {
            // TODO we need to store metadata to allow pagination
            items = items.data;
          }

          if (needsFilter)
          {
            this.previews = [];
            this.selected.forEach(id =>
            {
              let res = items.find(item => item[this.configuration.keys.id] === id);

              if (!res)
              {
                // TODO push error output
              }
              else
              {
                this.previews.push(JSON.parse(JSON.stringify(res)));
              }
            });
          }
          else
          {
            this.previews = items;
          }

          //console.info(JSON.parse(JSON.stringify(this.previews)));
        };

        if (this.configuration.autocomplete)
        {
          onLoaded(this.selected.map(s =>
          {
            let item = { };
            item[this.configuration.keys.id] = s;
            item[this.configuration.keys.name] = s;
            return item;
          }), false);
        }
        else
        {
          let promise = (Array.isArray(this.configuration.previews) && this.configuration.previews.length) || typeof this.configuration.previews === 'function' ? this.configuration.previews : this.configuration.items;
          let isFunc = typeof promise === 'function';

          if (isFunc && this.selected.length > 0)
          {
            promise(this.selected).then(onLoaded);
          }
          else if (isFunc)
          {
            onLoaded([]);
          }
          else
          {
            onLoaded(promise, true);
          }
        }
      },


      onSearch(value)
      {
        this.searchValue = value;
        if (!this.isRemote || this.configuration.search.local)
        {
          this.loadSuggestions();
        }
        else
        {
          this.debouncedUpdate();
        }
      },


      onSearchSubmit()
      {
        let value = this.searchValue.trim();
        let item = {};
        item[this.configuration.keys.id] = value;
        item[this.configuration.keys.name] = value;
        this.select(item);
      },


      select(item)
      {
        let value = this.configuration.autocomplete ? item[this.configuration.keys.name] : item[this.configuration.keys.id];

        if (this.multiple)
        {
          if (!this.canAdd)
          {
            if (this.limit > 1)
            {
              return;
            }

            this.selected = [];
          }

          var index = this.selected.indexOf(value);

          if (index > -1)
          {
            this.selected.splice(index, 1);
            this.onDeselected(value, index);
          }
          else
          {
            this.selected.push(value);
            this.onSelected(value, item);
          }

          this.onChange(this.selected);
        }
        else
        {
          this.selected = [value];
          this.onChange(value);
          this.onSelected(value, item);
        }
      },


      onSelected(value, item)
      {
        this.$emit('select', value, item);
      },


      onDeselected(value, index)
      {
        this.$emit('deselect', value, index);
      },


      onChange(value)
      {
        this.$emit('input', value);
        this.$emit('change', value);

        if (this.configuration.closeOnClick)
        {
          this.$refs.overlay.hide();
        }
      },


      isSelected(item)
      {
        let value = this.configuration.autocomplete ? item[this.configuration.keys.name] : item[this.configuration.keys.id];
        return this.selected.indexOf(value) > -1;
      },


      getPreviewIcon(preview)
      {
        return this.configuration.preview.icon ? (preview[this.configuration.keys.icon] || this.configuration.preview.defaultIcon) : null;
      },


      getPreviewDescription(preview)
      {
        return this.configuration.preview.description ? preview[this.configuration.keys.description] : null;
      },


      onSortingUpdated(ev)
      {
        this.selected = arrayMove(this.selected, ev.oldIndex, ev.newIndex);
        this.onChange(this.multiple ? this.selected : this.selected[0]);
      }
    }
  }
</script>

<style lang="scss">
  .ui-pick-overlay-search
  {
    margin: 15px 15px 5px;

    .ui-input
    {
      min-width: 0;
    }
  }

  .theme-dark .ui-pick-overlay-search .ui-input
  {
    background: var(--color-bg-shade-1);
    box-shadow: none !important;
  }

  .ui-pick-overlay 
  {
    .ui-dropdown
    {
      min-width: 0;
      width: 100%;
      max-width: 380px;
    }
  }

  .ui-pick-overlay-center
  {
    display: flex;
    justify-content: center;
    margin: 30px 0;
  }

  .ui-pick-overlay-head
  {
    padding: 15px 15px 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;

    .ui-icon-button
    {
      background: none;
    }
  }

  .ui-pick-overlay-head-title
  {
    font-size: var(--font-size-l);
    font-weight: 600;

    .-max
    {
      font-size: var(--font-size-s);
      font-weight: 400;
      color: var(--color-text-dim);
      margin-left: .6em;
    }
  }

  .ui-pick-overlay-message
  {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 0 15px;
  }

  .ui-pick-overlay-message-icon
  {
    font-size: 18px;
    margin-bottom: 10px;
  }

  .ui-pick-overlay-items
  {
    margin: 15px 0;
    max-height: 290px;
    overflow-y: auto;
  }

  .ui-pick-overlay-item
  {
    display: grid;
    grid-template-columns: auto 1fr;
    padding: 10px 18px;
    min-height: 48px;
    width: 100%;
    border-radius: var(--radius);
    align-items: center;
    transition: background .2s, transform .2s, opacity .2s;
    position: relative;

    &:hover
    {
      background: var(--color-dropdown-selected);
    }

    &.is-selected
    {
      padding-right: 40px;

      .-name
      {
        font-weight: 600;
      }
    }

    &.is-selected:after
    {
      font-family: "Feather";
      content: "\e83e";
      font-size: 16px;
      color: var(--color-primary);
      position: absolute;
      right: 20px;
    }

    .-icon
    {
      margin-right: var(--padding-s);
    }

    .-image
    {
      display: inline-flex;
      justify-content: center;
      width: 24px;
      height: 24px;
      margin-right: var(--padding-xs);

      img
      {
        max-width: 100%;
        max-height: 100%;
      }
    }
  }

  .ui-pick-overlay-item-title
  {
    .-name
    {
      display: block;
    }

    .-text
    {
      display: block;
      color: var(--color-text-dim);
      font-size: var(--font-size-s);
      margin-top: 3px;
    }
  }

  .ui-pick-preview
  {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .ui-pick-preview + .ui-pick-preview,
  .ui-pick-previews + .ui-select-button,
  .ui-pick-previews + .ui-pick-add
  {
    margin-top: 10px;
  }

  .ui-pick.is-combined .ui-select-button-label
  {
    font-weight: 400;
  }

  .ui-pick-previews
  {
    .ui-icon-button 
    {
      height: 24px;
      width: 24px;
      flex-shrink: 0; 

      .ui-button-icon
      {
        font-size: 13px;
      }
    }
  }
</style>