class ExtractHandler {

  constructor(headerSpace) {
    const elements = createBtnGroupArea('extract-action-area', '出欠集計表出力', this.handleExtractData, {
      btnElId: 'extract-btn',
      yearElId: 'extract-year',
      monthElId: 'extract-month',
      termElId: 'extract-term'
    })
    if (!elements) {
      return;
    }
    headerSpace.appendChild(elements['extract-action-area']);
  }

  handleExtractData = async (e, { year, month, term }) => {
    loading(true, '帳票出力中...');
    showError(false);
    const api = new KintoneRestAPIClient();
    // 本アプリからデータを読み取る
    const records = await this.getRecords(api, year, month, term);
    if (!records) {
      // エラー
      loading(false);
      return e;
    }
    const recordMap = this.buildRecordMap(records);

    const childMaster = await this.getChildMstRecords(api, year, month, term, Object.keys(recordMap.byId));
    if (!childMaster) {
      // エラー
      loading(false);
      return e;
    }

    const dayMaster = await this.getDayMstRecords(api, year, month);
    if (!dayMaster) {
      // エラー
      loading(false);
      return e;
    }

    const termTeacher = await this.getTermTeacherMstRecords(api, term);
    if (termTeacher === undefined) {
      // エラー
      loading(false);
      return e;
    }

    const excelName = env["園児別出欠簿入力"].excelName;
    await createExcelAndDownload({
      api,
      excelName,
      exportName: getExcelName(excelName, year + month + '_' + term),
      bizLogic: this.writeExcel({ records, recordMap, childMaster, dayMaster, termTeacher }, term, getJapaneseEraDate(new Date(year, month - 1, 1))),
    });
    loading(false);
  }

  getRecords = async (api, year, month, term) => {
    const firstDate = getFormatDateString(year, month, 1)
    const lastDate = getFormatDateString(getLastDate(year, month));
    try {
      return await api.record.getAllRecordsWithId({
        app: env["園児別出欠簿入力"].appId,
        condition: `登園日 >= "${firstDate}" and 登園日 <= "${lastDate}" and 学年 in ("${term}")`
      });
    } catch (e) {
      showError(true, '本アプリのデータ読み取りエラー\n - ' + e);
    }
  }

  getChildMstRecords = async (api, year, month, term, uniqueKeys) => {
    const date = getJapaneseEraDate(new Date(year, month - 1, 1))
    const prevMonth = getJapaneseEraDate(getLastDate(year, month - 1));
    const result = {};
    try {
      result['入園'] = await api.record.getAllRecordsWithId({
        app: env["園児台帳"].appId,
        fields: ['性別'],
        condition: `和暦_入園年月日 in ("${date.era}") and 年_入園年月日 = "${date.year}" and 月_入園年月日 = "${date.month}" and 学年 in ("${term}")`
      });
      result['退園'] = await api.record.getAllRecordsWithId({
        app: env["園児台帳"].appId,
        fields: ['性別'],
        condition: `和暦_退園年月日 in ("${prevMonth.era}") and 年_退園年月日 = "${prevMonth.year}" and 月_退園年月日 = "${prevMonth.month}" and 学年 in ("${term}")`
      });
      if (uniqueKeys?.length) {
        result['在籍'] = await api.record.getAllRecordsWithId({
          app: env["園児台帳"].appId,
          fields: ['ユニークキー', '性別'],
          condition: `ユニークキー in ("${uniqueKeys.join('", "')}")`
        });
      }
      return result;
    } catch (e) {
      showError(true, '園児台帳のデータ読み取りエラー\n - ' + e);
    }
  }

  getDayMstRecords = async (api, yearStr, monthStr) => {
    let year = Number(yearStr);
    const month = Number(monthStr);
    // ※年度=4月～翌年3月までの区切りとする（例：2025年3月→2024年度）
    if (month < 4) {
      year--;
    }
    try {
      const data = await api.record.getAllRecordsWithId({
        app: env["保育・教育日数マスタ"].appId,
        fields: ['教育日数' + month, '保育日数' + month, '休日_' + month + '月'],
        condition: `年度 = "${year}"`
      });
      if (!data || !data[0]) {
        showError(true, '保育・教育日数マスタのデータが存在しません。');
        return;
      }
      return data && data[0];
    } catch (e) {
      showError(true, '保育・教育日数マスタのデータ読み取りエラー\n - ' + e);
    }
  }

  getTermTeacherMstRecords = async (api, term) => {
    try {
      const data = await api.record.getAllRecordsWithId({
        app: env["担任マスタ"].appId,
        fields: ['担任'],
        condition: `学年 in ("${term}")`
      });
      return data && data[0] && data[0]['担任']?.value.map((x) => x.name).join('、') || '';
    } catch (e) {
      showError(true, '担任マスタのデータ読み取りエラー\n - ' + e);
    }
  }

