/** キャリアド記事 求職者書類の消し忘れ防止 配布版 1.0（スプレッドシート＋Googleドライブ版）
 * 管理表（スプレッドシート）を読み、保存期間を過ぎた求職者の書類フォルダを一覧にしてメールで知らせる。
 * MODE=trash にすると、「消してOK」にチェックが入った行のフォルダだけをゴミ箱に移す（30日以内なら戻せる）。
 * 新規GASプロジェクトにこのファイルだけ貼り付ける。設定はスクリプトプロパティへ。README.txt参照。
 * 完全削除はしない。帳簿（求人求職管理簿・手数料管理簿など）のフォルダは管理表に載せない。
 */
const DC_HEADERS = ['管理ID','求職者','状態','基準日','書類フォルダのURL','消す予定日','状況','消してOK','処理日'];
const DC_MAX_ROWS = 200;      // 1回の実行で見る最大行数
const DC_MAX_TRASH = 20;      // 1回の実行でゴミ箱に移す最大フォルダ数
const DC_NG_WORDS = ['管理簿','帳簿','台帳']; // フォルダ名にこれが入っていたら、ゴミ箱に移さない

function checkSetup() { dcGuard_(function(c) { dcSheet_(c); dcRoot_(c); console.log('PASS: 管理表・親フォルダ・保存期間（' + Object.keys(c.retention).join('／') + '）・通知先'); }); }
function weeklyCleanup() { dcGuard_(function(c) { dcRun_(c); }); }

