/**
 * ソースライブラリ・ヘルパー（NotebookLM 型）
 *
 * スペースタスクのエージェント取得物（WebFetch / DownloadFile / BrowseWeb）を
 * `{workspacePath}/source/` に蓄積し、出典メタを `source/index.jsonl` に追記する。
 *
 * 注意: これはヘルパーモジュールであり、ツールではない（TOOL_DEFS を持たない）。
 * raw-save.ts と同じ位置づけ。
 */

import * as fs from 'fs';
import * as path from 'path';
import { logger } from '../../logger.js';

/** ソースを生成したツール種別 */
export type SourceTool = 'WebFetch' | 'DownloadFile' | 'BrowseWeb';

/** source/index.jsonl の 1 行 */
export interface SourceIndexEntry {
  /** source/ 配下の相対ファイル名 */
  file: string;
  url: string;
  title?: string;
  /** ISO 8601 */
  fetched_at: string;
  tool: SourceTool;
  bytes: number;
}

const INDEX_FILENAME = 'index.jsonl';
const MAX_FILENAME_LEN = 120;

/** source/ ディレクトリの絶対パス */
export function sourceDir(workspacePath: string): string {
  return path.join(workspacePath, 'source');
}

/** index.jsonl の絶対パス */
export function sourceIndexPath(workspacePath: string): string {
  return path.join(sourceDir(workspacePath), INDEX_FILENAME);
}

/**
 * URL / タイトルから安全なファイル名（拡張子なし）を生成する。
 * - パス区切り `/`, `\`, `..`, 制御文字, Windows 禁止文字を除去
 * - 長さを MAX_FILENAME_LEN に制限
 * - 空になったら 'source' にフォールバック
 */
export function sanitizeSourceName(input: string): string {
  let name = (input ?? '').trim();
  // URL らしき入力はパス末尾＋ホストでヒントを作る
  try {
    const u = new URL(name);
    const last = u.pathname.split('/').filter(Boolean).pop() ?? '';
    name = [u.hostname, last].filter(Boolean).join('-') || u.hostname;
  } catch {
    /* URL でなければそのまま使う（タイトル等） */
  }
  // パス区切り・親参照・禁止文字・制御文字を安全な区切りに置換
  name = name
    .replace(/[\\/]+/g, '-')
    .replace(/\.\.+/g, '-')
    .replace(/[\x00-\x1f<>:"|?*]+/g, '-')
    .replace(/[\s]+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^[-.]+|[-.]+$/g, '');
  if (!name) name = 'source';
  if (name.length > MAX_FILENAME_LEN) name = name.slice(0, MAX_FILENAME_LEN).replace(/[-.]+$/, '');
  return name;
}

/**
 * 既存 index から url 済みエントリを探す（重複検出用）。
 * 読めない / 不正行はスキップ。
 */
export function findSourceByUrl(workspacePath: string, url: string): SourceIndexEntry | null {
  const idx = sourceIndexPath(workspacePath);
  let raw: string;
  try {
    raw = fs.readFileSync(idx, 'utf-8');
  } catch {
    return null;
  }
  for (const line of raw.split('\n')) {
    const trimmed = line.trim();
    if (!trimmed) continue;
    try {
      const entry = JSON.parse(trimmed) as SourceIndexEntry;
      if (entry.url === url) return entry;
    } catch {
      /* 壊れた行は無視 */
    }
  }
  return null;
}

/** index.jsonl に 1 行追記する（source/ は mkdir -p）。 */
export function appendSourceIndex(workspacePath: string, entry: SourceIndexEntry): void {
  try {
    fs.mkdirSync(sourceDir(workspacePath), { recursive: true });
    fs.appendFileSync(sourceIndexPath(workspacePath), `${JSON.stringify(entry)}\n`, 'utf-8');
  } catch (e) {
    logger.warn(`[source-library] failed to append index: ${(e as Error).message}`);
  }
}

/**
 * source/ 配下に `preferredName{.ext}` で書き込む。既存ファイルと衝突したら
 * `-1`, `-2`, ... の数値サフィックスで版を分ける（既存を上書きしない）。
 * @returns source/ 配下からの相対ファイル名（index.jsonl に記録する値）
 */
export function resolveUniqueSourceFile(workspacePath: string, preferredName: string, ext: string): string {
  const dir = sourceDir(workspacePath);
  const normalizedExt = ext && !ext.startsWith('.') ? `.${ext}` : ext;
  let candidate = `${preferredName}${normalizedExt}`;
  let n = 1;
  while (fs.existsSync(path.join(dir, candidate))) {
    candidate = `${preferredName}-${n}${normalizedExt}`;
    n += 1;
  }
  return candidate;
}

/**
 * テキスト本文を source/ に保存し、index.jsonl に追記する共有ヘルパー。
 *
 * 重複ポリシー: 同一 URL のエントリが既にあれば再保存せず null を返す（skip）。
 * 異なる URL で同じファイル名になった場合のみ数値サフィックスで版分けする。
 *
 * @returns 保存した相対ファイル名。skip した場合は null。
 */
export function saveSourceFile(
  workspacePath: string,
  opts: {
    url: string;
    title?: string;
    body: string;
    tool: SourceTool;
    /** 拡張子（'.md' / '.txt' 等）。既定 '.md' */
    ext?: string;
    /** ISO timestamp。未指定なら now */
    fetchedAt?: string;
  },
): string | null {
  const { url, title, body, tool } = opts;
  if (!body || body.trim().length === 0) return null; // 空本文は保存しない

  // 同一 URL は重複保存しない（最も単純で正しい挙動）
  if (findSourceByUrl(workspacePath, url)) return null;

  const ext = opts.ext ?? '.md';
  const baseName = sanitizeSourceName(title?.trim() || url);
  const relFile = resolveUniqueSourceFile(workspacePath, baseName, ext);

  const dir = sourceDir(workspacePath);
  fs.mkdirSync(dir, { recursive: true });
  const buf = Buffer.from(body, 'utf-8');
  fs.writeFileSync(path.join(dir, relFile), buf);

  appendSourceIndex(workspacePath, {
    file: relFile,
    url,
    ...(title ? { title } : {}),
    fetched_at: opts.fetchedAt ?? new Date().toISOString(),
    tool,
    bytes: buf.length,
  });

  return relFile;
}
