<template>
  <div>
    <div ref="dynamicTagInput" class="dynamic-tag-input" :class="{ textarea: isTypeTextarea, maxlength: hasMaxLength }">
      <schema-dynamic-field-dropdown
        v-if="dropdownOpened"
        :search-value="searchValue"
        @select="selectDynamicValue"
        @close="toggleDropdown(false)"
      />
      <textarea
        v-if="type === 'textarea'"
        class="form-control"
        :value="encodedValue"
        :placeholder="schema.title"
        :required="!!schema.required"
        :readonly="!!schema.isReadOnly"
        @input="onInput"
        @focus="startEditing"
        v-dynamic-tag-listener="toggleDropdown"
      />
      <input
        v-if="type === 'string'"
        :class="['form-control', 'form-control-' + schema.type]"
        :value="encodedValue"
        :placeholder="schema.placeholder || schema.title"
        :required="!!schema.required"
        :readonly="!!schema.isReadOnly"
        @input="onInput"
        @focus="startEditing"
        v-dynamic-tag-listener="toggleDropdown"
      />
      <markdown
        v-if="isMarkdown"
        @input="onInput"
        :value="encodedValue"
        :required="!!schema.required"
        :readonly="!!schema.isReadOnly"
        :placeholder="schema.placeholder"
        v-dynamic-tag-listener="toggleDropdown"
      />
      <rich-text
        v-if="isRichText"
        @input="onInput"
        :value="value"
        :required="!!schema.required"
        :placeholder="schema.placeholder || schema.title"
        v-dynamic-tag-listener="toggleDropdown"
      />
      <div
        v-if="!editing && !isMarkdown && !isRichText"
        class="dynamic-input form-control"
        :readonly="!!schema.isReadOnly"
        :class="{ placeholder: !encodedValue, integer }"
        @click="startEditing"
      >
        <p v-html="dynamicValue"></p>
      </div>
    </div>
    <div v-if="hasMaxLength && !isMarkdown" class="dynamic-tag-input-remaining-character-count" :class="{ error: maxLengthExceeded }">
      Resterend aantal tekens: <span>{{ remainingCharacters }}</span>
    </div>
  </div>
</template>

<style lang="scss" scoped>
// This class is used by the dynamic-tag-parser to style tags explicitely
:deep(.dynamic-tag) {
  background-color: #eee;
  border: 1px solid #ccc;
  border-radius: 0.25rem;
  padding: 2px 6px;
  margin: 0 4px;
}

.dynamic-tag-input-remaining-character-count {
  color: grey;

  span {
    font-weight: bold;
  }

  &.error span {
    color: red;
  }
}

.dynamic-tag-input {
  height: 100%;
  position: relative;

  &:hover {
    cursor: text;
  }

  &:not(.textarea) {
    height: calc(1.5em + 0.75rem + 2px);

    .dynamic-input p {
      white-space: nowrap;
    }
  }

  .dynamic-input {
    overflow: hidden;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;

    &.placeholder p {
      color: #8e8e8e;
    }

    &.integer {
      width: 33%;
    }

    p {
      margin: 1px 0 0 0;
    }
  }
}

.form-control-number,
.form-control-integer {
  width: 33%;
}
</style>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { namespace } from 'vuex-class';
import schemaDynamicFieldDropdown from '../schema/schema-dynamic-field-dropdown.vue';
import markdown from '../shared/form/markdown.vue';
import { parseDynamicTags } from '../tools/dynamic-tag-parser';
import { DynamicValueTag, SchemaProperty } from '../types/api';
import { DynamicTagListenerDirective } from './dynamic-tag-listener-directive';
import richText from '../shared/form/rich-text.vue';

const DataStore = namespace('data');

@Component({
  components: {
    schemaDynamicFieldDropdown,
    markdown,
    richText
  },
  directives: {
    'dynamic-tag-listener': DynamicTagListenerDirective
  }
})
export default class DynamicTagInput extends Vue {
  @Prop() readonly schema!: SchemaProperty;
  @Prop() readonly type!: string;
  @Prop() readonly value!: string;
  @Prop({ default: false }) readonly textarea!: boolean;
  @Prop() readonly preview!: string;

  @DataStore.State('dynamicTags') readonly dynamicTags!: DynamicValueTag[];

  editing = false;
  dropdownOpened = false;
  integer = false;
  searchValue = '';

  get encodedValue() {
    return this.encodeValue(this.value);
  }

  get hasMaxLength() {
    return this.schema.maxLength != undefined;
  }

  get isMarkdown() {
    return this.type == 'markdown';
  }

  get isRichText() {
    return this.type == 'rich-text';
  }

  get isTypeTextarea() {
    return ['textarea', 'markdown', 'rich-text'].includes(this.type);
  }

  get maxLengthExceeded() {
    return this.hasMaxLength && this.schema.maxLength < this.value.length;
  }

  get dynamicValue() {
    const parsedText = parseDynamicTags(this.encodedValue, this.dynamicTags);
    return this.encodedValue ? parsedText : this.preview;
  }

  get remainingCharacters() {
    if (this.hasMaxLength && !this.isMarkdown) {
      return this.schema.maxLength - this.value.length;
    }

    return undefined;
  }

  encodeValue(value: string) {
    // see also: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#output-encoding-rules-summary
    // only '<' is now encoded if it's not a '<br>', '<a>', '</a>', '<u>' or '</u>' which is possible in Markdown.
    // This now fixes the case where '"><svg onload=console.log(1)>' would log in the console
    return value.replace(/<(?!br|a|\/a|u|\/u)/g, encodeURI('<'));
  }

  mounted() {
    this.integer = typeof this.encodedValue === 'number';
    (this.$refs.dynamicTagInput as HTMLDivElement).addEventListener('focusout', this.stopEditing);
  }

  beforeDestroy() {
    (this.$refs.dynamicTagInput as HTMLDivElement).removeEventListener('focusout', this.stopEditing);
  }

  startEditing() {
    this.editing = true;
  }

  stopEditing() {
    this.editing = false;
  }

  selectDynamicValue(value: DynamicValueTag) {
    const newValue = this.encodedValue.replace(/{{([a-zA-Z]*)(?![a-zA-Z]*}})/, `{{${value.text}}}`);
    this.$emit('input', newValue);

    this.editing = true;
  }

  toggleDropdown(opened: boolean, searchValue: string) {
    this.searchValue = searchValue;
    this.dropdownOpened = opened;
  }

  onInput(event: InputEvent) {
    const { target } = event;
    const val = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement ? target.value : event;
    this.$emit('input', val);
  }
}
</script>
