/**
 * OpenAI Realtime Agent - 接続関連ユーティリティ
 * widget-app-persistent.js で使用される共通関数
 *
 * NS 移植版: `/realtime-token` がサーバー側で instructions/voice/turn_detection/
 * transcription を確定するため、`fetchSessionConfig()` はネットワークを一切呼ばず
 * `window.__AGENT_CONFIG__` からローカル合成する (計画書 §1.2.2 / §3.2(2))。
 * これに伴い、client からの `session.update` に instructions を含めることも禁止
 * される (二重指定でサーバ確定値を上書きしてしまうため — §3.2 の重要な整合点)。
 */

import { ephemeralTokenManager } from "./openai-realtime-connector.js";
import { createLogger } from "./debug-logger.js";
import { UserIdManager } from "../core/user-id-manager.js";

const logger = createLogger('ConnectionUtils');

/**
 * マイクのウォームアップ
 * ページ読み込み時に一度getUserMediaを呼び出し、
 * permission / device選択 / audio graphを事前に初期化する
 * これにより次回のgetUserMediaが100〜200msに短縮される
 *
 * @param {Object} state - 状態オブジェクト（micWarmedUpプロパティを持つ）
 * @param {Function} log - ログ出力関数
 * @returns {Promise<boolean>} - ウォームアップ成功時true
 */
export async function warmupMicrophone(state, log = logger.log.bind(logger)) {
    if (state.micWarmedUp) return true;

    log("Starting microphone warmup...");
    try {
        // 軽量なconstraintsでウォームアップ
        const stream = await navigator.mediaDevices.getUserMedia({
            audio: {
                echoCancellation: false,
                noiseSuppression: false,
                autoGainControl: false
            }
        });
        // 取得したトラックは即座に停止
        stream.getTracks().forEach(t => t.stop());
        state.micWarmedUp = true;
        log("Microphone warmup complete");
        return true;
    } catch (error) {
        log(`Microphone warmup error: ${error.message}`);
        return false;
    }
}

/**
 * セッション設定をローカル合成する (ネットワークを一切呼ばない — A8)。
 *
 * NS の `/realtime-token` はサーバー側で instructions/voice/transcription/
 * turn_detection を構築済みで返すため、ここでは `window.__AGENT_CONFIG__` から
 * クライアント側で必要な値だけを合成して返す。`instructions` は意図的に
 * `null` を返す — サーバー確定値をクライアントが上書きしないため。
 *
 * @param {Object} [_options] - pocxr 互換の引数 (userId/pageContext/conversationSummary)。
 *   ローカル合成では使用しないが呼び出し元互換のため受け取る。
 * @returns {Promise<Object>} - { instructions, model, voice_settings, tools, tts_config }
 */
export async function fetchSessionConfig(_options = {}) {
    const cfg = (typeof window !== "undefined" && window.__AGENT_CONFIG__) || {};
    return {
        instructions: null,
        model: null,
        voice_settings: { voice: cfg.realtime_voice || "alloy" },
        tools: [],
        tts_config: {},
    };
}

/**
 * エフェメラルトークンを事前取得
 * サーバーから session-config を取得し、その設定でトークンを取得する
 *
 * @param {Function} log - ログ出力関数
 * @param {Object} [options] - { userId, pageContext }
 * @returns {Promise<boolean>} - 取得成功時true
 */
export async function prefetchEphemeralToken(log = logger.log.bind(logger), options = {}) {
    log("Prefetching the ephemeral token...");
    try {
        // サーバーからセッション設定を取得
        const sessionConfig = await fetchSessionConfig(options);

        // セッション設定でトークン取得
        await ephemeralTokenManager.prefetch(sessionConfig);
        const status = ephemeralTokenManager.getCacheStatus();
        log(`Token prefetch complete (${status.remainingSeconds}s until expiry)`);
        return true;
    } catch (error) {
        log(`Token prefetch error: ${error.message}`);
        return false;
    }
}

/**
 * 接続直後の Instruction レイヤー処理。
 *
 * NS では `/realtime-token` が instructions を確定済みで返すため、ここから
 * `connector.updateSession()` へ instructions を送ることは行わない (二重指定で
 * サーバ確定値を上書きしてしまうため — §3.2 の重要な整合点。session.update は
 * ツール定義とモダリティのみに絞る)。DataChannel の readiness 待機と、
 * greeting 前の VAD 自動応答抑制 (ローカル state のみ、送信はしない) だけを行う。
 *
 * @param {Object} context - コンテキストオブジェクト
 * @param {string} context.currentInstructions - 呼び出し元互換のため受け取るが変更しない
 * @param {Object} context.connector - OpenAIRealtimeConnectorインスタンス
 * @param {Function} context.log - ログ出力関数
 * @param {Function} context.loadContextLater - 後方互換のため呼び出すが常に no-op
 * @returns {string} - 現在のcurrentInstructions (変更なし)
 */
export async function sendInstructionLayers(context) {
    const { connector, log, loadContextLater } = context;
    const { currentInstructions } = context;

    // DataChannelがopenになるまで待機
    if (connector.waitForDataChannel) {
        const ready = await connector.waitForDataChannel();
        if (!ready) {
            log("[Layer] WARNING: the DataChannel never became open");
            return currentInstructions;
        }
    }

    // Issue #558: greeting 送信前に VAD 自動 response.create を一時的に無効化
    // (ローカル state のみ — session.update では送信しない)。
    connector.setAutoResponseFlag?.(false);
    log("[Layer] instructions use the server-confirmed value (nothing sent via session.update)");

    if (context.onInstructionsUpdated) {
        context.onInstructionsUpdated(currentInstructions);
    }

    // 後方互換: loadContextLater は常に no-op (fetchUserContext が常に null を返す)
    if (loadContextLater) {
        try {
            await loadContextLater();
        } catch (e) {
            log(`[Layer] context: error (ignored): ${e?.message ?? e}`);
        }
    }

    return currentInstructions;
}

/**
 * ユーザーコンテキストの後追い取得 (記憶機能・チャットログ機能は削除済み)。
 * `fetchUserContext` は常に `null` を返すため、実質的に常に no-op となる。
 * session.update への送信は行わない (§3.2 の重要な整合点)。
 *
 * @param {Object} context - コンテキストオブジェクト
 * @param {string} context.currentInstructions - 現在のinstructions (変更なし)
 * @param {Function} context.fetchUserContext - ユーザーコンテキスト取得関数 (常に null)
 * @returns {Promise<string>} - currentInstructions (変更なし)
 */
export async function loadContextLater(context) {
    const { log, fetchUserContext } = context;
    const { currentInstructions } = context;

    const existingUserId = UserIdManager.getUserId();
    if (!existingUserId) {
        log?.("[Layer] context: no existing user, skipping");
        return currentInstructions;
    }

    try {
        const contextData = fetchUserContext ? await fetchUserContext(existingUserId) : null;
        if (!contextData) {
            log?.("[Layer] context: no data");
        }
    } catch (error) {
        log?.(`[Layer] context: error - ${error.message}`);
    }

    return currentInstructions;
}
