<script setup lang="ts">
import { ref, watchEffect, watch, computed } from 'vue'

import { color } from '~/composables/color'

import Pill from './Pill.vue'
import Layout from './Layout.vue'

const props = defineProps<{
  icon?: string,
  placeholder?: string,
  label?: string,
}>()

const model = defineModel<{
  current: string[],
  others?: string[],
  custom?: string[],
}>({ required: true })

const whenInteractive = (then:() => void) => {
  if (!model.value.others) return; then()
}

const editingValue = ref('')
const additionalValue = ref('')
const componentKey = ref(0)
const forceRerender = () => componentKey.value++

const isStatic = computed(() =>
  !model.value.others
)

const selectedLabel = ref('+')

// Dropdown changed -> select label

whenInteractive(() =>
  watchEffect(() => {
    if (!model.value.others) return
    const newLabel = selectedLabel.value
    selectedLabel.value = '+'
    if (!newLabel || newLabel === '+') return
    if (!model.value.current.includes(newLabel)) {
      model.value.current.push(newLabel)
      model.value.others = model.value.others.filter(value => value !== newLabel)
    }
  })
)

// Pill clicked --> edit or unselect label

const pillClicked = (value: string) => {
  model.value.custom?.includes(value)
    ? edit(value)
    : unselect(value)
}

const edit = (value: string) => {
  editingValue.value = value
}

const unselect = (value: string) => {
  model.value = {
    ...model.value,
    current: model.value.current.filter(v => v !== value),
    others: [value, ...(model.value.others || [])]
  }
}

// Editing value changed --> remove, add or replace a label

const remove = (value: string) => {
  model.value = {
    ...model.value,
    current: model.value.current.filter(v => v !== value),
    custom: model.value.custom?.filter(v => v !== value)
  }
}

const add = (value: string) => {
  if (model.value.current.includes(value)) return
  model.value = {
    ...model.value,
    current: [...model.value.current, value],
    custom: [...(model.value.custom || []), value]
  }
  additionalValue.value = ''

  // We have to force rerender because else, Vue keeps the previous additionalValue for the new "additionalValue" input pill :-(
  forceRerender()
}

const replace = (value: string) => {
  model.value = {
    ...model.value,
    current: model.value.current.map(v => v === value ? editingValue.value : v),
    custom: model.value.custom?.map(v => v === value ? editingValue.value : v)
  }
}

watch(editingValue, (newValue, oldValue) => {
  if (oldValue === '') return
  if (newValue === '') {
    remove(oldValue)
  } else {
    replace(oldValue)
  }
})

watch(additionalValue, (newValue, oldValue) => {
  if (newValue !== '') {
    additionalValue.value = ''
    add(newValue)
  }
})

// Remove duplicates

const unique = (a:string[]) => [...new Set(a)]

watch(model, () => {
  model.value.current = unique(model.value.current)
  model.value.others = model.value.others ? unique(model.value.others) : undefined
  model.value.custom = model.value.custom ? unique(model.value.custom) : undefined
})
</script>

<template>
  <Layout
    stack
    no-gap
    label
    :class="$style.pills"
    for="dropdown"
  >
    <!-- Label -->
    <span
      v-if="$slots['label']"
      :class="$style.label"
    >
      <slot name="label" />
    </span>
    <span
      v-if="props.label"
      :class="$style.label"
    >
      {{ props.label }}
    </span>

    <!-- List of Pills -->

    <Layout
      flex
      no-gap
      v-bind="color({}, ['solid', 'default', 'secondary'])()"
      :class="$style.list"
    >
      <!-- Add predefined or previously unselected pill -->

      <select
        v-if="model.others"
        v-model="selectedLabel"
        name="dropdown"
        :class="$style.dropdown"
        @change="e => { (e.target as HTMLInputElement).value='+' }"
      >
        <option value="+" />
        <option
          v-for="value in model.others"
          :key="value"
          :value="value"
        >
          {{ value }}
        </option>
      </select>
      <template
        v-for="value in model.current"
        :key="value"
      >
        <!-- List of current pills -->

        <Pill
          v-if="value !== editingValue"
          outline
          raised
          no-underline
          :class="[$style.pill, $style[isStatic ? 'static' : model.custom?.includes(value) ? 'custom' : 'preset']]"
          @click="!isStatic && pillClicked(value)"
        >
          <span :class="$style['pill-content']">{{ value }}</span>
        </Pill>

        <Pill
          v-if="value === editingValue"
          v-model="editingValue"
          outline
          raised
          no-underline
          autofocus
          :class="[$style.pill, $style.custom]"
          @click="!isStatic && pillClicked(value)"
        />
      </template>

      <!-- Empty pill to add custom label -->

      <!-- TODO: Add error state (or mitigation) if new label is already in `custom[]` -->

      <Pill
        v-if="model.custom"
        :key="componentKey"
        v-model="additionalValue"
        solid
        no-underline
        style="margin-right: 40px; height:32px; flex-grow: 1;"
      />
    </Layout>
  </Layout>
</template>

<style module lang="scss">
  .pills {
    >.label {
      margin-top: -18px;
      padding-bottom: 8px;
      font-size: 14px;
      font-weight: 600;
    }
    >.list {
      position: relative;

      // Compensation for round shapes -> https://en.wikipedia.org/wiki/Overshoot_(typography)
      margin: 0 -4px;

      padding:4px;
      border-radius: 24px;

      min-height: 48px;
      min-width: 160px;

      > .pill {
        margin: 4px;
        padding: 2px;
        &.static {
          text-decoration: none;
        }
        &.preset {
          &:is(:hover, :focus-visible) .pill-content {
            text-decoration: line-through;
          }
          .pill-content::after{
            content:'×';
            margin-left: 8px;
          }
        }
        &.custom {
          text-decoration: none;
        }

      }
      >.dropdown{
        position: absolute;
        inset: 0;

        border-radius: 15px;
        padding: 2px 11.25px;
        text-align: right;
        background: transparent;
        appearance: auto;
        margin-right: 12px;

        // From vitepress default, needed for app
        border: 0;
        color: inherit;
      }
    }
    &:hover:has(select)>.list {
      box-shadow: inset 0 0 0 4px var(--border-color)
    }
    :has(>select:focus) {
      box-shadow: inset 0 0 0 4px var(--focus-ring-color)
    }
  }
</style>
