gasファイルの「分析.js」を以下に差し替えてください。

// ==================== 投稿一覧取得(追記式・自動実行用)【引用偏差値対応版】 ====================
function fetchThreadsListAuto() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);

  // アクセストークンを取得
  const accessToken = settingsSheet.getRange(CONFIG.CELL.ACCESS_TOKEN).getValue();

  if (!accessToken) {
    console.log('❌ アクセストークンが設定されていません');
    return;
  }

  try {
    console.log('🔥 投稿一覧の自動取得開始');

    // バックエンド経由で投稿一覧を取得
    const threads = fetchThreadsListViaBackend(accessToken, 25);
    const data = { data: threads };

    // 投稿一覧シートを取得または作成
    let threadsListSheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST);
    if (!threadsListSheet) {
      threadsListSheet = ss.insertSheet(CONFIG.SHEET_NAME_THREADS_LIST);
      // ヘッダー行を作成(16列に変更)
      threadsListSheet.getRange(1, 1, 1, 16).setValues([
        ['投稿ID', '投稿日時', '投稿内容', '投稿URL', '閲覧数', 'いいね数', '返信数', 'リポスト数', '引用数', '閲覧偏差値', 'いいね偏差値', '返信偏差値', 'リポスト偏差値', '引用偏差値', '総合偏差値', '好反応フラグ']
      ]);
      threadsListSheet.getRange(1, 1, 1, 16).setFontWeight('bold').setBackground('#4a86e8').setFontColor('#ffffff');
    }

    // 既存の投稿ID一覧を取得
    const existingIds = new Set();
    const lastRow = threadsListSheet.getLastRow();

    if (lastRow > 1) {
      const existingData = threadsListSheet.getRange(2, 1, lastRow - 1, 1).getValues();
      existingData.forEach(row => {
        if (row[0]) {
          // 文字列に変換し、先頭のシングルクォートを除去
          const idStr = String(row[0]).replace(/^'/, '').trim();
          existingIds.add(idStr);
        }
      });
    }

    console.log(`既存投稿数: ${existingIds.size}件`);

    // 新しい投稿のみをフィルタリング
    const newPosts = [];

    if (data.data && data.data.length > 0) {
      data.data.forEach(thread => {
        // IDを文字列化してチェック
        const threadIdStr = String(thread.id);
        
        if (!existingIds.has(threadIdStr)) {
          const timestamp = thread.timestamp ? new Date(thread.timestamp) : '';
          newPosts.push([
            "'" + threadIdStr,                                                         // A: 投稿ID (文字列として強制)
            timestamp ? Utilities.formatDate(timestamp, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss') : '', // B: 投稿日時
            thread.text || '',                                                         // C: 投稿内容
            thread.permalink || '',                                                    // D: 投稿URL
            '',  // E: 閲覧数(後で取得)
            '',  // F: いいね数(後で取得)
            '',  // G: 返信数(後で取得)
            '',  // H: リポスト数(後で取得)
            '',  // I: 引用数(後で取得)
            '',  // J: 閲覧偏差値(後で計算)
            '',  // K: いいね偏差値(後で計算)
            '',  // L: 返信偏差値(後で計算)
            '',  // M: リポスト偏差値(後で計算)
            '',  // N: 引用偏差値(後で計算)← 新規追加!
            '',  // O: 総合偏差値(後で計算)
            ''   // P: 好反応フラグ(後で判定)
          ]);
        }
      });
    }

    // 🔥 重要:古い順に並び替え(reverse)
    newPosts.reverse();

    // 新しい投稿を追記
    if (newPosts.length > 0) {
      const nextRow = threadsListSheet.getLastRow() + 1;
      
      // ID列(A列)を書式なしテキストに設定
      threadsListSheet.getRange(nextRow, 1, newPosts.length, 1).setNumberFormat('@');
      
      threadsListSheet.getRange(nextRow, 1, newPosts.length, 16).setValues(newPosts);
      console.log(`✅ ${newPosts.length}件の新規投稿を追加しました`);
    } else {
      console.log('✅ 新規投稿なし');
    }

  } catch (error) {
    console.log('❌ 投稿一覧取得失敗: ' + error.message);
    showError('投稿一覧取得エラー:\n\n' + error.message);
    throw error;  // 上位関数にエラーを伝播
  }
}