function dcGuard_(fn) {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(1000)) { console.log('SKIP: 実行中'); return; }
  try { fn(dcConfig_()); } catch (e) { throw new Error(e && e.dcSafe ? e.message : '処理停止。設定とアクセス権を確認してください'); } finally { lock.releaseLock(); }
}
function dcFail_(text) { const e = new Error(text); e.dcSafe = true; throw e; }
function dcConfig_() {
  const p = PropertiesService.getScriptProperties();
  const req = function(k) { const v = (p.getProperty(k) || '').trim(); if (!v) dcFail_(k + ' が未設定です'); return v; };
  const c = { sheetId: req('SHEET_ID'), sheetName: req('SHEET_NAME'), rootId: req('ROOT_FOLDER_ID'), email: req('NOTIFY_EMAIL'),
    mode: (p.getProperty('MODE') || 'notify').trim() };
  if (c.mode !== 'notify' && c.mode !== 'trash') dcFail_('MODE は notify か trash');
  let r; try { r = JSON.parse(req('RETENTION_MONTHS')); } catch (e) { dcFail_('RETENTION_MONTHS の書き方を確認してください（例：{"選考終了":6}）'); }
  if (!r || typeof r !== 'object' || Array.isArray(r) || !Object.keys(r).length) dcFail_('RETENTION_MONTHS に状態と月数を1つ以上書いてください');
  Object.keys(r).forEach(function(k) { if (!Number.isInteger(r[k]) || r[k] < 1 || r[k] > 120) dcFail_('RETENTION_MONTHS の「' + k + '」は1〜120の整数'); });
  c.retention = r;
  return c;
}
function dcSheet_(c) {
  let sheet;
  try { sheet = SpreadsheetApp.openById(c.sheetId).getSheetByName(c.sheetName); } catch (e) { dcFail_('管理表を開けません。SHEET_ID と共有を確認してください'); }
  if (!sheet) dcFail_('シート「' + c.sheetName + '」が見つかりません');
  const head = sheet.getRange(1, 1, 1, DC_HEADERS.length).getValues()[0].map(String);
  DC_HEADERS.forEach(function(h, i) { if (head[i] !== h) dcFail_('1行目の見出しが違います（' + (i + 1) + '列目は「' + h + '」）'); });
  return sheet;
}
function dcRoot_(c) {
  try { return DriveApp.getFolderById(c.rootId); } catch (e) { dcFail_('親フォルダを開けません。ROOT_FOLDER_ID とアクセス権を確認してください'); }
}
function dcRun_(c) {
  const sheet = dcSheet_(c); dcRoot_(c);
  const last = sheet.getLastRow();
  if (last < 2) { console.log('完了: 対象の行なし'); return; }
  const n = Math.min(last - 1, DC_MAX_ROWS);
  const rows = sheet.getRange(2, 1, n, DC_HEADERS.length).getValues();
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const waiting = [], trashed = [], errors = [];
  for (let i = 0; i < rows.length; i++) {
    const r = rows[i], rowNo = i + 2;
    const id = String(r[0] || '').trim(), state = String(r[2] || '').trim(), url = String(r[4] || '').trim(), status = String(r[6] || '');
    if (!id && !url) continue;
    if (status.indexOf('ゴミ箱に移動済み') === 0) continue;
    const label = (id || '(管理IDなし)') + (String(r[1] || '').trim() ? '（' + String(r[1]).trim() + '）' : '');
    if (!id || !url) { dcMark_(sheet, rowNo, '確認待ち：管理IDと書類フォルダのURLを入れてください'); errors.push(label); continue; }
    if (!state) { dcMark_(sheet, rowNo, '保存中（状態が空欄）'); continue; }
    const months = c.retention[state];
    if (months === undefined) { dcMark_(sheet, rowNo, '確認待ち：状態「' + state + '」の保存期間が未設定です'); errors.push(label); continue; }
    if (!(r[3] instanceof Date) || isNaN(r[3])) { dcMark_(sheet, rowNo, '確認待ち：基準日を日付で入れてください'); errors.push(label); continue; }
    const due = dcAddMonths_(r[3], months);
    sheet.getRange(rowNo, 6).setValue(due);
    if (today < due) { dcMark_(sheet, rowNo, '保存中'); continue; }
    if (c.mode === 'trash' && r[7] === true) {
      if (trashed.length >= DC_MAX_TRASH) { dcMark_(sheet, rowNo, '期限切れ：確認待ち（今回の上限のため次回に処理）'); waiting.push(label); continue; }
      try {
        const folder = DriveApp.getFolderById(dcFolderId_(url));
        const name = folder.getName();
        if (DC_NG_WORDS.some(function(w) { return name.indexOf(w) >= 0; })) dcFail_('フォルダ名に「管理簿・帳簿・台帳」が入っているため移しません');
        if (!dcInside_(folder, c.rootId)) dcFail_('親フォルダの外にあるフォルダは移しません');
        folder.setTrashed(true);
        sheet.getRange(rowNo, 7, 1, 3).setValues([['ゴミ箱に移動済み（30日以内なら戻せます）', true, today]]);
        trashed.push(label);
      } catch (e) { dcMark_(sheet, rowNo, 'エラー：' + (e && e.dcSafe ? e.message : 'ゴミ箱に移せませんでした（オーナーでない、URLが違うなど）')); errors.push(label); }
      continue;
    }
    dcMark_(sheet, rowNo, '期限切れ：確認待ち');
    waiting.push(label);
  }
  if (last - 1 > DC_MAX_ROWS) console.log('INFO: ' + DC_MAX_ROWS + '行を超えた分は見ていません');
  const msg = '完了: 期限切れ（確認待ち） ' + waiting.length + '件／ゴミ箱に移動 ' + trashed.length + '件／確認が必要 ' + errors.length + '件';
  console.log(msg);
  if (waiting.length || trashed.length || errors.length) dcNotify_(c, sheet, waiting, trashed, errors);
}
function dcNotify_(c, sheet, waiting, trashed, errors) {
  const list = function(title, a) { return a.length ? '\n■ ' + title + '（' + a.length + '件）\n' + a.map(function(x) { return '・' + x; }).join('\n') + '\n' : ''; };
  let url = ''; try { url = SpreadsheetApp.openById(c.sheetId).getUrl(); } catch (e) {}
  const body = '求職者の書類の保存期間のお知らせです。\n' +
    list('保存期間を過ぎた書類（担当者の確認待ち）', waiting) + list('ゴミ箱に移した書類（30日以内なら戻せます）', trashed) + list('管理表の入力の確認が必要', errors) +
    '\n管理表：' + url + '\n確認して消してよいものは「消してOK」にチェックを入れてください。' + (c.mode === 'notify' ? '\n（今はお知らせだけのモードです。ゴミ箱への移動は行っていません）' : '');
  if (MailApp.getRemainingDailyQuota() < 1) { console.log('INFO: メールの送信上限のため、通知を送れませんでした'); return; }
  MailApp.sendEmail(c.email, '【書類の保存期間】確認待ち ' + waiting.length + '件', body);
}
function dcMark_(sheet, rowNo, text) { sheet.getRange(rowNo, 7).setValue(text); }
function dcAddMonths_(d, m) {
  const x = new Date(d.getFullYear(), d.getMonth() + m, 1);
  const lastDay = new Date(x.getFullYear(), x.getMonth() + 1, 0).getDate();
  x.setDate(Math.min(d.getDate(), lastDay));
  return x;
}
function dcFolderId_(url) {
  const m = String(url).match(/folders\/([A-Za-z0-9_-]{10,})/) || String(url).match(/[?&]id=([A-Za-z0-9_-]{10,})/);
  if (!m) dcFail_('書類フォルダのURLの形が違います（フォルダのURLを貼ってください）');
  return m[1];
}
function dcInside_(folder, rootId) {
  let cur = [folder];
  for (let depth = 0; depth < 10 && cur.length; depth++) {
    const next = [];
    for (let i = 0; i < cur.length; i++) {
      const it = cur[i].getParents();
      while (it.hasNext()) { const p = it.next(); if (p.getId() === rootId) return true; next.push(p); }
    }
    cur = next;
  }
  return false;
}
