import { Recorder, RecorderEvents } from "./Recorder";
export var MicState;
(function (MicState) {
    MicState[MicState["Idle"] = 0] = "Idle";
    MicState[MicState["Initializing"] = 1] = "Initializing";
    MicState[MicState["Recording"] = 2] = "Recording";
})(MicState || (MicState = {}));
export var MicEvents;
(function (MicEvents) {
    MicEvents["StateChanged"] = "stateChanged";
    MicEvents["Data"] = "data";
    MicEvents["Level"] = "level";
    MicEvents["Finished"] = "finished";
    MicEvents["Abort"] = "abort";
    MicEvents["Error"] = "error";
})(MicEvents || (MicEvents = {}));
export class MicStateManager {
    recorder = new Recorder();
    state = MicState.Idle;
    chunks = [];
    stream;
    listeners = {
        [MicEvents.StateChanged]: new Set(),
        [MicEvents.Data]: new Set(),
        [MicEvents.Level]: new Set(),
        [MicEvents.Finished]: new Set(),
        [MicEvents.Abort]: new Set(),
        [MicEvents.Error]: new Set(),
    };
    initialized = false;
    on(event, handler) {
        this.listeners[event].add(handler);
        return () => {
            this.listeners[event].delete(handler);
        };
    }
    emit(event, ...args) {
        for (const handler of this.listeners[event]) {
            ;
            handler(...args);
        }
    }
    init() {
        if (this.initialized) {
            return;
        }
        this.initialized = true;
        this.recorder.on(RecorderEvents.Data, (data) => {
            this.chunks.push(data);
            this.emit(MicEvents.Data, data);
        });
        this.recorder.on(RecorderEvents.Level, (levels) => {
            this.emit(MicEvents.Level, levels);
        });
        this.recorder.on(RecorderEvents.Stop, () => {
            const blob = new Blob(this.chunks, { type: "audio/pcm;rate=16000" });
            if (blob.size < 1024 * 8) {
                this.abort();
                return;
            }
            this.emit(MicEvents.Finished, blob);
            this.reset();
        });
        this.recorder.on(RecorderEvents.Error, (error) => {
            this.abort();
            this.emit(MicEvents.Error, error);
        });
    }
    async start() {
        if (this.state === MicState.Initializing || this.state === MicState.Recording) {
            return;
        }
        this.setState(MicState.Initializing);
        try {
            if (!navigator.mediaDevices?.getUserMedia) {
                throw new Error("Microphone access is not available in this browser.");
            }
            this.releaseStream();
            this.stream = await navigator.mediaDevices.getUserMedia({
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true,
                },
            });
            await this.recorder.init(this.stream);
            this.stream.getTracks().forEach((track) => {
                track.enabled = true;
            });
            await this.recorder.startRecording();
            this.setState(MicState.Recording);
        }
        catch (error) {
            this.reset();
            this.emit(MicEvents.Error, toError(error));
        }
    }
    stop() {
        if (this.state !== MicState.Initializing && this.state !== MicState.Recording) {
            return;
        }
        if (this.state === MicState.Initializing) {
            this.abort();
            return;
        }
        this.recorder.stopRecording();
        this.recorder.detachStream();
        this.pauseStream();
        this.setState(MicState.Idle);
    }
    abort() {
        if (this.state === MicState.Idle) {
            return;
        }
        this.recorder.stopRecording();
        this.recorder.detachStream();
        this.pauseStream();
        this.emit(MicEvents.Abort);
        this.reset();
    }
    dispose() {
        this.recorder.stopRecording();
        this.releaseStream();
        this.recorder.dispose();
        this.reset();
    }
    reset() {
        this.chunks = [];
        this.setState(MicState.Idle);
    }
    pauseStream() {
        this.stream?.getTracks().forEach((track) => {
            track.enabled = false;
        });
    }
    releaseStream() {
        this.recorder.detachStream();
        this.stream?.getTracks().forEach((track) => {
            try {
                track.stop();
            }
            catch {
                // ignore track shutdown failures during teardown
            }
        });
        this.stream = undefined;
    }
    setState(state) {
        this.state = state;
        this.emit(MicEvents.StateChanged, { state });
    }
}
function toError(error) {
    return error instanceof Error ? error : new Error(String(error));
}