// ==================== メトリクス自動取得(設定時間経過後) ====================
function fetchMetricsAuto() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);
  const threadsListSheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST);

  // アクセストークンを取得
  const accessToken = settingsSheet.getRange(CONFIG.CELL.ACCESS_TOKEN).getValue();

  if (!accessToken) {
    console.log('❌ アクセストークンが設定されていません');
    return { success: false, error: 'NO_TOKEN' };
  }

  if (!threadsListSheet) {
    console.log('❌ 投稿履歴シートが見つかりません');
    return { success: false, error: 'NO_SHEET' };
  }

  try {
    console.log('🔥 メトリクス自動取得開始');

    // メトリクス取得条件(時間)を取得(デフォルト6時間)
    const metricsDelayHours = Number(settingsSheet.getRange(CONFIG.CELL.METRICS_CONDITION).getValue()) || 6;
    console.log(`メトリクス取得条件: 投稿後${metricsDelayHours}時間経過`);

    const lastRow = threadsListSheet.getLastRow();
    if (lastRow < 2) {
      console.log('投稿データがありません');
      return { success: true, updatedCount: 0 };
    }

    // 全データを取得
    const data = threadsListSheet.getRange(2, 1, lastRow - 1, 15).getValues();

    const now = new Date();
    let updatedCount = 0;
    let permissionErrorCount = 0;  // 権限エラーカウント

    for (let i = 0; i < data.length; i++) {
      const row = data[i];
      const postId = row[0];           // A列: 投稿ID
      const postDateStr = row[1];      // B列: 投稿日時
      const viewsValue = row[4];       // E列: 閲覧数

      // スキップ条件:投稿IDがない、または既にメトリクス取得済み
      if (!postId || viewsValue !== '') {
        continue;
      }

      // 投稿日時をパース
      let postDate;
      try {
        postDate = new Date(postDateStr);
      } catch (error) {
        console.log(`行${i + 2}: 日付パースエラー - ${error.message}`);
        continue;
      }

      // 設定時間経過チェック
      const hoursPassed = (now - postDate) / (1000 * 60 * 60);
      if (hoursPassed < metricsDelayHours) {
        console.log(`行${i + 2}: まだ${metricsDelayHours}時間経過していません (${hoursPassed.toFixed(1)}時間)`);
        continue;
      }

      // メトリクスを取得
      console.log(`行${i + 2}: メトリクス取得開始 - 投稿ID: ${postId}`);

      try {
        const metrics = fetchInsights(postId, accessToken);

        if (metrics) {
          // E〜I列にメトリクスを記録
          const rowIndex = i + 2;
          threadsListSheet.getRange(rowIndex, 5).setValue(metrics.views || 0);      // E列: 閲覧数
          threadsListSheet.getRange(rowIndex, 6).setValue(metrics.likes || 0);      // F列: いいね数
          threadsListSheet.getRange(rowIndex, 7).setValue(metrics.replies || 0);    // G列: 返信数
          threadsListSheet.getRange(rowIndex, 8).setValue(metrics.reposts || 0);    // H列: リポスト数
          threadsListSheet.getRange(rowIndex, 9).setValue(metrics.quotes || 0);     // I列: 引用数

          updatedCount++;
          console.log(`✅ 行${rowIndex}: メトリクス記録完了`);

          // API制限対策:少し待機
          Utilities.sleep(500);
        }

      } catch (error) {
        console.log(`❌ 行${i + 2}: メトリクス取得エラー - ${error.message}`);
        const rowIndex = i + 2;

        // 投稿が存在しない場合(削除済み等)は「取得不可」と記録してスキップ
        if (error.message.includes('does not exist')) {
          console.log(`⏭️ 行${rowIndex}: 投稿が存在しないため「取得不可」を記録`);
          threadsListSheet.getRange(rowIndex, 5).setValue('取得不可');
          continue;
        }

        // その他の権限エラーを検出
        if (error.message.toLowerCase().includes('permission') ||
            error.message.includes('does not have permission')) {
          permissionErrorCount++;

          // 連続で権限エラーが発生したら処理を中断
          if (permissionErrorCount >= 2) {
            console.log('❌ 権限エラーが連続発生 - 処理を中断');
            return {
              success: false,
              error: 'PERMISSION_DENIED',
              message: '分析機能を使用するには、追加の権限が必要です。\n\n管理者にお問い合わせください。'
            };
          }
        }
      }
    }

    console.log(`✅ メトリクス取得完了: ${updatedCount}件を更新`);
    return { success: true, updatedCount: updatedCount };

  } catch (error) {
    console.log('❌ メトリクス取得失敗: ' + error.message);
    return { success: false, error: error.message };
  }
}

// ==================== Threads Insights APIからメトリクスを取得(バックエンド経由) ====================
function fetchInsights(mediaId, accessToken) {
  // バックエンド経由でメトリクスを取得
  return fetchMetricsViaBackend(accessToken, mediaId);
}

// ==================== 投稿一覧取得(手動実行用) ====================
function fetchThreadsList() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);

  // アクセストークンを取得
  const accessToken = settingsSheet.getRange(CONFIG.CELL.ACCESS_TOKEN).getValue();

  if (!accessToken) {
    showError(' エラー\n\nアクセストークンが設定されていません。\n先にOAuth認証を完了してください。');
    return;
  }

  try {
    // 自動取得関数を呼び出す
    fetchThreadsListAuto();

    // 結果をダイアログで表示
    const threadsListSheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST);
    const totalPosts = threadsListSheet ? threadsListSheet.getLastRow() - 1 : 0; // ヘッダー除く

    showSuccess(' 投稿一覧取得完了!\n\n現在の総投稿数: ' + totalPosts + '件');
    console.log('✅ 投稿一覧取得完了(手動実行)');
  } catch (error) {
    // エラーは既にfetchThreadsListAutoで表示されている
    console.log('❌ 投稿一覧取得失敗(手動実行): ' + error.message);
  }
}

