<script>
import { ref, computed } from 'vue';
import { parse as csvParse } from '@vanillaes/csv';

import { inject, keys as injectionKeys } from '../../../injection';
import { comparerByKey, readTextFile } from '../../../common/utils.js';

export default {
  name: 'PageMasterConfigStudent',
  setup() {
    const notification = inject(injectionKeys.notification);
    const handleError = inject(injectionKeys.errorHandler).handleErrorAsync;
    const resourceStore = inject(injectionKeys.resourceStore);

    const grades = ref(/** @type {{
      id: string, label: string, deleted: boolean,
    }[]} */([]));
    const gradeErrors = computed(() => {
      const notDeletedItems = grades.value.filter(x => !x.deleted);
      const ret = [];
      ret.push(...notDeletedItems.filter(x => !x.label).map(x => ({
        item: x, code: 'novalue', property: 'label',
      })));
      return ret;
    });

    const resources = ref(/** @type {{
      id: string | null, grade: string | null, firstName: string,
      lastName: string, firstNameKana: string, lastNameKana: string,
      disabled: boolean, deleted: boolean,
    }[]} */([]));
    const resourceErrors = computed(() => {
      const notDeletedItems = resources.value.filter(x => !x.deleted);
      const ret = [];
      ret.push(...notDeletedItems.filter(x => !x.grade).map(x => ({
        item: x, code: 'novalue', property: 'grade'
      })));
      ret.push(...notDeletedItems.filter(x => !x.firstName).map(x => ({
        item: x, code: 'novalue', property: 'firstName'
      })));
      ret.push(...notDeletedItems.filter(x => !x.lastName).map(x => ({
        item: x, code: 'novalue', property: 'lastName'
      })));
      ret.push(...notDeletedItems.filter(x => !x.firstNameKana).map(x => ({
        item: x, code: 'novalue', property: 'firstNameKana'
      })));
      ret.push(...notDeletedItems.filter(x => !x.lastNameKana).map(x => ({
        item: x, code: 'novalue', property: 'lastNameKana'
      })));
      ret.push(...notDeletedItems.filter(
        x => !/^[\x00-\x7F゠-ヿ]*$/u.test(x.firstNameKana)
      ).map(x => ({ item: x, code: 'nonkana', property: 'firstNameKana' })));
      ret.push(...notDeletedItems.filter(
        x => !/^[\x00-\x7F゠-ヿ]*$/u.test(x.lastNameKana)
      ).map(x => ({ item: x, code: 'nonkana', property: 'lastNameKana' })));
      ret.push(...notDeletedItems.filter(x => (
        x.grade != null &&
        !grades.value.filter(x => !x.deleted).map(x => x.id).includes(x.grade)
      )).map(x => ({ item: x, code: 'foreignkey', property: 'grade' })));
      return ret;
    });

    const load = handleError(async () => {
      await resourceStore.load();
      grades.value = resourceStore.studentGrades.value.map(x => ({
        id: x.id, label: x.label, deleted: false,
      }));
      resources.value = resourceStore.resources.value.filter(
        x => x.type === 'student'
      ).map(x => ({
        id: x.id, grade: x.grade.id, firstName: x.firstName, lastName: x.lastName,
        firstNameKana: x.firstNameKana, lastNameKana: x.lastNameKana,
        disabled: x.disabled, deleted: false,
      }));
      sortResources();
    });
    load();

    let gradeTemporaryId = 0;
    function addGrade() {
      grades.value.push({
        id: `!${++gradeTemporaryId}`, label: '', deleted: false,
      });
    }
    function sortGrades() {
      grades.value.sort(comparerByKey(x => x.label));
    }

    function addResource() {
      resources.value.push({
        id: null, grade: null, firstName: '', lastName: '',
        firstNameKana: '', lastNameKana: '', disabled: false,
        deleted: false,
      });
    }
    function sortResources() {
      resources.value.sort(comparerByKey(x => [
        grades.value.find(g => g.id === x.grade)?.label ?? '',
        x.lastNameKana, x.firstNameKana,
      ]));
    }

    const save = handleError(async () => {
      if(gradeErrors.value.length) {
        notification.add('error', '学年入力欄にエラーがあります。修正してください。');
        return;
      }
      if(resourceErrors.value.length) {
        notification.add('error', '生徒入力欄にエラーがあります。修正してください。');
        return;
      }

      const toAddItems = resources.value.filter(
        x => x.id == null && !x.deleted
      );
      const toUpdateItems = resources.value.filter(
        x => x.id != null && !x.deleted
      );
      const toRemoveItems = resources.value.filter(
        x => x.id != null && x.deleted
      );

      if(toRemoveItems.length) {
        if(!confirm('いくつかの生徒が削除されます。削除する生徒が指定されたイベントがある場合、それらのイベントからその生徒の指定が消えます。本当に削除しますか？')) {
          return;
        }
      }

      await resourceStore.mutate([...grades.value.filter(
        x => x.id.startsWith('!') && !x.deleted
      ).map(x => ({
        opcode: 'add_studentgrade', param: { __id: x.id, label: x.label },
      })), ...grades.value.filter(
        x => !x.id.startsWith('!') && !x.deleted
      ).map(x => ({
        opcode: 'update_studentgrade', param: { id: x.id, label: x.label },
      })), ...grades.value.filter(
        x => !x.id.startsWith('!') && x.deleted
      ).map(x => ({
        opcode: 'remove_studentgrade', param: { id: x.id },
      })), ...toAddItems.map(x => ({
        opcode: 'add_resource', param: {
          multiplicity: 1, disabled: x.disabled, type: 'student',
          grade: { [x.grade.startsWith('!') ? '__id' : 'id']: x.grade },
          firstName: x.firstName, lastName: x.lastName,
          firstNameKana: x.firstNameKana, lastNameKana: x.lastNameKana,
        },
      })), ...toUpdateItems.map(x => ({
        opcode: 'update_resource', param: {
          id: x.id, multiplicity: 1, disabled: x.disabled, type: 'student',
          grade: { [x.grade.startsWith('!') ? '__id' : 'id']: x.grade },
          firstName: x.firstName, lastName: x.lastName,
          firstNameKana: x.firstNameKana, lastNameKana: x.lastNameKana,
        },
      })), ...toRemoveItems.map(x => ({
        opcode: 'remove_resource', param: { id: x.id },
      }))]);

      notification.add('info', '保存しました。');

      await load();
    });

    const readFromCSV = handleError(async file => {
      const text = await readTextFile(file);
      const records = csvParse(text);
      if(records.some(x => x.length !== 5)) {
        notification.add(
          'error', 'CSVファイルの形式が異なります。各行は学年,姓,名,姓（カナ）,名（カナ）の5フィールドです。'
        );
        return;
      }
      await load();

      const gradeRecords = [...new Set(records.map(x => x[0]))];
      const existingGradeIds = gradeRecords.map(
        record => grades.value.find(x => x.label === record)?.id ?? null
      );
      for(const grade of grades.value) {
        if(!existingGradeIds.includes(grade.id)) {
          grade.deleted = true;
        }
      }
      grades.value.push(...gradeRecords.filter(
        (x, i) => existingGradeIds[i] == null
      ).map((x, idx) => ({ id: `!${idx}`, label: x, deleted: false })));

      const existingIds = records.map(record => resources.value.find(x => (
        x.grade === existingGradeIds[gradeRecords.indexOf(record[0])] &&
        x.lastName === record[1] && x.firstName === record[2] &&
        x.lastNameKana === record[3] && x.firstNameKana === record[4]
      ))?.id ?? null);
      for(const resource of resources.value) {
        if(!existingIds.includes(resource.id)) {
          resource.deleted = true;
        }
      }
      resources.value.push(...records.filter(
        (x, i) => existingIds[i] == null
      ).map(x => ({
        id: null, grade: grades.value.find(g => g.label === x[0]).id,
        lastName: x[1], firstName: x[2], lastNameKana: x[3],
        firstNameKana: x[4], disabled: false, deleted: false,
      })));
    });

    return {
      grades, gradeErrors, resources, resourceErrors, load,
      addGrade, sortGrades, addResource, sortResources, save, readFromCSV,
    };
  }
}
</script>

