/** @odoo-module **/
// Fusion Notes - Chatter Voice Note Integration
// Copyright 2026 Nexa Systems Inc.
// License OPL-1
//
// Patches the Odoo 19 Chatter component to add a voice recording
// microphone button.  Records audio via MediaRecorder, transcribes
// with OpenAI Whisper, optionally formats with GPT, and posts the
// result as a "Log note" in the chatter.

import { Chatter } from "@mail/chatter/web_portal/chatter";
import { patch } from "@web/core/utils/patch";
import { onWillUnmount } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import {
    AudioRecorder,
    SpeechRecognizer,
    blobToBase64,
} from "./voice_note_service";
import { rpc } from "@web/core/network/rpc";

// ---------------------------------------------------------------------------
// Settings cache - avoids repeated RPC calls for user preferences
// ---------------------------------------------------------------------------
let _settingsCache = null;
let _settingsCacheTime = 0;
const SETTINGS_CACHE_TTL = 60000; // 1 minute

async function getSettings() {
    const now = Date.now();
    if (_settingsCache && now - _settingsCacheTime < SETTINGS_CACHE_TTL) {
        return _settingsCache;
    }
    try {
        const result = await rpc('/fusion_notes/get_settings');
        _settingsCache = result;
        _settingsCacheTime = now;
        return result;
    } catch {
        return {
            review_mode: true,
            ai_format: false,
            max_seconds: 300,
            has_api_key: false,
        };
    }
}