// ==================== メトリクス取得(手動実行用) ====================
function fetchMetricsManual() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);

  // アクセストークンを取得
  const accessToken = settingsSheet.getRange(CONFIG.CELL.ACCESS_TOKEN).getValue();

  if (!accessToken) {
    showError(' エラー\n\nアクセストークンが設定されていません。\n先にOAuth認証を完了してください。');
    return;
  }

  // 自動取得関数を呼び出す
  const result = fetchMetricsAuto();

  // 権限エラーの場合はユーザーに通知
  if (result && result.error === 'PERMISSION_DENIED') {
    showError('⚠️ 分析機能エラー\n\n' + result.message);
    console.log('❌ メトリクス取得失敗(権限エラー)');
    return;
  }

  if (result && result.success) {
    showSuccess(` メトリクス取得完了!\n\n${result.updatedCount}件を更新しました。`);
    console.log('✅ メトリクス取得完了(手動実行)');
  } else {
    showError(' メトリクス取得エラー\n\n詳細はログを確認してください。');
    console.log('❌ メトリクス取得失敗(手動実行)');
  }
}

// ==================== 偏差値計算&好反応判定(自動実行用)【引用偏差値対応版】 ====================
function calculateDeviationsAndJudge() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const threadsListSheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST);

  if (!threadsListSheet) {
    console.log('❌ 投稿履歴シートが見つかりません');
    return;
  }

  try {
    console.log('🔥 偏差値計算&好反応判定開始');

    const lastRow = threadsListSheet.getLastRow();
    if (lastRow < 2) {
      console.log('投稿データがありません');
      return;
    }

    // 全データを取得(16列)
    const data = threadsListSheet.getRange(2, 1, lastRow - 1, 16).getValues();

    // メトリクスが記録されている投稿を収集
    const postsWithMetrics = collectPostsWithMetrics_(data);

    if (postsWithMetrics.length < 5) {
      console.log('⚠️ メトリクスが記録されている投稿が5件未満です。判定を実行しません。');
      return;
    }

    console.log(`メトリクス記録済み投稿数: ${postsWithMetrics.length}件`);

    // 計算対象データを取得(過去14日間 or 全件)
    const baseData = getCalculationBaseData_(postsWithMetrics);
    console.log(`計算対象データ数: ${baseData.length}件`);

    // 統計値を計算
    const stats = calculateMetricsStatistics_(baseData);
    console.log(`中央値 - 閲覧:${stats.median.views}, いいね:${stats.median.likes}, 返信:${stats.median.replies}, リポスト:${stats.median.reposts}, 引用:${stats.median.quotes}`);
    console.log(`標準偏差 - 閲覧:${stats.stdDev.views.toFixed(2)}, いいね:${stats.stdDev.likes.toFixed(2)}, 返信:${stats.stdDev.replies.toFixed(2)}, リポスト:${stats.stdDev.reposts.toFixed(2)}, 引用:${stats.stdDev.quotes.toFixed(2)}`);

    // 各投稿について偏差値と判定を計算
    let updatedCount = 0;
    for (const post of postsWithMetrics) {
      updatePostDeviations_(threadsListSheet, post, stats);
      updatedCount++;
    }

    console.log(`✅ 偏差値計算&判定完了: ${updatedCount}件を更新`);

  } catch (error) {
    console.log('❌ 偏差値計算&判定失敗: ' + error.message);
  }
}

/** メトリクスが記録されている投稿を収集 */
function collectPostsWithMetrics_(data) {
  const postsWithMetrics = [];

  for (let i = 0; i < data.length; i++) {
    const row = data[i];
    const postDateStr = row[1];  // B列: 投稿日時
    const views = row[4];         // E列: 閲覧数

    // メトリクスが記録されている行のみ(「取得不可」などの文字列は除外)
    if (views !== '' && !isNaN(Number(views)) && postDateStr) {
      postsWithMetrics.push({
        rowIndex: i + 2,
        postDate: new Date(postDateStr),
        views: Number(views) || 0,
        likes: Number(row[5]) || 0,    // F列: いいね数
        replies: Number(row[6]) || 0,  // G列: 返信数
        reposts: Number(row[7]) || 0,  // H列: リポスト数
        quotes: Number(row[8]) || 0    // I列: 引用数
      });
    }
  }

  return postsWithMetrics;
}

/** 計算対象データを取得(設定期間 or 全件) */
function getCalculationBaseData_(postsWithMetrics) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);

  // 偏差値取得期間(日数)を取得(デフォルト14日)
  const deviationPeriodDays = Number(settingsSheet.getRange(CONFIG.CELL.DEVIATION_PERIOD).getValue()) || 14;

  const now = new Date();
  const periodAgo = new Date(now.getTime() - (deviationPeriodDays * 24 * 60 * 60 * 1000));

  const pastPeriodData = postsWithMetrics.filter(post => post.postDate >= periodAgo);

  if (pastPeriodData.length < 5) {
    console.log(`⚠️ 過去${deviationPeriodDays}日間の投稿が5件未満です。全データを使用します。`);
    return postsWithMetrics;
  }

  console.log(`偏差値計算対象: 過去${deviationPeriodDays}日間`);
  return pastPeriodData;
}

/** 統計値(平均・標準偏差・中央値)を計算 */
function calculateMetricsStatistics_(baseData) {
  // 各指標の配列を作成
  const arrays = {
    views: baseData.map(p => p.views),
    likes: baseData.map(p => p.likes),
    replies: baseData.map(p => p.replies),
    reposts: baseData.map(p => p.reposts),
    quotes: baseData.map(p => p.quotes)
  };

  // 平均値を計算
  const avg = {};
  for (const key in arrays) {
    avg[key] = arrays[key].reduce((sum, v) => sum + v, 0) / arrays[key].length;
  }

  // 標準偏差を計算
  const stdDev = {};
  for (const key in arrays) {
    stdDev[key] = calculateStdDev(arrays[key], avg[key]);
  }

  // 中央値を計算
  const median = {};
  for (const key in arrays) {
    median[key] = calculateMedian(arrays[key]);
  }

  return { avg, stdDev, median };
}