  toHolidaySet = (str) => {
    if (!str) {
      return new Set();
    }
    return new Set(str.split(','))
  }

  writeExcel = ({ records, recordMap, childMaster, dayMaster, termTeacher }, term, { era, year, westernYear, month }) => {
    const teachDays = Number(dayMaster['教育日数' + month].value);
    const careDays = Number(dayMaster['保育日数' + month].value);
    const holidaySet = this.toHolidaySet(dayMaster['休日_' + month + '月'].value);
    return async (api, worksheet) => {
      const baseCells = findCellsInfo(worksheet, ['13', '16', '19', '25', '(担 任)', '1', '番号']);

      // header
      updateCell(worksheet, { base: baseCells['13'][0], up: 1 }, era);
      updateCell(worksheet, { base: baseCells['16'][0], up: 1 }, year + '年');
      updateCell(worksheet, { base: baseCells['19'][0], up: 1 }, month + '月');
      updateCell(worksheet, { base: baseCells['25'][0], up: 1 }, term);

      const weekRow = worksheet.getRow(baseCells['1'][0].row + 1);
      const startCol = baseCells['1'][0].col;
      const weekStart = new Date(westernYear, month - 1, 1).getDay();
      const lastDate = getLastDate(westernYear, month).getDate();
      for (let i = 0; i < lastDate; i++) {
        updateCell(weekRow, { base: { col: startCol + i } }, WEEK[(weekStart + i) % 7]);
      }

      if (!records.length) {
        return;
      }
      updateCell(worksheet, { base: baseCells['(担 任)'][0], down: 1 }, termTeacher);

      fillMainPage(baseCells, worksheet, recordMap);
      fillFooter(worksheet, recordMap);
    }

    function fillMainPage(baseCells, worksheet, recordMap) {
      baseCells['番号'] = baseCells['番号'].filter((_, index) => index % 2 !== 0);
      const sortedRecords = Object.values(recordMap.byId).sort((a, b) =>
        parseInt(a['id'], 10) - parseInt(b['id'], 10)
      );

      const lastPage = 2;
      const baseForTemplate = baseCells['番号'][lastPage - 1]; // 番号 merged 2 rows
      const pageSize = 15;
      const totalPages = Math.ceil(sortedRecords.length / pageSize);

      // make new copy
      if (totalPages > 2) {
        const copyPageRowStart = baseForTemplate.row - 2;
        const copyPageRowEnd = baseForTemplate.row + (pageSize);

        createCopyFromTemplate(worksheet, {
          startPage: lastPage + 1,
          totalPages,
          copyPageRowStart,
          copyPageRowEnd,
          callback: (newPage, rowCount) => {
            ['番号'].forEach((label) => {
              const last = baseCells[label][newPage - 2];
              baseCells[label].push({
                col: last.col,
                row: last.row + rowCount
              });
            });
          }
        });
      }

      for (let i = 0; i < totalPages; i++) {
        const childLabelCell = baseCells['番号'][i];
        let currentRow = childLabelCell.row + 1;

        for (let j = 0; j < pageSize; j++) {
          const index = i * pageSize + j;
          const recordWrapper = sortedRecords[index];
          if (!recordWrapper) {
            break;
          }
          const row = worksheet.getRow(currentRow);
          const base = { row: currentRow, col: 1 };
          // 番号
          updateCell(row, { base }, recordWrapper['id']);
          // 児 童 氏 名
          updateCell(row, { base, right: 1 }, recordWrapper['name']);

          const reasons = [];
          const sum = {
            '出席': 0,
            '出席停止': 0,
            '病欠': 0,
            '自欠': 0,
            '休日欠席': 0
          }
          // 日
          recordWrapper.list.forEach((record, i) => {
            const res = record["出欠"].value;
            sum[res]++;
            if (res === '出席') {
              return;
            }
            if (res === '出席停止') {
              if (record["出席停止理由"].value) {
                reasons.push(record["出席停止理由"].value);
              }
              updateCell(row, { base, right: 2 + i }, '×');
              return
            }
            // 欠席
            if (holidaySet.has(record['登園日'].value)) {
              sum['休日欠席']++;
            }
            updateCell(row, { base, right: 2 + i }, '／');
          })

          // 出 席
          updateCell(row, { base, right: 33 }, sum['出席']);
          updateCell(row, { base, right: 34 }, sum['出席停止']);
          // 欠 席
          updateCell(row, { base, right: 35 }, sum['病欠']);
          updateCell(row, { base, right: 36 }, sum['自欠']);
          // 教育日数
          // updateCell(row, { base, right: 37 }, sum['出席'] + sum['出席停止'] - sum['病欠'] - sum['自欠']);
          updateCell(row, { base, right: 37 }, teachDays - sum['病欠'] - sum['自欠'] + sum['休日欠席']);

          // 備考
          updateCell(row, { base, right: 38 }, reasons.join("\n"));

          currentRow += 1;
        }

        worksheet.getRow(childLabelCell.row + pageSize + 1).addPageBreak();
      }
    }


    function fillFooter(worksheet, recordMap) {
      const baseCells = findCellsInfo(worksheet, ['合 計', '保 育 日 数', '男', '女', '欠 席 総 数', '出 席 率', '％']);
      // 合 計
      const totalAreaRow = worksheet.getRow(baseCells['合 計'][0].row);
      const totalAreaRow2 = worksheet.getRow(baseCells['合 計'][1].row);
      const totalAreaRow3 = worksheet.getRow(baseCells['合 計'][2].row);
      const base = { col: 3 };
      const dateList = recordMap.sum.list;
      for (let i = 0; i < dateList.length; i++) {
        if (dateList[i]) {
          updateCell(totalAreaRow, { base, right: i }, dateList[i]['出席']);
          updateCell(totalAreaRow2, { base, right: i }, dateList[i]['出停']);
          updateCell(totalAreaRow3, { base, right: i }, dateList[i]['欠席']);
        }
      }

      // 保 育 日 数
      updateCell(worksheet, { base: baseCells['保 育 日 数'][0], down: 1 }, careDays);

      // 入 園 数
      let list = childMaster['入園'];
      let [male, female] = groupingBySex(list);
      updateCell(worksheet, { base: baseCells['男'][0], right: 1 }, male.length);
      updateCell(worksheet, { base: baseCells['女'][0], right: 1 }, female.length);
      // 退 園 数
      list = childMaster['退園'];
      [male, female] = groupingBySex(list);
      updateCell(worksheet, { base: baseCells['男'][1], right: 1 }, male.length);
      updateCell(worksheet, { base: baseCells['女'][1], right: 1 }, female.length);
      // 在 籍 数
      list = childMaster['在籍'];
      [male, female] = groupingBySex(list);
      updateCell(worksheet, { base: baseCells['男'][2], right: 1 }, male.length);
      updateCell(worksheet, { base: baseCells['女'][2], right: 1 }, female.length);

      const total = recordMap.sum['欠席'] + recordMap.sum['出席'];
      if (total) {
        // 出 席 総 数
        updateCell(worksheet, { base: baseCells['欠 席 総 数'][0], left: 1 }, recordMap.sum['出席']);
        // 欠 席 総 数
        updateCell(worksheet, { base: baseCells['出 席 率'][0], left: 1 }, recordMap.sum['欠席']);
        // 出 席 率
        updateCell(worksheet, { base: baseCells['％'][0], right: 1 }, Math.round(recordMap.sum['出席'] / total * 1000) / 10 + '％');
      }

    }

  }

