// src/commands/tools.ts
import * as vscode from 'vscode';
import OpenAI from 'openai';

import * as fs from 'fs';
import * as path from 'path';

// --- 定数・グローバル変数の定義 ---
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
const MAX_TOKENS=2048;

let openai: OpenAI ;
let apiKey: string | undefined = '';
let baseURL: string = '';
let selectedModel: string = '' ;
/** チャットの対話履歴を保持する配列 */
let messageHistory: Array<{ role: "user" | "assistant"; content: string }> = [];
/** 設定ファイル（config.json）のデータを保持するオブジェクト */
let config: any, key: string, value: any;
/** コード生成用のシステムプロンプト */
let createCodePrompt: string = '';
/** コード生成用プロンプトファイルのパス */
let createCodePromptPath: string;

/**
 * 設定ファイル（config.json）を読み込み、グローバル変数に設定します。
 * @returns {any} 読み込んだ設定オブジェクト。ファイルが存在しない場合はundefined。
 */
export function loadConfig() {
    const configPath = path.join(PROJECT_ROOT, 'config.json');
    if (fs.existsSync(configPath)) {
        const configFileContent = fs.readFileSync(configPath, 'utf8');
        config = JSON.parse(configFileContent);
        // 設定値のフォールバック処理（未設定時のデフォルト値）
        apiKey = config.apiKey || 'hogehoge';
        baseURL = config.baseURL || 'http://localhost:11434';
        return config;
    } else {
        vscode.window.showErrorMessage("設定ファイルが見つかりません。");
    }
}

/**
 * コード生成用のシステムプロンプトをファイルから取得します。
 * @returns {string} 取得したプロンプト文字列
 */
export function getCreateCodePrompt():string{
        // 設定ファイルからパスを取得（デフォルトは 'createCodePrompt.txt'）    
        createCodePromptPath =  config.createCodePromptPath || 'createCodePrompt.txt';
        let absoluteFilePath = path.join(PROJECT_ROOT, createCodePromptPath );

    try {
        if (fs.existsSync(absoluteFilePath)) {
            // ファイルを読み込んで内容を取得
            const createCodePrompt = fs.readFileSync(absoluteFilePath, 'utf-8').trim();
            return createCodePrompt;
        } else {
            console.error('ファイルが見つかりません');
            return '';
        } 
    } catch (error) {
        console.error('ファイルの読み込み中にエラーが発生しました', error);
        return '';
    }        
}

/**
 * OpenAIクライアントを初期化します。
 */
export function initializeOpenAI() {
    loadConfig();

    if (!apiKey) {
        vscode.window.showErrorMessage("APIキーが設定されていません。");
        return;
    }
    // OpenAI/Ollama互換APIクライアントのインスタンスを作成
    openai = new OpenAI({ apiKey, baseURL });
}


/**
 * 選択されたモデルを設定します。
 */
export function setModel(modelId: string) {
    selectedModel = modelId;
    vscode.window.showInformationMessage(`現在のモデルが設定されました: ${modelId}`);
}

/**
 * 現在選択されているモデルを取得します。
 * @returns {string | null} 選択されているモデルID（未設定時は空文字）
 */
export function getModel(): string | null {
    return selectedModel;
}

/**
 * モデル一覧をローカルLLM（ollama）またはOpenAI APIから取得します。
 * @returns {Promise<string[]>} 利用可能なモデルIDの配列
 * @throws {Error} OpenAIクライアントが未初期化、またはモデル情報の取得に失敗した場合
 */
export async function fetchModelsList(): Promise<string[]> {

    if (!openai) {
        throw new Error("OpenAIクライアントが初期化されていません。");
    }

    try {
        const response = await openai.models.list();
        // レスポンスデータの整合性をチェックしてモデルIDのリストを抽出
        if (response.data && Array.isArray(response.data)) {
            return response.data.map(model => model.id);
        } else {
            throw new Error("モデル情報が不正です。");
        }
    } catch (error) {
        vscode.window.showErrorMessage(`モデル一覧の取得中にエラーが発生しました: ${error}`);
        throw error;
    }

}