/** 投稿の偏差値を計算してシートに記録 */
function updatePostDeviations_(sheet, post, stats) {
  // 偏差値を計算
  const deviations = {
    views: calculateDeviation(post.views, stats.avg.views, stats.stdDev.views),
    likes: calculateDeviation(post.likes, stats.avg.likes, stats.stdDev.likes),
    replies: calculateDeviation(post.replies, stats.avg.replies, stats.stdDev.replies),
    reposts: calculateDeviation(post.reposts, stats.avg.reposts, stats.stdDev.reposts),
    quotes: calculateDeviation(post.quotes, stats.avg.quotes, stats.stdDev.quotes)
  };

  const totalDeviation = (deviations.views + deviations.likes + deviations.replies + deviations.reposts + deviations.quotes) / 5;

  // 好反応判定(5項目のうち1つ以上が中央値超え)
  const isHighPerformance =
    post.views > stats.median.views ||
    post.likes > stats.median.likes ||
    post.replies > stats.median.replies ||
    post.reposts > stats.median.reposts ||
    post.quotes > stats.median.quotes;

  // スプレッドシートに記録
  const rowIndex = post.rowIndex;
  sheet.getRange(rowIndex, 10).setValue(Math.round(deviations.views * 10) / 10);     // J列: 閲覧偏差値
  sheet.getRange(rowIndex, 11).setValue(Math.round(deviations.likes * 10) / 10);     // K列: いいね偏差値
  sheet.getRange(rowIndex, 12).setValue(Math.round(deviations.replies * 10) / 10);   // L列: 返信偏差値
  sheet.getRange(rowIndex, 13).setValue(Math.round(deviations.reposts * 10) / 10);   // M列: リポスト偏差値
  sheet.getRange(rowIndex, 14).setValue(Math.round(deviations.quotes * 10) / 10);    // N列: 引用偏差値
  sheet.getRange(rowIndex, 15).setValue(Math.round(totalDeviation * 10) / 10);       // O列: 総合偏差値
  sheet.getRange(rowIndex, 16).setValue(isHighPerformance ? 'はい' : 'いいえ');      // P列: 好反応フラグ
}

// ==================== 偏差値計算&好反応判定(手動実行用) ====================
function calculateDeviationsManual() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);

  // アクセストークンチェック(念のため)
  const accessToken = settingsSheet.getRange(CONFIG.CELL.ACCESS_TOKEN).getValue();

  if (!accessToken) {
    showError(' エラー\n\nアクセストークンが設定されていません。\n先にOAuth認証を完了してください。');
    return;
  }

  // 計算実行
  calculateDeviationsAndJudge();

  showSuccess(' 偏差値計算&好反応判定完了!\n\n実行ログで詳細を確認してください。');
  console.log('✅ 偏差値計算&判定完了(手動実行)');
}

// ==================== 中央値計算 ====================
function calculateMedian(numbers) {
  if (numbers.length === 0) return 0;

  const sorted = numbers.slice().sort((a, b) => a - b);
  const middle = Math.floor(sorted.length / 2);

  if (sorted.length % 2 === 0) {
    // 偶数個の場合は中央2つの平均
    return (sorted[middle - 1] + sorted[middle]) / 2;
  } else {
    // 奇数個の場合は中央値
    return sorted[middle];
  }
}

// ==================== 標準偏差計算 ====================
function calculateStdDev(numbers, average) {
  if (numbers.length === 0) return 0;

  const squareDiffs = numbers.map(value => {
    const diff = value - average;
    return diff * diff;
  });

  const avgSquareDiff = squareDiffs.reduce((sum, value) => sum + value, 0) / numbers.length;
  return Math.sqrt(avgSquareDiff);
}

// ==================== 偏差値計算 ====================
function calculateDeviation(value, average, stdDev) {
  if (stdDev === 0) return 50; // 標準偏差が0の場合は50
  return 50 + ((value - average) / stdDev) * 10;
}

// ==================== 投稿履歴&メトリクス&デイリーサマリー統合実行(トリガー用) ====================
function executeAnalysisRoutine() {
  console.log('🔥 分析ルーチン開始');

  // 1. 投稿一覧を取得
  fetchThreadsListAuto();

  // 2. メトリクスを取得
  Utilities.sleep(2000); // 2秒待機
  const metricsResult = fetchMetricsAuto();

  // 権限エラーの場合はログに記録(自動実行なのでダイアログは出さない)
  if (metricsResult && metricsResult.error === 'PERMISSION_DENIED') {
    console.log('⚠️ メトリクス取得: 権限エラー - 分析機能を使用するには追加の権限が必要です');
  }

  // 3. 偏差値計算&好反応判定
  Utilities.sleep(1000); // 1秒待機
  calculateDeviationsAndJudge();

  // 4. デイリーサマリー作成
  Utilities.sleep(1000); // 1秒待機
  createDailySummary();

  // 5. 高偏差値投稿をループ投稿に自動移設
  Utilities.sleep(1000); // 1秒待機
  transferHighDeviationToLoopAuto();

  // 6. 投稿方法(予約/手動)を判定・記録
  Utilities.sleep(1000); // 1秒待機
  updatePostingMethod();

  console.log('🔥 分析ルーチン完了(高偏差値移設・投稿方法判定含む)');
}

