<template>
  <div class="vue-csv-uploader">
    <div class="form">
      <div class="vue-csv-uploader-part-one">
        <div
          class="form-check form-group csv-import-checkbox"
          v-if="headers === null"
        >
          <slot
            name="hasHeaders"
            :headers="hasHeaders"
            :toggle="toggleHasHeaders"
          >
            <input
              :class="checkboxClass"
              type="checkbox"
              :id="makeId('hasHeaders')"
              :value="hasHeaders"
              @change="toggleHasHeaders"
            />
            <label class="form-check-label" :for="makeId('hasHeaders')">
              File Has Headers
            </label>
          </slot>
        </div>
        <div class="form-group csv-import-file">
          <input
            ref="csv"
            type="file"
            :disabled="loading_save"
            @change.prevent="validFileMimeType"
            :class="inputClass"
            name="csv"
          />
          <slot name="error" v-if="showErrorMessage">
            <div class="invalid-feedback d-block">
              File type is invalid
            </div>
          </slot>
        </div>
        <div class="form-group">
          <slot name="next" :load="load">
            <button
              type="submit"
              :disabled="disabledNextButton"
              :class="buttonClass"
              @click.prevent="load"
            >
              {{ loadBtnText }}
            </button>
          </slot>
        </div>
      </div>
      <div class="vue-csv-uploader-part-two">
        <div class="vue-csv-mapping" v-if="sample">
          <table :class="tableClass">
            <slot name="thead">
              <thead>
                <tr>
                  <th>Field</th>
                  <th>CSV Column</th>
                </tr>
              </thead>
            </slot>
            <tbody>
              <tr v-for="(field, key) in fieldsToMap" :key="key">
                <td>{{ field.label }}</td>
                <td>
                  <select
                    :class="tableSelectClass"
                    :name="`csv_uploader_map_${key}`"
                    v-model="map[field.key]"
                  >
                    <option :value="null" v-if="canIgnore">Ignore</option>
                    <option
                      v-for="(column, key) in firstRow"
                      :key="key"
                      :value="key"
                      >{{ column }}</option
                    >
                  </select>
                </td>
              </tr>
            </tbody>
          </table>
          <div class="form-group" v-if="url">
            <slot name="submit" :submit="submit">
              <input
                type="submit"
                :disabled="loading_save"
                :class="buttonClass"
                @click.prevent="submit"
                :value="submitBtnText"
              />
            </slot>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import { drop, every, forEach, get, isArray, map, set } from "lodash";
  import axios from "axios";
  import Papa from "papaparse";
  import mimeTypes from "mime-types";

  export default {
    props: {
      value: Array,
      url: {
        type: String,
      },
      mapFields: {
        required: true,
      },
      callback: {
        type: Function,
        default: () => ({}),
      },
      catch: {
        type: Function,
        default: () => ({}),
      },
      finally: {
        type: Function,
        default: () => ({}),
      },
      parseConfig: {
        type: Object,
        default() {
          return {};
        },
      },
      headers: {
        default: null,
      },
      loadBtnText: {
        type: String,
        default: "Next",
      },
      submitBtnText: {
        type: String,
        default: "Submit",
      },
      tableClass: {
        type: String,
        default: "table",
      },
      checkboxClass: {
        type: String,
        default: "form-check-input",
      },
      buttonClass: {
        type: String,
        default: "btn btn-primary",
      },
      inputClass: {
        type: String,
        default: "form-control-file",
      },
      validation: {
        type: Boolean,
        default: true,
      },
      fileMimeTypes: {
        type: Array,
        default: () => {
          return [
            "text/csv",
            "text/x-csv",
            "application/vnd.ms-excel",
            "text/plain",
          ];
        },
      },
      tableSelectClass: {
        type: String,
        default: "form-control",
      },
      canIgnore: {
        type: Boolean,
        default: false,
      },
    },

    data: () => ({
      form: {
        csv: null,
      },
      fieldsToMap: [],
      map: {},
      hasHeaders: true,
      csv: null,
      sample: null,
      isValidFileMimeType: false,
      fileSelected: false,
    }),

    created() {
      this.hasHeaders = this.headers;

      if (isArray(this.mapFields)) {
        this.fieldsToMap = map(this.mapFields, (item) => {
          return {
            key: item,
            label: item,
          };
        });
      } else {
        this.fieldsToMap = map(this.mapFields, (label, key) => {
          return {
            key: key,
            label: label,
          };
        });
      }
    },

    methods: {
      submit() {
        const _this = this;
        this.form.csv = this.buildMappedCsv();
        this.$emit("input", this.form.csv);

        if (this.url) {
          axios
            .post(this.url, this.form)
            .then((response) => {
              _this.callback(response);
            })
            .catch((response) => {
              _this.catch(response);
            })
            .finally((response) => {
              _this.finally(response);
            });
        } else {
          _this.callback(this.form.csv);
        }
      },
      buildMappedCsv() {
        const _this = this;

        let csv = this.hasHeaders ? drop(this.csv) : this.csv;

        return map(csv, (row) => {
          let newRow = {};

          forEach(_this.map, (column, field) => {
            set(newRow, field, get(row, column));
          });

          return newRow;
        });
      },
      validFileMimeType() {
        let file = this.$refs.csv.files[0];
        const mimeType =
          file.type === "" ? mimeTypes.lookup(file.name) : file.type;

        if (file) {
          this.fileSelected = true;
          this.isValidFileMimeType = this.validation
            ? this.validateMimeType(mimeType)
            : true;
        } else {
          this.isValidFileMimeType = !this.validation;
          this.fileSelected = false;
        }
      },
      validateMimeType(type) {
        return this.fileMimeTypes.indexOf(type) > -1;
      },
      load() {
        const _this = this;

        this.readFile((output) => {
          _this.sample = get(
            Papa.parse(output, { preview: 2, skipEmptyLines: true }),
            "data"
          );
          _this.csv = get(Papa.parse(output, { skipEmptyLines: true }), "data");
        });
      },
      readFile(callback) {
        let file = this.$refs.csv.files[0];

        if (file) {
          let reader = new FileReader();
          reader.readAsText(file, "UTF-8");
          reader.onload = function (evt) {
            callback(evt.target.result);
          };
          reader.onerror = function () {};
        }
      },
      toggleHasHeaders() {
        this.hasHeaders = !this.hasHeaders;
      },
      makeId(id) {
        return `${id}${this._uid}`;
      },
    },
    watch: {
      map: {
        deep: true,
        handler: function (newVal) {
          if (!this.url) {
            let hasAllKeys = Array.isArray(this.mapFields)
              ? every(this.mapFields, function (item) {
                  return newVal.hasOwnProperty(item);
                })
              : every(this.mapFields, function (item, key) {
                  return newVal.hasOwnProperty(key);
                });

            if (hasAllKeys) {
              this.submit();
            }
          }
        },
      },
      sample(newVal, oldVal) {
        if (newVal !== null) {
          this.fieldsToMap.forEach((field) => {
            newVal[0].forEach((columnName, index) => {
              if (field.key === columnName) {
                this.map[field.key] = index;
              }
            });
          });
        }
      },
    },
    computed: {
      firstRow() {
        return get(this, "sample.0");
      },
      showErrorMessage() {
        return this.fileSelected && !this.isValidFileMimeType;
      },
      disabledNextButton() {
        return !this.isValidFileMimeType;
      },
    },
  };
</script>