// ---------------------------------------------------------------------------
// Patch Chatter to add voice note capabilities
// ---------------------------------------------------------------------------
patch(Chatter.prototype, {
    setup() {
        super.setup(...arguments);

        // Notification service for user feedback
        this.notificationService = useService("notification");

        // Voice note reactive state
        Object.assign(this.state, {
            // Status: idle | recording | transcribing | formatting | review
            voiceStatus: 'idle',
            voiceText: '',             // Current display text
            voiceRawText: '',          // Original transcription
            voiceFormattedText: '',    // AI-formatted version (cached)
            voiceAiFormat: false,      // AI formatting toggle
            voiceQuickPost: false,     // Quick-post mode toggle
            voiceDuration: 0,          // Recording duration in seconds
            voiceError: '',            // Last error message
        });

        // Private instances (not reactive)
        this._voiceRecorder = new AudioRecorder();
        this._voiceSpeechRecognizer = null;
        this._voiceTimer = null;
        this._voiceUsingSpeechFallback = false;
        this._voiceSpeechPromise = null;

        // Cleanup on component destroy
        onWillUnmount(() => {
            this._voiceCleanup();
        });
    },

    // ===================================================================
    // Public methods (called from template)
    // ===================================================================

    /**
     * Start recording audio from the microphone.
     * Uses MediaRecorder if available, falls back to Web Speech API.
     */
    async voiceStartRecording() {
        // Close the normal composer if open
        if (this.state.composerType) {
            this.state.composerType = false;
        }

        // Reset voice state
        Object.assign(this.state, {
            voiceStatus: 'recording',
            voiceText: '',
            voiceRawText: '',
            voiceFormattedText: '',
            voiceError: '',
            voiceDuration: 0,
            voiceAiFormat: false,
        });

        // Load user preferences
        const settings = await getSettings();
        this.state.voiceQuickPost = !settings.review_mode;
        this.state.voiceAiFormat = settings.ai_format;

        try {
            if (this._voiceRecorder.isSupported) {
                // Primary: MediaRecorder + Whisper API
                await this._voiceRecorder.start();
                this._voiceUsingSpeechFallback = false;
            } else {
                // Fallback: Browser Speech Recognition
                this._voiceSpeechRecognizer = new SpeechRecognizer();
                if (!this._voiceSpeechRecognizer.isSupported) {
                    throw new Error(
                        'Neither audio recording nor speech recognition ' +
                        'is supported in this browser. Please use Chrome, ' +
                        'Edge, or Firefox.'
                    );
                }
                this._voiceSpeechPromise =
                    this._voiceSpeechRecognizer.start();
                this._voiceUsingSpeechFallback = true;
            }

            // Start duration timer
            this._voiceTimer = setInterval(() => {
                this.state.voiceDuration++;
                // Auto-stop at max duration
                if (
                    settings.max_seconds &&
                    this.state.voiceDuration >= settings.max_seconds
                ) {
                    this.voiceStopRecording();
                }
            }, 1000);
        } catch (error) {
            this.state.voiceStatus = 'idle';
            this.state.voiceError =
                error.message || 'Failed to start recording.';
            this._voiceNotify(this.state.voiceError, 'danger');
        }
    },

    /**
     * Stop recording and transcribe the audio.
     */
    async voiceStopRecording() {
        if (this.state.voiceStatus !== 'recording') {
            return;
        }

        // Stop timer
        clearInterval(this._voiceTimer);
        this._voiceTimer = null;

        this.state.voiceStatus = 'transcribing';

        try {
            let text = '';

            if (this._voiceUsingSpeechFallback) {
                // Speech Recognition fallback - text already accumulated
                this._voiceSpeechRecognizer.stop();
                text = await this._voiceSpeechPromise;
                this._voiceSpeechRecognizer = null;
                this._voiceSpeechPromise = null;
            } else {
                // MediaRecorder - send audio to Whisper API
                const audioBlob = await this._voiceRecorder.stop();
                const audioBase64 = await blobToBase64(audioBlob);

                const result = await rpc('/fusion_notes/transcribe', {
                    audio_base64: audioBase64,
                    mime_type: this._voiceRecorder.baseMimeType,
                });

                if (result.error) {
                    throw new Error(result.error);
                }
                text = result.text;
            }

            if (!text || !text.trim()) {
                throw new Error('No speech detected. Please try again.');
            }

            this.state.voiceRawText = text.trim();
            this.state.voiceText = this.state.voiceRawText;

            // AI format if enabled by default
            if (this.state.voiceAiFormat) {
                await this._voiceFormatText();
            }

            // Quick post or show for review
            if (this.state.voiceQuickPost) {
                await this._voicePostNote();
            } else {
                this.state.voiceStatus = 'review';
            }
        } catch (error) {
            this.state.voiceStatus = 'idle';
            this.state.voiceError =
                error.message || 'Transcription failed.';
            this._voiceNotify(this.state.voiceError, 'danger');
        }
    },

    /**
     * Toggle AI formatting on the transcribed text.
     */
    async voiceToggleAiFormat() {
        if (this.state.voiceStatus !== 'review') {
            return;
        }

        this.state.voiceAiFormat = !this.state.voiceAiFormat;

        if (this.state.voiceAiFormat) {
            if (this.state.voiceFormattedText) {
                // Use cached formatted text
                this.state.voiceText = this.state.voiceFormattedText;
            } else {
                // Fetch from GPT
                await this._voiceFormatText();
            }
        } else {
            // Switch back to raw transcription
            this.state.voiceText = this.state.voiceRawText;
        }
    },

    /**
     * Post the current voice note text to chatter.
     */
    async voicePostNote() {
        await this._voicePostNote();
    },

    /**
     * Cancel the voice note review and reset to idle.
     */
    voiceCancelNote() {
        this._voiceCleanup();
        Object.assign(this.state, {
            voiceStatus: 'idle',
            voiceText: '',
            voiceRawText: '',
            voiceFormattedText: '',
            voiceError: '',
            voiceDuration: 0,
        });
    },

    /**
     * Cancel an active recording without transcribing.
     */
    voiceCancelRecording() {
        this._voiceRecorder.cancel();
        if (this._voiceSpeechRecognizer) {
            this._voiceSpeechRecognizer.cancel();
            this._voiceSpeechRecognizer = null;
        }
        clearInterval(this._voiceTimer);
        this._voiceTimer = null;
        this.state.voiceStatus = 'idle';
        this.state.voiceDuration = 0;
    },

    /**
     * Format seconds as M:SS for the recording timer display.
     */
    voiceFormatDuration(seconds) {
        const mins = Math.floor(seconds / 60);
        const secs = seconds % 60;
        return `${mins}:${secs.toString().padStart(2, '0')}`;
    },

    /**
     * Handle textarea input for the review panel.
     */
    onVoiceTextInput(ev) {
        this.state.voiceText = ev.target.value;
    },

    /**
     * Override toggleComposer to close voice note review when the user
     * opens the normal Send message / Log note composer.
     */
    toggleComposer(mode = false, options = {}) {
        if (this.state.voiceStatus === 'review') {
            this.voiceCancelNote();
        }
        return super.toggleComposer(mode, options);
    },

    // ===================================================================
    // Private methods
    // ===================================================================

    /**
     * Send the raw transcription to GPT for professional formatting.
     */
    async _voiceFormatText() {
        const previousStatus = this.state.voiceStatus;
        this.state.voiceStatus = 'formatting';

        try {
            const result = await rpc('/fusion_notes/format', {
                text: this.state.voiceRawText,
            });

            if (result.error) {
                this._voiceNotify(
                    `Formatting failed: ${result.error}`,
                    'warning'
                );
                this.state.voiceAiFormat = false;
                this.state.voiceText = this.state.voiceRawText;
            } else {
                this.state.voiceFormattedText = result.text;
                this.state.voiceText = result.text;
            }
        } catch {
            this._voiceNotify(
                'AI formatting failed. Showing raw text.',
                'warning'
            );
            this.state.voiceAiFormat = false;
            this.state.voiceText = this.state.voiceRawText;
        }

        // Restore appropriate status
        this.state.voiceStatus =
            previousStatus === 'transcribing' || previousStatus === 'formatting'
                ? 'review'
                : previousStatus;
    },

    /**
     * Post the voice note to the thread's chatter via RPC.
     */
    async _voicePostNote() {
        if (!this.state.voiceText || !this.state.voiceText.trim()) {
            this._voiceNotify('Cannot post an empty note.', 'warning');
            return;
        }

        const thread = this.state.thread;
        if (!thread || !thread.id) {
            this._voiceNotify(
                'Cannot post: no active record.',
                'warning'
            );
            return;
        }

        try {
            const result = await rpc('/fusion_notes/post_note', {
                thread_model: thread.model,
                thread_id: thread.id,
                body: this.state.voiceText,
            });

            if (result.error) {
                throw new Error(result.error);
            }

            // Success - reset state and refresh chatter messages
            this._voiceNotify('Voice note posted.', 'success');
            this.voiceCancelNote();
            this.onPostCallback();
        } catch (error) {
            this._voiceNotify(
                `Failed to post note: ${error.message || 'Unknown error'}`,
                'danger'
            );
        }
    },

    /**
     * Show a notification to the user.
     */
    _voiceNotify(message, type = 'info') {
        if (this.notificationService) {
            this.notificationService.add(message, { type });
        }
    },

    /**
     * Clean up all voice recording resources.
     */
    _voiceCleanup() {
        if (this._voiceTimer) {
            clearInterval(this._voiceTimer);
            this._voiceTimer = null;
        }
        if (this._voiceRecorder) {
            this._voiceRecorder.cancel();
        }
        if (this._voiceSpeechRecognizer) {
            this._voiceSpeechRecognizer.cancel();
            this._voiceSpeechRecognizer = null;
        }
    },
});
