import { readdirSync, statSync } from 'node:fs';
import { join } from 'node:path';

export interface ExistingFile {
  rel: string;
  size: number;
}

// workspace 直下で「入力候補」として提示しないもの（成果物 / ログ / 内部）。
const EXCLUDE_TOP = new Set(['logs', 'output', 'input', 'subtasks', '.conflict', '.git']);

/**
 * workspace 直下の非慣習ファイルと input/ 配下のファイルを、入力候補として列挙する（純関数）。
 * - 再帰はしない（直下 + input/ の浅いスキャンのみ。深い木は提示しすぎない）。
 * - ドットファイル・成果物 / ログ系ディレクトリは除外（Files タブ・commit 除外と揃える）。
 * - 存在しない / 読めないパスは throw せず空扱い。
 */
export function listExistingWorkspaceFiles(
  workspacePath: string,
  opts: { maxEntries?: number } = {},
): { files: ExistingFile[]; truncated: number } {
  const max = opts.maxEntries ?? 50;
  const found: ExistingFile[] = [];

  const scan = (dir: string, prefix: string) => {
    let entries: import('node:fs').Dirent[];
    try {
      entries = readdirSync(dir, { withFileTypes: true }) as import('node:fs').Dirent[];
    } catch {
      return;
    }
    for (const e of entries) {
      if (e.name.startsWith('.')) continue;
      if (prefix === '' && EXCLUDE_TOP.has(e.name)) continue;
      if (!e.isFile()) continue; // symlink / directory は除外（isFile は実ファイルのみ true）
      try {
        const st = statSync(join(dir, e.name));
        found.push({ rel: prefix ? `${prefix}/${e.name}` : e.name, size: st.size });
      } catch {
        // skip unreadable entry
      }
    }
  };

  scan(workspacePath, '');
  scan(join(workspacePath, 'input'), 'input');
  // input/ は明示的なアップロード先＝主役。root のノイズ（多数のループ生成物等）に
  // 埋もれて truncate で落ちないよう、input/ を先頭に寄せてから上限を切る。
  found.sort((a, b) => {
    const ai = a.rel.startsWith('input/') ? 0 : 1;
    const bi = b.rel.startsWith('input/') ? 0 : 1;
    if (ai !== bi) return ai - bi;
    return a.rel.localeCompare(b.rel);
  });
  const truncated = Math.max(0, found.length - max);
  return { files: found.slice(0, max), truncated };
}

function formatSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

/**
 * 表示用にファイル名を無害化する。成果物ツリーはスペース共有で、どのメンバーも
 * input/ にアップロードできる＝ファイル名は信頼できない。改行・制御文字をそのまま
 * 補間すると system prompt の行構造を割って指示を注入できる（例: 改行 + "## ..."）。
 * 制御文字（U+0000–U+001F と DEL）を空白へ畳み、過剰に長い名前を詰める。
 */
function sanitizeRelForPrompt(rel: string): string {
  let s = rel.replace(/[\x00-\x1f\x7f]+/g, ' ').replace(/\s{2,}/g, ' ').trim();
  if (s.length > 120) s = `${s.slice(0, 117)}…`;
  return s;
}

/**
 * system prompt 用の「ワークスペースの既存ファイル」節を生成する。
 * 入力候補が無ければ空文字（節ごと省略してノイズと誤誘導を避ける）。
 */
export function formatExistingFilesSection(workspacePath: string): string {
  const { files, truncated } = listExistingWorkspaceFiles(workspacePath);
  if (files.length === 0) return '';
  const lines = files.map(f => `- ${sanitizeRelForPrompt(f.rel)} (${formatSize(f.size)})`);
  const more = truncated > 0 ? `\n- ほか ${truncated} 件。Glob で全件確認してください` : '';
  return `\n\n## ワークスペースの既存ファイル\n以下のファイルが既にワークスペースにあります（あなたの入力候補です）。必要なら Read / Glob で参照してください。\n${lines.join('\n')}${more}`;
}
