<template>
  <v-card flat elevation="0">
    <v-card-title v-if="title">{{ title }}</v-card-title>
    <v-card-subtitle v-if="subtitle">{{ subtitle }}</v-card-subtitle>
    <v-card-text>
      <v-form>

        <v-container>

          <dougal-delimited-string-decoder-field v-for="(field, key) in fields" :key="key"
            :colour="getHSLColourFor(key)"
            :readonly="readonly"
            :name="key"
            :value="fields[key]"
            @input="$emit('update:fields', {...fields, [key]: $event})"
          >
            <template v-slot:append v-if="editableFieldList && !readonly">
              <v-btn
                class="ml-3"
                fab
                text
                small
                title="Remove this property"
              >
                <v-icon
                  color="error"
                  @click="removeField(key)"
                >mdi-minus</v-icon>
              </v-btn>
            </template>
          </dougal-delimited-string-decoder-field>
          <v-row dense no-gutters v-if="editableFieldList && !readonly">
            <v-col cols=6 offset=1>
              <v-text-field
                label="Add new field"
                hint="Enter the name of a new field"
                :error-messages="fieldNameErrors"
                v-model="fieldName"
                append-outer-icon="mdi-plus-circle"
                @keydown.enter.prevent="addField"
              >
                <template v-slot:append-outer>
                  <v-icon
                    color="primary"
                    :disabled="fieldName && !!fieldNameErrors"
                    @click="addField"
                  >mdi-plus</v-icon>
                </template>
              </v-text-field>
            </v-col>
          </v-row>

          <v-row>
            <v-col cols="6">
              <v-combobox
                label="Field delimiter"
                hint="How are the fields separated from each other?"
                :items="delimiters"
                v-model="delimiter_"
              ></v-combobox>
            </v-col>
          </v-row>

          <v-row>
            <v-col cols="6">
              <v-text-field
                class="ml-3"
                label="Skip lines"
                hint="This lets you to skip file headers if present"
                type="number"
                min="0"
                :value.number="numberedLines"
                @input="$emit('update:numbered-lines', Number($event))"
              ></v-text-field>
            </v-col>
            <v-col cols="6">
              <v-checkbox
                v-ripple
                label="First non-skipped line are field names"
                :value="headerRow"
                @change="$emit('update:header-row', $event)"
              ></v-checkbox>
            </v-col>
          </v-row>

          <v-row>
            <v-col>
              <v-simple-table dense>
                <template v-slot:default>
                  <colgroup v-if="showLineNumbers">
                    <col class="line_no"/>
                  </colgroup>
                  <thead>
                    <tr>
                      <th class="line_no">
                        <v-simple-checkbox
                          off-icon="mdi-format-list-numbered"
                          title="Show line numbers"
                          v-model="showLineNumbers"
                        >
                        </v-simple-checkbox>
                      </th>
                      <th v-for="(header, idx) in headers" :key="idx"
                        :style="`color:${header.colour};`"
                      >
                        <v-select
                          dense
                          clearable
                          :items="fieldsAvailableFor(idx)"
                          :value="header.fieldName"
                          @input="fieldSelected(idx, $event)"
                        >
                        </v-select>
                      </th>
                    </tr>
                    <tr>
                      <th class="line_no">
                        <small v-if="showLineNumbers && headers.length">Line no.</small>
                      </th>
                      <th v-for="(header, idx) in headers" :key="idx"
                        :style="`color:${header.colour};`"
                      >
                        {{ header.text }}
                      </th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="(row, ridx) in rows" :key="ridx">
                      <td class="line_no"">
                        <small v-if="showLineNumbers">
                          {{ ridx + (typeof numberedLines == "number" ? numberedLines : 0)+1 }}
                        </small>
                      </td>
                      <td v-for="(cell, cidx) in row" :key="cidx"
                        :style="`background-color:${cell.colour};`"
                      >
                        {{ cell.text }}
                      </td>
                    </tr>
                  </tbody>
                </template>
              </v-simple-table>
            </v-col>
          </v-row>

        </v-container>



      </v-form>
    </v-card-text>
    <v-card-actions>
    </v-card-actions>
  </v-card>
</template>

<style scoped>
/*.v-data-table table tbody tr td*/
th {
  border: 1px solid hsl(0, 0%, 33.3%);
}

td {
  border-inline: 1px solid hsl(0, 0%, 33.3%);
}

.line_no {
  text-align: right;
  width: 4ex;
  border: none !important;
}
</style>

