import type { OpenAICompatClient, Message } from '../llm/openai-compat.js';
import { logger } from '../logger.js';

export interface PieceDescription {
  name: string;
  description: string;
  keywords?: string[];
}

export function buildClassificationPrompt(
  taskText: string,
  pieces: PieceDescription[],
  fileNames: string[],
): string {
  const pieceList = pieces
    .map(p => `- ${p.name}: ${p.description}`)
    .join('\n');
  const filesLine = fileNames.length > 0
    ? `\n添付ファイル: ${fileNames.join(', ')}`
    : '';

  // キーワードマッチした piece をヒントとして追加
  const keywordHints = pieces
    .filter(p => p.keywords && p.keywords.length > 0)
    .map(p => {
      const matched = p.keywords!.filter(kw => taskText.includes(kw));
      return matched.length > 0 ? `- ${p.name}: マッチしたキーワード [${matched.join(', ')}]` : null;
    })
    .filter(Boolean);
  const hintLine = keywordHints.length > 0
    ? `\nキーワードマッチによる候補（参考）:\n${keywordHints.join('\n')}\n`
    : '';

  return `以下のタスクに最適な処理タイプを1つ選んでください。選択肢名のみ回答してください。

## 選択ルール（重要）
- **デフォルトは "chat"** — 特化型 piece に明確にマッチしない依頼はすべて "chat" を選ぶ
- 特化型 piece を選ぶのは、タスク内容が以下のいずれかに **強く** 該当する場合のみ:
  - スライド/プレゼン作成依頼 → slide
  - データ加工・集計・分析依頼 → data-process
  - 構造化された調査レポート作成依頼 → research
  - ブレスト・アイデア出し依頼 → brainstorming
  - その他、piece description が依頼内容と直接対応する場合
- 単なる質問・対話・コード生成・文書執筆・短いタスクは "chat" を選ぶ
- 迷ったら "chat" を選ぶこと

選択肢:
${pieceList}
${hintLine}
タスク内容:
${taskText.slice(0, 800)}${filesLine}`;
}

export function parseClassificationResponse(
  response: string,
  validPieceNames: string[],
): string | null {
  const cleaned = response
    .replace(/<think>[\s\S]*?<\/think>/g, '')
    .trim()
    .toLowerCase();
  if (!cleaned) return null;

  // 完全一致を試す
  for (const name of validPieceNames) {
    if (cleaned === name) return name;
  }
  // 部分一致を試す（長い名前順にソートし、短い名前が先にマッチするのを防ぐ）
  const sorted = [...validPieceNames].sort((a, b) => b.length - a.length);
  for (const name of sorted) {
    if (cleaned.includes(name)) return name;
  }
  return null;
}

export async function classifyPiece(
  client: OpenAICompatClient,
  taskText: string,
  pieces: PieceDescription[],
  fileNames: string[],
  timeoutMs: number = 8000,
  userId?: string,
): Promise<string | null> {
  const prompt = buildClassificationPrompt(taskText, pieces, fileNames);
  logger.debug(`[piece-classifier] candidates=[${pieces.map(p => p.name).join(', ')}] textLen=${taskText.length}`);
  const messages: Message[] = [{ role: 'user', content: prompt }];

  const llmCall = async (): Promise<string | null> => {
    let result = '';
    try {
      for await (const event of client.chat(messages, undefined, undefined, { userId })) {
        if (event.type === 'text') result += event.text;
        else if (event.type === 'error') return null;
        else if (event.type === 'done') break;
      }
    } catch (err) {
      logger.warn(`[piece-classifier] LLM call failed: ${err}`);
      return null;
    }
    const validNames = pieces.map(p => p.name);
    const classified = parseClassificationResponse(result, validNames);
    if (classified) {
      logger.info(`[piece-classifier] classified piece=${classified} candidates=${validNames.length} textLen=${taskText.length}`);
    } else {
      logger.warn(`[piece-classifier] classification failed rawResponse="${result.slice(0, 100)}" validNames=[${validNames.join(', ')}]`);
    }
    return classified;
  };

  return Promise.race([
    llmCall(),
    new Promise<null>((resolve) => setTimeout(() => {
      logger.warn(`[piece-classifier] LLM call timed out after ${timeoutMs}ms`);
      resolve(null);
    }, timeoutMs)),
  ]);
}