  buildRecordMap = (records) => {
    const recordMap = {
      sum: {
        '出席': 0,
        '欠席': 0,
        list: []
      },
      byId: {}
    }

    records.forEach((record) => {
      const dateIndex = Number(record['登園日'].value.split('-')[2]) - 1;
      const idKey = record['園児ユニークキー'].value;
      let attendance = recordMap.byId[idKey];
      if (!attendance) {
        attendance = {
          idKey,
          id: record['出席番号'].value,
          name: record['園児名'].value,
          list: [],
        };
        recordMap.byId[idKey] = attendance
      }
      attendance.list[dateIndex] = record;

      const status = record["出欠"].value;
      if (status === '出席' || status === '出席停止') {
        recordMap.sum['出席']++;
      } else if (status === '病欠' || status === '自欠') {
        recordMap.sum['欠席']++;
      }

      let dateSum = recordMap.sum.list[dateIndex];
      if (!dateSum) {
        dateSum = {
          '出席': 0,
          '出停': 0,
          '欠席': 0,
        };
        recordMap.sum.list[dateIndex] = dateSum
      }
      if (status === '出席') {
        dateSum['出席']++;
      } else if (status === '出席停止') {
        dateSum['出停']++;
      } else if (status === '病欠' || status === '自欠') {
        dateSum['欠席']++;
      }
    });
    return recordMap;
  }

  static getInstance(headerSpace) {
    if (!ExtractHandler.instance) {
      ExtractHandler.instance = new ExtractHandler(headerSpace);
    }
    return ExtractHandler.instance;
  }
}