// ==================== 投稿履歴&分析の統合トリガー設定 ====================
function setupAnalysisTrigger() {
  // 既存のトリガーを削除
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'executeAnalysisRoutine' ||
        trigger.getHandlerFunction() === 'fetchThreadsListAuto') {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  // 新しいトリガーを作成(1時間ごと)
  ScriptApp.newTrigger('executeAnalysisRoutine')
    .timeBased()
    .everyHours(1)
    .create();

  showSuccess(' 投稿履歴&分析トリガー設定完了\n\n1時間ごとに以下を自動実行します:\n・投稿一覧取得\n・メトリクス取得(6時間経過後)\n・偏差値計算&好反応判定\n・デイリーサマリー作成 ← NEW!');
  console.log('✅ 投稿履歴&分析トリガー設定完了(デイリーサマリー含む)');
}

// ==================== 投稿履歴&分析の統合トリガー削除 ====================
function deleteAnalysisTrigger() {
  const triggers = ScriptApp.getProjectTriggers();
  let count = 0;

  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'executeAnalysisRoutine' ||
        trigger.getHandlerFunction() === 'fetchThreadsListAuto') {
      ScriptApp.deleteTrigger(trigger);
      count++;
    }
  });

  showSuccess(` 投稿履歴&分析トリガー削除完了\n\n${count}個のトリガーを削除しました。`);
  console.log(`✅ 投稿履歴&分析トリガー削除完了: ${count}個`);
}

// ==================== デイリーサマリー作成 ====================
function createDailySummary() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sourceSheet = ss.getSheetByName('投稿履歴');
  const targetSheet = ss.getSheetByName('デイリーサマリー');

  if (!sourceSheet) {
    console.log('エラー: 投稿履歴シートが見つかりません');
    return;
  }

  if (!targetSheet) {
    console.log('エラー: デイリーサマリーシートが見つかりません');
    return;
  }

  const today = Utilities.formatDate(new Date(), 'JST', 'yyyy/MM/dd');
  const todayRowIndex = findTodayRowIndex_(targetSheet, today);
  console.log(`今日の日付: ${today}, 既存行: ${todayRowIndex}`);

  const followersCount = getFollowersCount();
  const dailyData = groupPostsByDate_(sourceSheet);

  if (!dailyData[today]) {
    console.log('今日の投稿データがまだありません');
    return;
  }

  const todayData = buildDailySummaryData_(today, followersCount, dailyData[today]);

  if (todayRowIndex > 0) {
    targetSheet.getRange(todayRowIndex, 1, 1, 19).setValues([todayData]);
    console.log(`デイリーサマリー更新完了: ${today} (行${todayRowIndex})`);
  } else {
    targetSheet.appendRow(todayData);
    console.log(`デイリーサマリー追加完了: ${today} (新規)`);
  }
}

/** 指定日付の既存行インデックスを探す */
function findTodayRowIndex_(sheet, today) {
  const lastRow = sheet.getLastRow();
  if (lastRow <= 1) return -1;

  const existingDates = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
  for (let i = 0; i < existingDates.length; i++) {
    const dateStr = typeof existingDates[i][0] === 'string'
      ? existingDates[i][0]
      : Utilities.formatDate(existingDates[i][0], 'JST', 'yyyy/MM/dd');

    if (dateStr === today) {
      return i + 2;
    }
  }
  return -1;
}

/** 投稿履歴を日付ごとにグループ化 */
function groupPostsByDate_(sourceSheet) {
  const data = sourceSheet.getDataRange().getValues();
  const rows = data.slice(1);
  const dailyData = {};

  rows.forEach(row => {
    const dateTimeStr = row[1];
    if (!dateTimeStr) return;

    const dateStr = typeof dateTimeStr === 'string'
      ? dateTimeStr.split(' ')[0]
      : Utilities.formatDate(dateTimeStr, 'JST', 'yyyy/MM/dd');

    if (!dailyData[dateStr]) {
      dailyData[dateStr] = [];
    }

    const toNum = (v) => (typeof v === 'number') ? v : (isNaN(Number(v)) ? 0 : Number(v));

    dailyData[dateStr].push({
      views: toNum(row[4]),
      likes: toNum(row[5]),
      replies: toNum(row[6]),
      reposts: toNum(row[7]),
      quotes: toNum(row[8]),
      viewScore: toNum(row[9]),
      likeScore: toNum(row[10]),
      replyScore: toNum(row[11]),
      repostScore: toNum(row[12]),
      quoteScore: toNum(row[13]),
      totalScore: toNum(row[14])
    });
  });

  return dailyData;
}

/** デイリーサマリーデータを構築 */
function buildDailySummaryData_(today, followersCount, dayPosts) {
  const postCount = dayPosts.length;
  const metrics = calculateDailyMetrics_(dayPosts, postCount);
  const scores = calculateDailyScoreAverages_(dayPosts);

  return [
    today,
    followersCount,
    postCount,
    metrics.totalViews,
    metrics.totalLikes,
    metrics.totalReplies,
    metrics.totalReposts,
    metrics.totalQuotes,
    metrics.avgViews,
    metrics.avgLikes,
    metrics.avgReplies,
    metrics.avgReposts,
    metrics.avgQuotes,
    scores.avgViewScore,
    scores.avgLikeScore,
    scores.avgReplyScore,
    scores.avgRepostScore,
    scores.avgQuoteScore,
    scores.avgTotalScore
  ];
}

