// Shared camera stream manager for webcam access (WebcamFileSource, Scan) - centralizes device
// enumeration, preferred-camera memory, stream reuse, and disconnect/reconnect handling.
class CameraManager {
    constructor() {
        this.availableCameras = [];
        this.activeStream = null;
        this.selectedCameraId = null;
        this.isInitialized = false;
        this.streamUsers = 0;
    }

    async enumerateDevices() {
        try {
            const devices = await navigator.mediaDevices.enumerateDevices();
            const videoDevices = devices.filter((d) => d.kind === 'videoinput' && d.deviceId);

            if (videoDevices.length === 0) return this.availableCameras;

            const uniqueMap = new Map();
            videoDevices.forEach((device) => uniqueMap.set(device.deviceId, device));

            this.availableCameras = Array.from(uniqueMap.values());
            return this.availableCameras;
        } catch (err) {
            console.error('Error enumerating devices:', err);
            return [];
        }
    }

    loadPreferredCamera() {
        const savedCameras = this.getRecentCameras();
        if (savedCameras.length === 0) return null;

        if (this.availableCameras.length === 0) {
            this.selectedCameraId = savedCameras[0];
            return savedCameras[0];
        }

        for (const cameraId of savedCameras) {
            if (this.availableCameras.some((cam) => cam.deviceId === cameraId)) {
                this.selectedCameraId = cameraId;
                return cameraId;
            }
        }
        return null;
    }

    getRecentCameras() {
        try {
            const saved = localStorage.getItem('recentCameraIds');
            return saved ? JSON.parse(saved) : [];
        } catch (err) {
            console.error('Error loading recent cameras:', err);
            return [];
        }
    }

    savePreferredCamera(cameraId) {
        if (!cameraId) return;

        const recentCameras = this.getRecentCameras();
        const updated = [cameraId, ...recentCameras.filter((id) => id !== cameraId)];

        try {
            localStorage.setItem('recentCameraIds', JSON.stringify(updated));
            this.selectedCameraId = cameraId;
        } catch (err) {
            console.error('Error saving recent cameras:', err);
        }
    }

    async openStream(cameraId = null) {
        const targetCameraId = cameraId || this.selectedCameraId;

        if (this.activeStream) {
            const currentSettings = this.activeStream.getVideoTracks()[0].getSettings();
            if (currentSettings.deviceId === targetCameraId) {
                this.streamUsers++;
                return this.activeStream;
            }
            this.closeStream(true);
        }

        if (!this.isInitialized) {
            await this.enumerateDevices();
            this.isInitialized = true;
        }

        this.selectedCameraId = this._selectCamera(targetCameraId);

        const constraints = {
            video: this.selectedCameraId
                ? { deviceId: { ideal: this.selectedCameraId } }
                : { facingMode: 'environment' },
            audio: false,
        };

        try {
            this.activeStream = await navigator.mediaDevices.getUserMedia(constraints);
            this.streamUsers = 1;
            await this.enumerateDevices();

            const actualDeviceId = this.activeStream.getVideoTracks()[0]?.getSettings().deviceId;
            if (actualDeviceId) this.selectedCameraId = actualDeviceId;

            this.activeStream.getVideoTracks().forEach((track) => {
                track.onended = () => this.handleTrackEnded(track);
            });

            return this.activeStream;
        } catch (err) {
            console.error('Error opening camera stream:', err);
            if (err.name === 'OverconstrainedError' && this.selectedCameraId) {
                this.selectedCameraId = null;
                localStorage.removeItem('recentCameraIds');
                return await this.openStream();
            }
            throw err;
        }
    }

    _selectCamera(targetCameraId) {
        if (targetCameraId) return targetCameraId;

        const preferredCamera = this.loadPreferredCamera();
        if (preferredCamera) return preferredCamera;

        return this.availableCameras[0]?.deviceId || null;
    }

    async switchCamera(cameraId) {
        this.savePreferredCamera(cameraId);
        const wasActive = this.streamUsers > 0;
        this.closeStream(true);
        return wasActive ? await this.openStream(cameraId) : null;
    }

    async handleTrackEnded(track) {
        console.warn('Camera track ended (may have been unplugged)');
        const disconnectedDeviceId = track.getSettings().deviceId;

        this.activeStream = null;
        this.streamUsers = 0;
        await this.enumerateDevices();

        if (this.availableCameras.some((cam) => cam.deviceId === disconnectedDeviceId)) return;

        if (this.availableCameras.length === 0) {
            console.error('No cameras available after disconnection');
            this._dispatchCameraEvent('camera-disconnected', { error: 'No cameras available' });
            return;
        }

        const fallbackCameraId = this._selectFallbackCamera();
        const fallbackCamera = this.availableCameras.find((cam) => cam.deviceId === fallbackCameraId);

        try {
            await this.openStream(fallbackCameraId);
            this._dispatchCameraEvent('camera-reconnected', {
                deviceId: fallbackCameraId,
                label: fallbackCamera?.label || 'Unknown',
            });
        } catch (err) {
            console.error('Failed to open fallback camera:', err);
            this._dispatchCameraEvent('camera-disconnected', { error: 'No cameras available' });
        }
    }

    _selectFallbackCamera() {
        const recentCameras = this.getRecentCameras();
        for (const cameraId of recentCameras) {
            if (this.availableCameras.some((cam) => cam.deviceId === cameraId)) {
                return cameraId;
            }
        }

        return this.availableCameras[0]?.deviceId;
    }

    _cleanCameraLabel(label) {
        if (!label) return 'Unknown';
        const parts = label.split(':').map((p) => p.trim());
        if (parts.length === 2 && parts[0] === parts[1]) {
            return parts[0];
        }
        return label;
    }

    _dispatchCameraEvent(eventName, detail) {
        if (detail.label) {
            detail.label = this._cleanCameraLabel(detail.label);
        }
        window.dispatchEvent(new CustomEvent(eventName, { detail }));
    }

    closeStream(force = false) {
        if (!this.activeStream) return;

        this.streamUsers = force ? 0 : Math.max(0, this.streamUsers - 1);

        if (this.streamUsers === 0) {
            this.activeStream.getTracks().forEach((track) => track.stop());
            this.activeStream = null;
        }
    }

    getAvailableCameras() {
        return this.availableCameras;
    }

    getActiveStream() {
        return this.activeStream;
    }

    getSelectedCameraId() {
        return this.selectedCameraId;
    }
}

export default new CameraManager();