<template>
  <h1>生徒登録</h1>
  <p>「変更を適用」ボタンを押さない限り変更は保存されません！</p>
  <details>
    <summary>CSVファイルから読み込み</summary>
    <label>
      ファイル選択：
      <input type="file" accept="text/csv" @change="
        readFromCSV($event.target.files[0]); $event.target.value = '';
      " />
    </label>
    <p>各行は<code>学年,姓,名,姓（カナ）,名（カナ）</code>の形式です。読み込む際は事前にこのページ上の保存されていない変更が全てリセットされます。</p>
  </details>
  <section>
    <h1>学年</h1>
    <table>
      <thead><tr><th>ID</th><th>変更状況</th><th>ラベル</th></tr></thead>
      <tbody>
        <template v-for="x, idx of grades" :key="x.id">
          <tr>
            <th
              :title="x.id.startsWith('!') ? null : x.id"
            >{{ x.id.startsWith('!') ? '[未割当]' : `...${x.id.slice(-4)}` }}</th>
            <td>
              {{ x.deleted ? '削除✘' : x.id.startsWith('!') ? '新規✧' : '維持✔' }}
              <button v-if="x.id.startsWith('!')" type="button" @click="
                grades.splice(idx, 1);
              ">削除</button>
              <button v-else type="button" @click="x.deleted = !x.deleted;">
                {{ x.deleted ? '削除しない' : '削除' }}
              </button>
            </td>
            <td><input v-model="x.label" /></td>
          </tr>
        </template>
      </tbody>
    </table>
    <button type="button" @click="addGrade()">追加</button>
    <button type="button" @click="sortGrades()">ソート</button>
    <ul>
      <li v-for="error, idx of gradeErrors" :key="idx">
        {{
          `${grades.indexOf(error.item) + 1}番目の${{
            label: 'ラベル',
          }[error.property]}${{ novalue: 'が指定されていません' }[error.code]}`
        }}
      </li>
    </ul>
  </section>

  <section>
    <h1>生徒</h1>
    <table>
      <thead>
        <tr>
          <th>ID</th><th>変更状況</th><th>学年</th><th>姓</th><th>名</th>
          <th>姓（カナ）</th><th>名（カナ）</th><th>非表示</th>
        </tr>
      </thead>
      <tbody>
        <template v-for="x, idx of resources" :key="x.id">
          <tr>
            <th
              :title="x.id"
            >{{ x.id != null ? `...${x.id.slice(-4)}` : '[未割当]' }}</th>
            <td>
              {{ x.deleted ? '削除✘' : x.id == null ? '新規✧' : '維持✔' }}
              <button v-if="x.id == null" type="button" @click="
                resources.splice(idx, 1);
              ">削除</button>
              <button v-else type="button" @click="x.deleted = !x.deleted;">
                {{ x.deleted ? '削除しない' : '削除' }}
              </button>
            </td>
            <td>
              <select v-model="x.grade">
                <option :value="null">[未選択]</option>
                <option v-for="
                  grade of grades.filter(x => !x.deleted)
                " :key="grade.id" :value="grade.id">{{ grade.label }}</option>
              </select>
            </td>
            <td><input v-model="x.lastName" /></td>
            <td><input v-model="x.firstName" /></td>
            <td><input v-model="x.lastNameKana" /></td>
            <td><input v-model="x.firstNameKana" /></td>
            <td><input type="checkbox" v-model="x.disabled" /></td>
          </tr>
        </template>
      </tbody>
    </table>
    <button type="button" @click="addResource()">追加</button>
    <button type="button" @click="sortResources()">ソート</button>
    <ul>
      <li v-for="error, idx of resourceErrors" :key="idx">
        {{
          `${resources.indexOf(error.item) + 1}番目の${{
            grade: '学年', firstName: '名', lastName: '姓',
            firstNameKana: '名（カナ）', lastNameKana: '姓（カナ）',
          }[error.property]}${{
            novalue: 'が指定されていません', nonkana: 'にカタカナ以外が含まれています',
            foreignkey: 'が無効な値です',
          }[error.code]}`
        }}
      </li>
    </ul>
  </section>

  <p><button type="button" @click="save()">変更を適用</button></p>
  <!-- <p><button type="button" @click="load()">リセット</button></p> -->
</template>