/**
 * 履歴を保持した状態でOpenAI APIとチャットを行います（通常の非同期応答）。
 * @param {string} message - ユーザーからの入力メッセージ
 * @returns {Promise<string>} アシスタントからの返答メッセージ
 * @throws {Error} 未初期化、モデル未選択、またはAPI通信に失敗した場合
 */
export async function chatWithOpenAI(message: string): Promise<string> {
    try {
        if (!openai) {
            throw new Error("OpenAI API is not initialized.");
        }
        if (!selectedModel) {
            throw new Error("モデルが選択されていません。先にモデル一覧表示・選択を実行してください。");
        }

        // メッセージ履歴に新しいユーザーメッセージを追加
        messageHistory.push({ role: "user", content: message });
        // APIリクエストの送信
        const response = await openai.chat.completions.create({
            model: selectedModel,
            messages: messageHistory, // 履歴全体を渡す
        });
        // 応答結果を履歴に追加して呼び出し元に返す
        const assistantMessage =  response.choices[0].message.content ?? "";   
        messageHistory.push({ role: "assistant", content: assistantMessage });

        return assistantMessage;
    } catch (error) {
        console.error("Error during chat with OpenAI:", error);
        throw new Error("Failed to communicate with OpenAI API.");
    }
}

/**
 * 履歴を保持した状態でOpenAI APIとチャットを行います（ストリーミング応答）。
 * @param {string} message - ユーザーからの入力メッセージ
 * @returns {AsyncGenerator<string>} 文字片（チャンク）を逐次返す非同期ジェネレータ
 * @throws {Error} 未初期化、モデル未選択、またはAPI通信に失敗した場合
 */
export async function* chatWithOpenAIStream(message: string): AsyncGenerator<string> {
    try {
        if (!openai) {
            throw new Error("OpenAI API is not initialized.");
        }
        if (!selectedModel) {
            throw new Error("モデルが選択されていません。先にモデル一覧表示・選択を実行してください。");
        }

        // 履歴にユーザーメッセージを追加
        messageHistory.push({ role: "user", content: message });
        // ストリーミングモードを有効にしてAPIリクエストを送信
        const stream = await openai.chat.completions.create({
            model: selectedModel,
            messages: messageHistory,
            stream: true, // ストリーミングを有効化
        });

        let fullContent = "";

        // 受信したストリームデータをチャンクごとに処理
        for await (const chunk of stream) {
            const content = chunk.choices[0]?.delta?.content || "";
            if (content) {
                fullContent += content;
                yield content; // 文字片（チャンク）を逐次返す
            }
        }

        // 完了後、結合した最終的なアシスタントのメッセージを履歴に保存
        messageHistory.push({ role: "assistant", content: fullContent });

    } catch (error) {
        console.error("Error during streaming with OpenAI:", error);
        throw new Error("Failed to communicate with OpenAI API.");
    }
}
/**
 * チャットの対話履歴をクリアします。
 */
export function clearHistory() {
    messageHistory = [];
}

/**
 * 与えられたシステムプロンプトとユーザープロンプトからプログラムコードを単発で生成します（履歴は保持しません）。
 * @param {string} systemPrompt - 役割や生成規則を定義するシステムプロンプト
 * @param {string} userPrompt - 具体的な生成指示を出すユーザープロンプト
 * @returns {Promise<string>} 生成されたコード文字列
 * @throws {Error} コードが生成されなかった場合、またはAPI通信に失敗した場合
 */
export async function generateCode(systemPrompt: string, userPrompt: string): Promise<string> {
    try {
        // 対話履歴は使用せず、指定されたプロンプトの組み合わせのみでリクエスト
        const response = await openai.chat.completions.create({
            model: selectedModel,
            messages: [
                { role: 'system', content: systemPrompt },
                { role: 'user', content: userPrompt }
            ],
            max_tokens: MAX_TOKENS // 最大トークン数を制限
        });

        if (response.choices && response.choices[0].message.content ) {
            return response.choices[0].message.content ;
        } else {
            throw new Error('No completion was generated.');
        }
    } catch (error) {
        console.error('Error generating code:', error);
        throw new Error(`Failed to generate code: ${error}`);
    }
}