<script>
import { parse } from 'csv-parse/sync'
import { getHSLColourFor } from '@/lib/hsl'
import truncateText from '@/lib/truncate-text'
import DougalDelimitedStringDecoderField from './delimited-string-decoder-field'

export default {
  name: "DougalDelimitedStringDecoder",

  components: {
    DougalDelimitedStringDecoderField
  },

  props: {
    text: String,
    fields: Object,
    delimiter: String,
    headerRow: { type: [ Boolean, Number ], default: false},
    numberedLines: [ Boolean, Number ],
    maxHeight: String,
    editableFieldList: { type: Boolean, default: true },
    readonly: Boolean,
    title: String,
    subtitle: String
  },

  data () {
    return {
      //< The reason for not using this.text directly is that at some point
      //< we might extend this component to allow editing the sample text.
      text_: "",
      //< The name of a new field to add.
      fieldName: "",
      showLineNumbers: null,
      delimiters: [
        { text: "Comma (,)", value: "," },
        { text: "Tabulator (⇥)", value: "\x09" },
        { text: "Semicolon (;)", value: ";" }
      ]
    }
  },

  computed: {

    /** The index of the last column.
     *
     * This will be the higher of the number of columns available
     * in the sample text or the highest column number defined in
     * this.fields.
     *
     * NOTE: May return NaN
     */
    numberOfColumns () {
      const lastIndex = Object.values(this.fields)
        .reduce( (acc, cur) => Math.max(acc, cur.column), this.cells[0]?.length-1);
      return isNaN(lastIndex) ? 0 : (lastIndex + 1);
    },

    cells () {
      return parse(this.text_, {delimiter: this.delimiter, trim: true});
    },

    headers () {

      const headerNames = typeof this.headerRow == "number"
        ? this.cells[this.headerRow]
        : this.headerRow === true
          ? this.cells[0]
          : Array.from(this.cells[0] ?? [], (_, ι) => `Column ${ι}`);

      return headerNames?.map((c, ι) => {
        const fieldName = Object.keys(this.fields).find(i => this.fields[i].column == ι);
        const field = this.fields[fieldName] ?? {}
        const colour = this.headerRow === false
          ? this.getHSLColourFor(ι*10)
          : this.getHSLColourFor(c);

        return {
          text: c,
          colour: this.getHSLColourFor(c),
          fieldName,
          field
        } ?? {}
      }) ?? [];
    },

    rows () {
      // NOTE It doesn't matter if headerRow is boolean, it works just the same.
      return [...this.cells].slice(this.headerRow).map(r =>
        r.map( (c, ι) => ({
          text: truncateText(c),
          colour: this.headers.length
            ? this.getHSLColourFor(this.headers[ι]?.text, 0.2)
            : this.getHSLColourFor(ι*10, 0.2)
        })));
    },

    fieldNameErrors () {
      return Object.keys(this.fields).includes(this.fieldName)
        ? "A field with this name already exists"
        : null;
    },

    delimiter_: {
      get () {
        return this.delimiters.find(i => i.value == this.delimiter) ?? this.delimiter;
      },

      set (v) {
        this.$emit("update:delimiter", typeof v == "object" ? v.value : v);
      }
    }

  },

  watch: {

    text () {
      if (this.text != this.text_) {
        this.reset();
      }
    },

    numberedLines (cur, prev) {
      if (cur != prev) {
        this.showLineNumbers = typeof cur == "number" || cur;
      }
    }

  },

  methods: {



    fieldsAvailableFor (idx) {
      return Object.keys(this.fields).filter( i =>
        this.fields[i].column === idx || this.fields[i].column === null) ?? [];
    },

    fieldSelected (col, key) {

      const fields = {};
      for (const k in this.fields) {
        const field = {...this.fields[k]};
        if (k === key) {
          field.column = col
        } else {
          if (field.column === col) {
            field.column = null;
          }
        }
        fields[k] = field;
      }

      this.$emit("update:fields", fields);

    },

    addField () {
      if (!this.fieldNameErrors) {
        this.$emit("update:fields", {
          ...this.fields,
          [this.fieldName]: { column: null }
        });
        this.fieldName = "";
      }
    },

    removeField (key) {
      const fields = {...this.fields};
      delete fields[key];
      this.$emit("update:fields", fields);
    },

    getHSLColourFor: getHSLColourFor.bind(this),

    numberLine (number, line) {
      return `<span class="line-number">${number}</span>${line}`;
    },

    reset () {
      this.text_ = this.text.replaceAll("\r", "");
    }

  },

  mounted () {
    this.reset();
  }

}
</script>