/** 日別の集計値を計算 */
function calculateDailyMetrics_(dayPosts, postCount) {
  const totalViews = dayPosts.reduce((sum, post) => sum + post.views, 0);
  const totalLikes = dayPosts.reduce((sum, post) => sum + post.likes, 0);
  const totalReplies = dayPosts.reduce((sum, post) => sum + post.replies, 0);
  const totalReposts = dayPosts.reduce((sum, post) => sum + post.reposts, 0);
  const totalQuotes = dayPosts.reduce((sum, post) => sum + post.quotes, 0);

  return {
    totalViews,
    totalLikes,
    totalReplies,
    totalReposts,
    totalQuotes,
    avgViews: Math.round(totalViews / postCount * 10) / 10,
    avgLikes: Math.round(totalLikes / postCount * 10) / 10,
    avgReplies: Math.round(totalReplies / postCount * 10) / 10,
    avgReposts: Math.round(totalReposts / postCount * 10) / 10,
    avgQuotes: Math.round(totalQuotes / postCount * 10) / 10
  };
}

/** スコア平均を計算 */
function calculateDailyScoreAverages_(dayPosts) {
  const calcAvg = (posts, key) => {
    const valid = posts.filter(post => post[key] > 0);
    return valid.length > 0
      ? Math.round(valid.reduce((sum, post) => sum + post[key], 0) / valid.length * 10) / 10
      : 0;
  };

  return {
    avgViewScore: calcAvg(dayPosts, 'viewScore'),
    avgLikeScore: calcAvg(dayPosts, 'likeScore'),
    avgReplyScore: calcAvg(dayPosts, 'replyScore'),
    avgRepostScore: calcAvg(dayPosts, 'repostScore'),
    avgQuoteScore: calcAvg(dayPosts, 'quoteScore'),
    avgTotalScore: calcAvg(dayPosts, 'totalScore')
  };
}

// ==================== デイリーサマリー作成(手動実行用) ====================
function createDailySummaryManual() {
  try {
    createDailySummary();
    showSuccess(' デイリーサマリー作成完了!\n\nデイリーサマリーシートを確認してください。');
    console.log('✅ デイリーサマリー作成完了(手動実行)');
  } catch (error) {
    showError(' デイリーサマリー作成失敗\n\n' + error.message);
    console.log('❌ デイリーサマリー作成失敗: ' + error.message);
  }
}

// ==================== 高偏差値投稿をループ投稿に移設(自動実行用) ====================
/**
 * トリガーから呼び出される自動実行版(ダイアログ表示なし)
 */
function transferHighDeviationToLoopAuto() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const historySheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST);
  const loopSheet = ss.getSheetByName(LOOP_CONFIG.SHEET_NAME);
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);

  if (!historySheet || !loopSheet || !settingsSheet) {
    console.log('❌ 必要なシートが見つかりません');
    return;
  }

  try {
    const prefixTextRaw = settingsSheet.getRange(CONFIG.CELL.INTRO_WORD).getValue() || '';
    const transferThreshold = Number(settingsSheet.getRange(CONFIG.CELL.TRANSFER_THRESHOLD).getValue()) || 65;
    console.log(`移設基準偏差値: ${transferThreshold}`);

    const historyLastRow = historySheet.getLastRow();
    if (historyLastRow < 2) {
      console.log('⏭️ 投稿履歴にデータなし');
      return;
    }

    const historyData = historySheet.getRange(2, 1, historyLastRow - 1, 17).getValues();
    const targetRows = [];
    const notRequiredRows = [];  // 移設不要の行

    for (let i = 0; i < historyData.length; i++) {
      const row = historyData[i];
      const postId = row[0];
      const postContent = row[2];    // C列: 投稿内容
      const totalDeviation = row[14];
      const transferFlag = row[16];

      // Q列が空で偏差値が記録されている行のみ処理
      if (postId && totalDeviation !== '' && (!transferFlag || transferFlag === '')) {
        if (Number(totalDeviation) >= transferThreshold) {
          // 基準値以上 → 移設対象
          targetRows.push({
            rowIndex: i + 2,
            postId: postId.toString().replace(/^'/, ''),
            postContent: postContent || '',
            deviation: totalDeviation
          });
        } else {
          // 基準値未満 → 移設不要
          notRequiredRows.push(i + 2);
        }
      }
    }

    // 移設不要フラグを設定
    if (notRequiredRows.length > 0) {
      for (const rowIndex of notRequiredRows) {
        historySheet.getRange(rowIndex, 17).setValue('移設不要');
      }
      console.log(`📝 移設不要フラグ設定: ${notRequiredRows.length}件`);
    }

    if (targetRows.length === 0) {
      console.log('⏭️ 移設対象なし');
      return;
    }

    let loopLastRow = loopSheet.getLastRow();
    let transferCount = 0;

    for (const target of targetRows) {
      // 誘導パワーワードをランダム選択(カンマ区切り対応)
      const prefixText = selectRandomPowerWord_(prefixTextRaw);
      const newContent = prefixText ? `${prefixText}\n${target.postId}` : target.postId;
      loopLastRow++;
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.NO).setValue(loopLastRow - 1);
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.THREAD_ID).setValue(target.postContent);  // F列: 投稿内容
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.CONTENT).setValue(newContent);
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.CHAR_COUNT).setFormula(`=LEN(G${loopLastRow})`);
      historySheet.getRange(target.rowIndex, 17).setValue('移設完了');
      console.log(`✅ 自動移設: 行${target.rowIndex}(偏差値: ${target.deviation})`);
      transferCount++;
    }

    console.log(`🔥 高偏差値投稿の自動移設完了: ${transferCount}件`);

  } catch (error) {
    console.log(`❌ 自動移設エラー: ${error.message}`);
  }
}

