/**
 * Audio Interop - Exposes AudioPlayer to Blazor via window.DeepDrftAudio
 */

import { AudioPlayer, AudioResult, StreamingResult, AudioState } from './AudioPlayer.js';

// Player instances by ID
const audioPlayers = new Map<string, AudioPlayer>();

// Readiness state, flipped true at the end of module execution once the API is
// attached to window. Read via DeepDrftAudio.isReady().
let ready = false;

// .NET interop type
interface DotNetObjectReference {
    invokeMethodAsync(methodName: string, ...args: unknown[]): Promise<unknown>;
}

// Global API exposed to Blazor
const DeepDrftAudio = {
    createPlayer: async (playerId: string): Promise<AudioResult> => {
        try {
            const player = new AudioPlayer();
            const result = await player.initialize();
            if (result.success) {
                audioPlayers.set(playerId, player);
            }
            return result;
        } catch (error) {
            return { success: false, error: (error as Error).message };
        }
    },

    initializeStreaming: (playerId: string, totalStreamLength: number, contentType: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.initializeStreaming(totalStreamLength, contentType);
    },

    processStreamingChunk: async (playerId: string, chunk: Uint8Array): Promise<StreamingResult> => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.processStreamingChunk(chunk);
    },

    startStreamingPlayback: async (playerId: string): Promise<AudioResult> => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.startStreamingPlayback();
    },

    markStreamComplete: async (playerId: string): Promise<StreamingResult> => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.markStreamComplete();
    },

    ensureAudioContextReady: async (playerId: string): Promise<AudioResult> => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.ensureAudioContextReady();
    },

    play: async (playerId: string): Promise<AudioResult> => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.play();
    },

    pause: (playerId: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.pause();
    },

    stop: (playerId: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.stop();
    },

    unload: (playerId: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.unload();
    },

    seek: (playerId: string, position: number): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.seek(position);
    },

    // New methods for seek-beyond-buffer support
    getBufferedDuration: (playerId: string): number => {
        const player = audioPlayers.get(playerId);
        return player?.getBufferedDuration() ?? 0;
    },

    calculateByteOffset: (playerId: string, positionSeconds: number): number => {
        const player = audioPlayers.get(playerId);
        return player?.calculateByteOffset(positionSeconds) ?? 0;
    },

    reinitializeFromOffset: (playerId: string, totalStreamLength: number, seekPosition: number): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.reinitializeFromOffset(totalStreamLength, seekPosition);
    },

    setVolume: (playerId: string, volume: number): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.setVolume(volume);
    },

    getCurrentTime: (playerId: string): number => {
        const player = audioPlayers.get(playerId);
        return player?.getCurrentTime() ?? 0;
    },

    getState: (playerId: string): AudioState | null => {
        const player = audioPlayers.get(playerId);
        return player?.getState() ?? null;
    },

    setOnProgressCallback: (
        playerId: string,
        dotNetRef: DotNetObjectReference,
        methodName: string
    ): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };

        player.setOnProgressCallback((currentTime: number) => {
            dotNetRef.invokeMethodAsync(methodName, currentTime);
        });
        return { success: true };
    },

    setOnEndCallback: (
        playerId: string,
        dotNetRef: DotNetObjectReference,
        methodName: string
    ): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };

        player.setOnEndCallback(() => {
            dotNetRef.invokeMethodAsync(methodName);
        });
        return { success: true };
    },

    // Spectrum analyzer methods
    getSpectrumData: (playerId: string): number[] | null => {
        const player = audioPlayers.get(playerId);
        return player?.getSpectrumData() ?? null;
    },

    setSpectrumHighPass: (playerId: string, freq: number): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.setSpectrumHighPass(freq);
    },

    setSpectrumLowPass: (playerId: string, freq: number): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.setSpectrumLowPass(freq);
    },

    setSpectrumSlope: (playerId: string, dbPerDecade: number): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        return player.setSpectrumSlope(dbPerDecade);
    },

    startSpectrumAnimation: (
        playerId: string,
        callbackId: string,
        dotNetRef: DotNetObjectReference,
        methodName: string
    ): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };

        player.startSpectrumAnimation(callbackId, (data: number[]) => {
            dotNetRef.invokeMethodAsync(methodName, data);
        });
        return { success: true };
    },

    stopSpectrumAnimation: (playerId: string, callbackId: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        player.stopSpectrumAnimation(callbackId);
        return { success: true };
    },

    getLevelDb: (playerId: string): number => {
        const player = audioPlayers.get(playerId);
        return player?.getLevelDb() ?? -Infinity;
    },

    startLevelAnimation: (
        playerId: string,
        callbackId: string,
        dotNetRef: DotNetObjectReference,
        methodName: string
    ): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        player.startLevelAnimation(callbackId, (db: number) => {
            dotNetRef.invokeMethodAsync(methodName, db);
        });
        return { success: true };
    },

    stopLevelAnimation: (playerId: string, callbackId: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (!player) return { success: false, error: 'Player not found' };
        player.stopLevelAnimation(callbackId);
        return { success: true };
    },

    disposePlayer: (playerId: string): AudioResult => {
        const player = audioPlayers.get(playerId);
        if (player) {
            player.dispose();
            audioPlayers.delete(playerId);
            return { success: true };
        }
        return { success: false, error: 'Player not found' };
    },

    // Readiness probe — true once this module has finished executing and the API
    // is attached to window. Blazor polls this before the first interop call so a
    // slow WASM boot / cache miss does not surface as a generic init failure.
    // Exposed as a method because Blazor JS interop invokes functions, not bare
    // properties.
    isReady: (): boolean => ready
};

// Expose to window
declare global {
    interface Window {
        DeepDrftAudio: typeof DeepDrftAudio;
    }
}

window.DeepDrftAudio = DeepDrftAudio;
// Flip ready last so a poller that sees isReady() === true is guaranteed the
// whole surface is attached and callable.
ready = true;

export { DeepDrftAudio };