// ==================== 高偏差値投稿をループ投稿に移設(手動実行用) ====================
/**
 * 投稿履歴シートのO列(総合偏差値)が65以上の投稿を
 * ループ投稿管理シートのG列に移設する
 *
 * 処理内容:
 * 1. 投稿履歴O列が65以上 かつ Q列が空の行を検索
 * 2. A列の投稿IDを取得
 * 3. 自動生成設定B35のテキスト + 投稿ID をループ投稿管理G列に追加
 * 4. 投稿履歴Q列に「移設完了」を記録
 */
function transferHighDeviationToLoop() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const historySheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST); // 投稿履歴
  const loopSheet = ss.getSheetByName(LOOP_CONFIG.SHEET_NAME);            // ループ投稿管理
  const settingsSheet = ss.getSheetByName(CONFIG.SHEET_NAME_SETTINGS);    // 設定

  if (!historySheet) {
    showError('エラー\n\n「投稿履歴」シートが見つかりません');
    return;
  }

  if (!loopSheet) {
    showError('エラー\n\n「ループ投稿管理」シートが見つかりません');
    return;
  }

  if (!settingsSheet) {
    showError('エラー\n\n「設定」シートが見つかりません');
    return;
  }

  try {
    console.log('🔥 高偏差値投稿の移設開始');

    // 設定値を取得
    const prefixTextRaw = settingsSheet.getRange(CONFIG.CELL.INTRO_WORD).getValue() || '';
    const transferThreshold = Number(settingsSheet.getRange(CONFIG.CELL.TRANSFER_THRESHOLD).getValue()) || 65;
    console.log(`誘導パワーワード: ${prefixTextRaw}`);
    console.log(`移設基準偏差値: ${transferThreshold}`);

    // 投稿履歴の全データを取得(17列:A〜Q)
    const historyLastRow = historySheet.getLastRow();
    if (historyLastRow < 2) {
      showInfo('情報\n\n投稿履歴にデータがありません');
      return;
    }

    const historyData = historySheet.getRange(2, 1, historyLastRow - 1, 17).getValues();

    // 基準値以上 かつ Q列が空の行を抽出
    const targetRows = [];
    const notRequiredRows = [];  // 移設不要の行

    for (let i = 0; i < historyData.length; i++) {
      const row = historyData[i];
      const postId = row[0];              // A列: 投稿ID
      const postContent = row[2];         // C列: 投稿内容
      const totalDeviation = row[14];     // O列: 総合偏差値(15番目、0始まり)
      const transferFlag = row[16];       // Q列: 移設フラグ(17番目、0始まり)

      // Q列が空で偏差値が記録されている行のみ処理
      if (postId && totalDeviation !== '' && (!transferFlag || transferFlag === '')) {
        if (Number(totalDeviation) >= transferThreshold) {
          // 基準値以上 → 移設対象
          targetRows.push({
            rowIndex: i + 2,  // ヘッダー行を考慮
            postId: postId.toString().replace(/^'/, ''),  // 先頭のシングルクォートを除去
            postContent: postContent || '',
            deviation: totalDeviation
          });
        } else {
          // 基準値未満 → 移設不要
          notRequiredRows.push(i + 2);
        }
      }
    }

    // 移設不要フラグを設定
    if (notRequiredRows.length > 0) {
      for (const rowIndex of notRequiredRows) {
        historySheet.getRange(rowIndex, 17).setValue('移設不要');
      }
      console.log(`📝 移設不要フラグ設定: ${notRequiredRows.length}件`);
    }

    if (targetRows.length === 0) {
      showInfo('情報\n\n移設対象の投稿がありません\n(偏差値65以上かつ未移設の投稿なし)');
      return;
    }

    console.log(`移設対象: ${targetRows.length}件`);

    // ループ投稿管理の最終行を取得
    let loopLastRow = loopSheet.getLastRow();

    // 各対象行を処理
    let transferCount = 0;
    for (const target of targetRows) {
      // 誘導パワーワードをランダム選択(カンマ区切り対応)
      const prefixText = selectRandomPowerWord_(prefixTextRaw);
      // ループ投稿管理に追加する本文を作成
      const newContent = prefixText ? `${prefixText}\n${target.postId}` : target.postId;

      // ループ投稿管理に新しい行を追加
      loopLastRow++;
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.NO).setValue(loopLastRow - 1);
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.THREAD_ID).setValue(target.postContent);  // F列: 投稿内容
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.CONTENT).setValue(newContent);
      // 文字数カウント数式を設定
      loopSheet.getRange(loopLastRow, LOOP_CONFIG.COLUMN.CHAR_COUNT).setFormula(`=LEN(G${loopLastRow})`);

      // 投稿履歴のQ列に「移設完了」を記録
      historySheet.getRange(target.rowIndex, 17).setValue('移設完了');

      console.log(`✅ 移設完了: 行${target.rowIndex} → ループ行${loopLastRow}(偏差値: ${target.deviation})`);
      transferCount++;
    }

    showSuccess(`移設完了!\n\n${transferCount}件の高偏差値投稿をループ投稿管理に追加しました`);
    console.log(`🔥 移設完了: ${transferCount}件`);

  } catch (error) {
    const errorMsg = `エラー: ${error.message}\n\nStack: ${error.stack}`;
    showError(errorMsg);
    console.log(errorMsg);
  }
}

// ==================== 投稿方法判定(予約/手動)====================
/**
 * 投稿履歴のR列に「予約」または「手動」を記録
 *
 * ロジック:
 * 1. 投稿履歴シートのA列(投稿ID)を取得
 * 2. 自由投稿管理・固定投稿管理シートのM列(投稿ID)と照合
 * 3. マッチ → 「予約」、マッチしない → 「手動」
 * 4. R列(18列目)に結果を記録
 */
function updatePostingMethod() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const historySheet = ss.getSheetByName(CONFIG.SHEET_NAME_THREADS_LIST);
  const freeSheet = ss.getSheetByName(CONFIG.SHEET_NAME_POSTS_FREE);
  const fixedSheet = ss.getSheetByName(CONFIG.SHEET_NAME_POSTS_FIXED);

  if (!historySheet) {
    console.log('❌ 投稿履歴シートが見つかりません');
    return;
  }

  try {
    console.log('🔥 投稿方法判定開始');

    // R列(18列目)のヘッダーを確認・設定
    const header = historySheet.getRange(1, 18).getValue();
    if (!header || header !== '投稿方法') {
      historySheet.getRange(1, 18).setValue('投稿方法');
      historySheet.getRange(1, 18).setFontWeight('bold').setBackground('#4a86e8').setFontColor('#ffffff');
    }

    // 自由投稿管理・固定投稿管理から投稿IDを収集
    const scheduledPostIds = new Set();

    // 自由投稿管理のM列(13列目)から投稿IDを取得
    if (freeSheet) {
      const freeLastRow = freeSheet.getLastRow();
      if (freeLastRow > 1) {
        const freeIds = freeSheet.getRange(2, CONFIG.COLUMN.POST_ID, freeLastRow - 1, 1).getValues();
        freeIds.forEach(row => {
          if (row[0]) {
            const idStr = String(row[0]).replace(/^'/, '').trim();
            if (idStr) scheduledPostIds.add(idStr);
          }
        });
      }
    }

    // 固定投稿管理のM列(13列目)から投稿IDを取得
    if (fixedSheet) {
      const fixedLastRow = fixedSheet.getLastRow();
      if (fixedLastRow > 1) {
        const fixedIds = fixedSheet.getRange(2, CONFIG.COLUMN.POST_ID, fixedLastRow - 1, 1).getValues();
        fixedIds.forEach(row => {
          if (row[0]) {
            const idStr = String(row[0]).replace(/^'/, '').trim();
            if (idStr) scheduledPostIds.add(idStr);
          }
        });
      }
    }

    console.log(`予約投稿ID数: ${scheduledPostIds.size}件`);

    // 投稿履歴のA列とR列を取得
    const historyLastRow = historySheet.getLastRow();
    if (historyLastRow < 2) {
      console.log('⏭️ 投稿履歴にデータなし');
      return;
    }

    // A列(投稿ID)とR列(投稿方法)を取得
    const historyIds = historySheet.getRange(2, 1, historyLastRow - 1, 1).getValues();
    const postingMethods = historySheet.getRange(2, 18, historyLastRow - 1, 1).getValues();

    let updatedCount = 0;

    for (let i = 0; i < historyIds.length; i++) {
      const postId = historyIds[i][0];
      const currentMethod = postingMethods[i][0];

      // 既に記録済みならスキップ
      if (currentMethod && currentMethod !== '') {
        continue;
      }

      // 投稿IDがない場合はスキップ
      if (!postId) {
        continue;
      }

      const postIdStr = String(postId).replace(/^'/, '').trim();
      const method = scheduledPostIds.has(postIdStr) ? '予約投稿' : '手動投稿';

      historySheet.getRange(i + 2, 18).setValue(method);
      updatedCount++;
    }

    console.log(`✅ 投稿方法判定完了: ${updatedCount}件更新`);

  } catch (error) {
    console.log(`❌ 投稿方法判定エラー: ${error.message}`);
  }
}

// ==================== 投稿方法判定(手動実行用)====================
function updatePostingMethodManual() {
  updatePostingMethod();
  showSuccess('投稿方法判定完了!\n\n投稿履歴のR列を確認してください。');
}

// ==================== 誘導パワーワードのランダム選択 ====================
/**
 * カンマ区切りの誘導パワーワードから1つをランダムに選択
 * @param {string} rawText - カンマ区切りのテキスト(例:「見てね,チェック,要確認」)
 * @return {string} - ランダムに選択された1つのワード(空白トリム済み)
 */
function selectRandomPowerWord_(rawText) {
  if (!rawText || rawText.toString().trim() === '') {
    return '';
  }

  // カンマで分割して配列化
  const words = rawText.toString().split(',').map(w => w.trim()).filter(w => w !== '');

  if (words.length === 0) {
    return '';
  }

  if (words.length === 1) {
    return words[0];
  }

  // ランダムで1つ選択
  const randomIndex = Math.floor(Math.random() * words.length);
  return words[randomIndex];
}